Created
October 17, 2019 06:56
-
-
Save ptflp/95997fd872d238eb12b76f1d3fb2d9c5 to your computer and use it in GitHub Desktop.
Unix tree cli
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"io" | |
"io/ioutil" | |
"log" | |
"os" | |
"path/filepath" | |
"regexp" | |
"strconv" | |
) | |
type Walker struct { | |
io.Writer | |
directory string | |
showHidden bool | |
onlyDirs bool | |
absPaths bool | |
sizes bool | |
help bool | |
version bool | |
permissions bool | |
reversed bool | |
level int | |
} | |
func getArguments() (*Walker, error) { | |
w := Walker{ | |
directory: ".", | |
Writer: os.Stdout, | |
level: -1, | |
} | |
pathUndefined := true | |
prevIsFlag := false | |
reg := regexp.MustCompile(`-\w{1,}`) | |
// Because flag module don't understand unnamed w before flags | |
args := os.Args[1:] | |
for i, arg := range args { | |
if arg == "--help" { | |
w.help = true | |
} else if arg == "--version" { | |
w.version = true | |
} else if reg.MatchString(arg) { | |
flags := []rune(arg) | |
for _, flag := range flags { | |
switch flag { | |
case 'a': | |
w.showHidden = true | |
case 'd': | |
w.onlyDirs = true | |
case 'f': | |
w.absPaths = true | |
case 's': | |
w.sizes = true | |
case 'p': | |
w.permissions = true | |
case 'r': | |
w.reversed = true | |
case 'L', 'o', '-': | |
continue | |
default: | |
return nil, errors.New("can't parse flag " + string(flag)) | |
} | |
} | |
if arg == "-o" { | |
if len(args)-1 < i+1 { | |
return nil, errors.New("can't find filename") | |
} | |
file, err := os.Create(args[i+1]) | |
if err != nil { | |
return nil, err | |
} | |
w.Writer = file | |
prevIsFlag = true | |
} else if arg == "-L" { | |
if len(args)-1 < i+1 { | |
return nil, errors.New("can't find depth of the directory tree") | |
} | |
depth, err := strconv.Atoi(args[i+1]) | |
if err != nil { | |
return nil, err | |
} | |
w.level = depth | |
prevIsFlag = true | |
} | |
} else if pathUndefined && !prevIsFlag { | |
w.directory = arg | |
pathUndefined = false | |
} | |
} | |
return &w, nil | |
} | |
func isHidden(filename string) bool { | |
if len(filename) != 0 { | |
return []rune(filename)[0] == '.' | |
} | |
return false | |
} | |
func main() { | |
walker, err := getArguments() | |
if err != nil { | |
log.Fatalln(err) | |
} | |
if walker.help { | |
err := walker.showHelp() | |
if err != nil { | |
log.Fatalln(err) | |
} | |
os.Exit(0) | |
} | |
if walker.version { | |
err := walker.showVersion() | |
if err != nil { | |
log.Fatalln(err) | |
} | |
os.Exit(0) | |
} | |
filename, err := filepath.Abs(walker.directory) | |
if err != nil { | |
log.Fatalln(err) | |
} | |
if !walker.absPaths { | |
_, filename = filepath.Split(filename) | |
} | |
err = walker.write(filename) | |
if err != nil { | |
log.Fatalln(err) | |
} | |
if err := walker.showDir(walker.directory, "", 0); err != nil { | |
log.Println(err) | |
os.Exit(1) | |
} | |
} | |
func (w Walker) write(data string) error { | |
b := []byte(data + "\n") | |
_, err := w.Write(b) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func (w Walker) showVersion() error { | |
return w.write("indriver test task ver 0.0.0.0001 :)") | |
} | |
func (w Walker) showHelp() error { | |
usage := ` | |
Usage: appname [--help] [--version] [-adfpsr] [-L level] [-o filename] | |
--help Outputs a verbose usage listing. | |
--version Outputs the version of tree. | |
-a All files are printed. By default tree does not print hidden files (those beginning with a dot '.'). | |
-d List directories only. | |
-f Prints the full path prefix for each file. | |
-p Print the file permissions for each file (as per ls -l). | |
-s Print the size of each file in bytes along with the name. | |
-r Sort the output in reverse alphabetic order. | |
-L level Max display depth of the directory tree. | |
-o filename Send output to filename.` | |
err := w.showVersion() | |
if err != nil { | |
return err | |
} | |
err = w.write(usage) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
func (w Walker) showDir(dirPath string, prefix string, level int) error { | |
if w.level >= 1 && level > w.level { | |
return nil | |
} | |
files, err := ioutil.ReadDir(dirPath) | |
if err != nil { | |
return errors.New("can't open directory " + dirPath) | |
} | |
// avoid memory allocation | |
tfiles := make([]os.FileInfo, 0, len(files)) | |
for _, f := range files { | |
if (!w.onlyDirs || f.IsDir()) && // dirs only/all | |
(w.showHidden || !isHidden(f.Name())) { // with/without hidden | |
tfiles = append(tfiles, f) | |
} | |
files = tfiles | |
} | |
if w.reversed { | |
for i := range files[:len(files)/2] { | |
files[i], files[len(files)-i-1] = files[len(files)-i-1], files[i] | |
} | |
} | |
for i, file := range files { | |
size := "" | |
if w.sizes { | |
size = " " + strconv.FormatInt(file.Size(), 10) | |
} | |
filename := file.Name() | |
showedName := filename | |
if w.absPaths { | |
showedName, err = filepath.Abs(filename) | |
if err != nil { | |
return err | |
} | |
} | |
outerPrefix := "├─── " | |
innerPrefix := "| " | |
if i == len(files)-1 { | |
outerPrefix = "└─── " | |
innerPrefix = " " | |
} | |
permissions := "" | |
if w.permissions { | |
permissions = " " + file.Mode().String() | |
} | |
if file.IsDir() { | |
err := w.write(prefix + outerPrefix + showedName + size + permissions) | |
if err != nil { | |
return err | |
} | |
err = w.showDir(filepath.Join(dirPath, filename), prefix+innerPrefix, level+1) | |
if err != nil { | |
return err | |
} | |
} else if !w.onlyDirs { | |
err := w.write(prefix + outerPrefix + showedName + size + permissions) | |
if err != nil { | |
return err | |
} | |
} | |
} | |
return nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment