1package main
2
3import (
4	"errors"
5	"flag"
6	"fmt"
7	"os"
8
9	"github.com/a8m/tree"
10	"github.com/a8m/tree/ostree"
11)
12
13var (
14	// List
15	a          = flag.Bool("a", false, "")
16	d          = flag.Bool("d", false, "")
17	f          = flag.Bool("f", false, "")
18	ignorecase = flag.Bool("ignore-case", false, "")
19	noreport   = flag.Bool("noreport", false, "")
20	l          = flag.Bool("l", false, "")
21	L          = flag.Int("L", 3, "")
22	P          = flag.String("P", "", "")
23	I          = flag.String("I", "", "")
24	o          = flag.String("o", "", "")
25	// Files
26	s      = flag.Bool("s", false, "")
27	h      = flag.Bool("h", false, "")
28	p      = flag.Bool("p", false, "")
29	u      = flag.Bool("u", false, "")
30	g      = flag.Bool("g", false, "")
31	Q      = flag.Bool("Q", false, "")
32	D      = flag.Bool("D", false, "")
33	inodes = flag.Bool("inodes", false, "")
34	device = flag.Bool("device", false, "")
35	// Sort
36	U         = flag.Bool("U", false, "")
37	v         = flag.Bool("v", false, "")
38	t         = flag.Bool("t", false, "")
39	c         = flag.Bool("c", false, "")
40	r         = flag.Bool("r", false, "")
41	dirsfirst = flag.Bool("dirsfirst", false, "")
42	sort      = flag.String("sort", "", "")
43	// Graphics
44	i = flag.Bool("i", false, "")
45	C = flag.Bool("C", false, "")
46)
47
48var usage = `Usage: tree [options...] [paths...]
49
50Options:
51    ------- Listing options -------
52    -a		    All files are listed.
53    -d		    List directories only.
54    -l		    Follow symbolic links like directories.
55    -f		    Print the full path prefix for each file.
56    -L		    Descend only level directories deep.
57    -P		    List only those files that match the pattern given.
58    -I		    Do not list files that match the given pattern.
59    --ignore-case   Ignore case when pattern matching.
60    --noreport	    Turn off file/directory count at end of tree listing.
61    -o filename	    Output to file instead of stdout.
62    -------- File options ---------
63    -Q		    Quote filenames with double quotes.
64    -p		    Print the protections for each file.
65    -u		    Displays file owner or UID number.
66    -g		    Displays file group owner or GID number.
67    -s		    Print the size in bytes of each file.
68    -h		    Print the size in a more human readable way.
69    -D		    Print the date of last modification or (-c) status change.
70    --inodes	    Print inode number of each file.
71    --device	    Print device ID number to which each file belongs.
72    ------- Sorting options -------
73    -v		    Sort files alphanumerically by version.
74    -t		    Sort files by last modification time.
75    -c		    Sort files by last status change time.
76    -U		    Leave files unsorted.
77    -r		    Reverse the order of the sort.
78    --dirsfirst	    List directories before files (-U disables).
79    --sort X	    Select sort: name,version,size,mtime,ctime.
80    ------- Graphics options ------
81    -i		    Don't print indentation lines.
82    -C		    Turn colorization on always.
83`
84
85func main() {
86	flag.Usage = func() { fmt.Fprint(os.Stderr, usage) }
87	var nd, nf int
88	var dirs = []string{"."}
89	flag.Parse()
90	// Make it work with leading dirs
91	if args := flag.Args(); len(args) > 0 {
92		dirs = args
93	}
94	// Output file
95	var outFile = os.Stdout
96	var err error
97	if *o != "" {
98		outFile, err = os.Create(*o)
99		if err != nil {
100			errAndExit(err)
101		}
102	}
103	defer outFile.Close()
104	// Check sort-type
105	if *sort != "" {
106		switch *sort {
107		case "version", "mtime", "ctime", "name", "size":
108		default:
109			msg := fmt.Sprintf("sort type '%s' not valid, should be one of: "+
110				"name,version,size,mtime,ctime", *sort)
111			errAndExit(errors.New(msg))
112		}
113	}
114	// Set options
115	opts := &tree.Options{
116		// Required
117		Fs:      new(ostree.FS),
118		OutFile: outFile,
119		// List
120		All:        *a,
121		DirsOnly:   *d,
122		FullPath:   *f,
123		DeepLevel:  *L,
124		FollowLink: *l,
125		Pattern:    *P,
126		IPattern:   *I,
127		IgnoreCase: *ignorecase,
128		// Files
129		ByteSize: *s,
130		UnitSize: *h,
131		FileMode: *p,
132		ShowUid:  *u,
133		ShowGid:  *g,
134		LastMod:  *D,
135		Quotes:   *Q,
136		Inodes:   *inodes,
137		Device:   *device,
138		// Sort
139		NoSort:    *U,
140		ReverSort: *r,
141		DirSort:   *dirsfirst,
142		VerSort:   *v || *sort == "version",
143		ModSort:   *t || *sort == "mtime",
144		CTimeSort: *c || *sort == "ctime",
145		NameSort:  *sort == "name",
146		SizeSort:  *sort == "size",
147		// Graphics
148		NoIndent: *i,
149		Colorize: *C,
150	}
151	for _, dir := range dirs {
152		inf := tree.New(dir)
153		d, f := inf.Visit(opts)
154		nd, nf = nd+d, nf+f
155		inf.Print(opts)
156	}
157	// Print footer report
158	if !*noreport {
159		footer := fmt.Sprintf("\n%d directories", nd)
160		if !opts.DirsOnly {
161			footer += fmt.Sprintf(", %d files", nf)
162		}
163		fmt.Fprintln(outFile, footer)
164	}
165}
166
167func usageAndExit(msg string) {
168	if msg != "" {
169		fmt.Fprintf(os.Stderr, msg)
170		fmt.Fprintf(os.Stderr, "\n\n")
171	}
172	flag.Usage()
173	fmt.Fprintf(os.Stderr, "\n")
174	os.Exit(1)
175}
176
177func errAndExit(err error) {
178	fmt.Fprintf(os.Stderr, "tree: \"%s\"\n", err)
179	os.Exit(1)
180}
181