forked from vanadium-archive/go.ref
/
printer.go
186 lines (175 loc) · 4.86 KB
/
printer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package dirprinter contains utilities for dumping the contents and structure
// of a directory tree.
package dirprinter
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
)
const (
// \u2502 is Box Drawings Light Vertical.
vertLine = '\u2502'
// \u2514 is Box Drawings Light Up And Right.
upRightLine = '\u2514'
// \u251c is Box Drawings Light Vertical And Right.
vertRightLine = '\u251c'
// \u2500 is Box Drawings Light Horizontal.
horizLine = '\u2500'
)
// computeLastChild traverses dir and returns a map containing the descendants
// of dir that are the last entry in their respective parents.
func computeLastChild(dir string) (map[string]bool, error) {
lastChild := make(map[string]string)
setChild := func(path string, _ os.FileInfo, _ error) error {
lastChild[filepath.Dir(path)] = path
return nil
}
if err := filepath.Walk(dir, setChild); err != nil {
return nil, err
}
isLastChild := make(map[string]bool)
for _, p := range lastChild {
isLastChild[p] = true
}
return isLastChild, nil
}
// printDirTree traverses dir and pretty-prints the tree under dir.
func printDirTree(w io.Writer, dir string) error {
isLastChild, err := computeLastChild(dir)
if err != nil {
return err
}
printFn := func(path string, info os.FileInfo, err error) error {
me := path
var graphics []rune
prepend := func(p []rune, r ...rune) []rune {
return append(r, p...)
}
if err != nil {
// If the entry has an error, we want to print the error
// at the appropriate place in the tree (right below the
// entry, with the appropriate graphics preceding it).
me = filepath.Join(me, "dummy")
}
for me != dir {
parent, _ := filepath.Split(me)
me = filepath.Clean(parent)
if me == dir {
break
}
graphics = prepend(graphics, ' ', ' ')
if isLastChild[me] {
graphics = prepend(graphics, ' ')
} else {
graphics = prepend(graphics, vertLine)
}
}
if err != nil {
fmt.Fprint(w, string(graphics))
fmt.Fprintf(w, "ERROR: %v\n", err)
return filepath.SkipDir
}
if filepath.Clean(path) != dir {
if isLastChild[path] {
graphics = append(graphics, upRightLine)
} else {
graphics = append(graphics, vertRightLine)
}
graphics = append(graphics, horizLine, horizLine)
}
fmt.Fprint(w, string(graphics))
printEntry(w, info)
fmt.Fprint(w, "\n")
return nil
}
return filepath.Walk(dir, printFn)
}
// printEntry is used by printDirTree to show a summary of a directory entry.
func printEntry(w io.Writer, info os.FileInfo) {
fmt.Fprint(w, info.Name())
if info.IsDir() {
fmt.Fprint(w, string(filepath.Separator))
} else {
fmt.Fprintf(w, " %dB", info.Size())
}
fmt.Fprintf(w, " %v", info.ModTime().Format("15:04:05.000000"))
}
// printDirContents traverses the given dir and prints out information about
// descendant files. If the file is text, it also dumps the contents out.
func printDirContents(w io.Writer, dir string) error {
printFn := func(path string, info os.FileInfo, _ error) error {
if info.IsDir() {
return nil
}
path = filepath.Clean(path)
printPath, err := filepath.Rel(dir, path)
if err != nil {
return err
}
bannerSep := strings.Repeat(string(horizLine), len(printPath))
fmt.Fprintln(w, bannerSep)
fmt.Fprintln(w, printPath)
fmt.Fprintln(w, bannerSep)
if err := dumpFile(w, path, info); err != nil {
fmt.Fprintf(w, "ERROR: %v\n", err)
}
return nil
}
return filepath.Walk(dir, printFn)
}
// dumpFile prints out information about the file at path.
func dumpFile(w io.Writer, path string, info os.FileInfo) error {
switch {
case info.Mode().IsRegular():
return dumpRegular(w, path)
case info.Mode()&os.ModeSymlink != 0:
target, err := filepath.EvalSymlinks(path)
if err != nil {
return err
}
fmt.Fprintf(w, "Link to: %s\n", target)
return nil
default:
fmt.Fprintf(w, "MODE: %x\n", info.Mode())
return nil
}
}
// dumpRegular prints out information about the regular file at path. If the
// content type is text, it dumps out the contents of the file.
func dumpRegular(w io.Writer, path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
data := make([]byte, 512)
count, err := io.ReadAtLeast(f, data, 512)
if err != nil && err != io.ErrUnexpectedEOF {
return err
}
data = data[:count]
contentType := http.DetectContentType(data)
fmt.Fprintln(w, "Content-type:", contentType)
if strings.HasPrefix(contentType, "text") {
if _, err := w.Write(data); err != nil {
return err
}
io.Copy(w, f)
fmt.Fprintln(w)
}
return nil
}
// DumpDir dumps the dir's structure and files.
func DumpDir(w io.Writer, dir string) error {
dir = filepath.Clean(dir)
if err := printDirTree(w, dir); err != nil {
return err
}
return printDirContents(w, dir)
}