-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcli.go
187 lines (147 loc) · 4.69 KB
/
cli.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
187
package main
import (
"bufio"
"context"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/jessevdk/go-flags"
"golang.org/x/xerrors"
)
// ListOptions is Options for basic gtree command.
type ListOptions struct {
// ListSearchOptions is options which use when searching file tree.
ListSearchOptions *ListSearchOptions
// ListDisplayOptions is options which use when display file tree.
ListDisplayOptions *ListDisplayOptions
}
// ListSearchOptions is options which use when searching file tree.
type ListSearchOptions struct {
All []bool `short:"a" long:"all" description:"All files are listed."`
OnlyDirectory []bool `short:"d" description:"List directories only."`
IgnorePatterns []string `short:"I" description:"Do not list files that match the given pattern."`
Level *int `short:"L" long:"level" description:"Descend only level directories deep."`
}
// IsAll returns true, if user specify '-a' or '-all' option.
func (l *ListSearchOptions) IsAll() bool {
return len(l.All) != 0
}
// IsOnlyDirectry returns true, if user specify '-d' option.
func (l *ListSearchOptions) IsOnlyDirectry() bool {
return len(l.OnlyDirectory) != 0
}
// ListDisplayOptions is options which use when display file tree.
type ListDisplayOptions struct {
FullPath []bool `short:"f" description:"Print the full path prefix for each file."`
Output string `short:"o" description:"Output to file instead of stdout."`
NoIcons []bool `short:"n" description:"Do not show the icon of files and directories"`
}
// IsFullPath returns true, if user specify '-f' option.
func (l *ListDisplayOptions) IsFullPath() bool {
return len(l.FullPath) != 0
}
// NoIcon returns true, if user specify '-n' option.
func (l *ListDisplayOptions) NoIcon() bool {
return len(l.NoIcons) != 0
}
type MiscellaneousOptions struct {
Version func() `long:"version" description:"show version"`
}
// Options is all options.
type Options struct {
ListOptions *ListOptions `group:"List Options"`
MiscellaneousOptions *MiscellaneousOptions `group:"Miscellaneous Options"`
}
func newOptionsParser(opts *Options) *flags.Parser {
opts.ListOptions = &ListOptions{}
opts.MiscellaneousOptions = &MiscellaneousOptions{}
opts.MiscellaneousOptions.Version = func() {
fmt.Println("gtree v0.2")
os.Exit(0)
}
parser := flags.NewParser(opts, flags.Default)
parser.Name = "gtree"
parser.Usage = "[-adfn] [--version] [-I pattern] [-o filename] [-L level] [--help] [--] [<directory list>]"
return parser
}
// run is to search files, and display these files.
// Search and Display communicate through channel.
func run(ctx context.Context) int {
var opts Options
parser := newOptionsParser(&opts)
directories, err := parser.Parse()
if err != nil {
return statusErr
}
if len(directories) == 0 {
directories = append(directories, ".")
}
for _, d := range directories {
err = showTree(d, opts)
if err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", parser.Name, err)
return statusErr
}
}
return statusOK
}
func showTree(root string, opts Options) error {
if opts.ListOptions.ListSearchOptions.Level != nil && *opts.ListOptions.ListSearchOptions.Level <= 0 {
return fmt.Errorf("Invalid level, must be greater than 0.")
}
f, err := os.Stat(root)
if err != nil {
return xerrors.Errorf("failed to find root: %v", err)
}
base, _ := filepath.Split(root)
rootFile := NewFileInfoForBase(f, nil, base, true)
ch := make(chan FileInfo)
if !rootFile.IsDir() {
errRootIsNotDir := fmt.Errorf("%s is not dir", rootFile.Name())
rootFile.SetError(errRootIsNotDir)
}
// Search files.
go Dirwalk(rootFile, ch, opts.ListOptions.ListSearchOptions)
// Display files.
var out io.Writer
if outputFile := opts.ListOptions.ListDisplayOptions.Output; outputFile != "" {
var err error
if err = checkOverWrite(outputFile); err != nil {
return xerrors.Errorf("denided overwrite: %w", err)
}
out, err = os.Create(outputFile)
if err != nil {
return xerrors.Errorf("file create/open error: %w", err)
}
defer out.(*os.File).Close()
} else {
out = os.Stdout
}
w := bufio.NewWriter(out)
p := NewPrinter(opts.ListOptions.ListDisplayOptions)
for file := range ch {
err := p.Write(w, file)
if err != nil {
return err
}
}
w.Flush()
return nil
}
var errFileExist = fmt.Errorf("output file already exists")
func checkOverWrite(filename string) error {
var err error
if _, err = os.Stat(filename); !os.IsNotExist(err) {
fmt.Printf("Output file already exists. Are you sure to overwrite %s?[Y/n] ", filename)
var answer string
if _, err = fmt.Scan(&answer); err != nil {
return xerrors.Errorf("failed to scan answer: %v", err)
}
if strings.ToLower(strings.TrimRight(answer, "\n")) != "y" {
return errFileExist
}
}
return nil
}