Skip to content

Instantly share code, notes, and snippets.

@ptflp
Created October 17, 2019 06:56
Show Gist options
  • Save ptflp/95997fd872d238eb12b76f1d3fb2d9c5 to your computer and use it in GitHub Desktop.
Save ptflp/95997fd872d238eb12b76f1d3fb2d9c5 to your computer and use it in GitHub Desktop.
Unix tree cli
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