initial commit [141.14.140.180,mike]
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
._*
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/perl
|
||||
######################################################################## count files and directories (mwx'2016)
|
||||
use Getopt::Std;getopts('dnh');
|
||||
|
||||
$version='1.0.2';
|
||||
|
||||
if ($opt_h) {;&help;exit;}
|
||||
|
||||
if (-d $ARGV[0]) {
|
||||
&dx($ARGV[0]);
|
||||
} else {
|
||||
&dx(".");
|
||||
}
|
||||
|
||||
sub dx() {
|
||||
my($dir)=@_;
|
||||
$ml=length($dir);
|
||||
|
||||
opendir(DIR,$dir);
|
||||
while($f=readdir(DIR)) {
|
||||
if ($f ne '.' && $f ne '..') {
|
||||
|
||||
if (-d "$dir/$f") {
|
||||
$n=0;$s=0;
|
||||
open(IN,"find '$dir/$f' -type f |");
|
||||
while(<IN>) {
|
||||
chomp();
|
||||
$s+=(stat($_))[7];
|
||||
$ts+=(stat($_))[7];
|
||||
$n++;
|
||||
$tn++;
|
||||
}
|
||||
close(IN);
|
||||
$N{"$f/"}=$n;
|
||||
$S{"$f/"}=$s;
|
||||
$ml=length($f) if (length($f)>$ml);
|
||||
}
|
||||
|
||||
if (-f "$dir/$f" && !$opt_d) {
|
||||
$S{$f}+=(stat("$dir/$f"))[7];
|
||||
$ts+=(stat("$dir/$f"))[7];
|
||||
$N{$f}++;
|
||||
$tn++;
|
||||
$ml=length($f) if (length($f)>$ml);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
closedir(DIR);
|
||||
|
||||
$frm=sprintf "%%-%ds",$ml+1;
|
||||
$out=sprintf "$frm %8d %8s\n",$dir,$tn,&formsize($ts);
|
||||
|
||||
if ($opt_n) {
|
||||
for $f (sort { $N{$a} <=> $N{$b} } (keys(%N))) {
|
||||
$out.=sprintf " $frm %8d %8s\n",$f,$N{$f},&formsize($S{$f});
|
||||
}
|
||||
} else {
|
||||
for $f (sort { $S{$a} <=> $S{$b} } (keys(%S))) {
|
||||
$out.=sprintf " $frm %8d %8s\n",$f,$N{$f},&formsize($S{$f});
|
||||
}
|
||||
}
|
||||
print $out;
|
||||
}
|
||||
|
||||
####################################################################################################### SUPPORT
|
||||
|
||||
sub formsize() {
|
||||
my($b)=@_;
|
||||
if ($b>1024*1024*1024*1024) {
|
||||
$s=sprintf("%.0fT",$b/1024/1024/1024/1024)
|
||||
} elsif ($b>1024*1024*1024) {
|
||||
$s=sprintf("%.0fG",$b/1024/1024/1024)
|
||||
} elsif ($b>1024*1024) {
|
||||
$s=sprintf("%.0fM",$b/1024/1024)
|
||||
} elsif ($b>1024) {
|
||||
$s=sprintf("%.0fK",$b/1024)
|
||||
} else {
|
||||
$s=sprintf("%.0f",$b)
|
||||
}
|
||||
return $s;
|
||||
}
|
||||
|
||||
sub help() {
|
||||
print <<"ENDOFHELP";
|
||||
dx ${version}, count files and directories, mwx'2016
|
||||
usage: dx [-n] [-d] [path]
|
||||
-n sort by number of files (default by size)
|
||||
-d count only directories
|
||||
-h this help
|
||||
ENDOFHELP
|
||||
}
|
||||
|
||||
########################################################################################################### END
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
// Dateityp → Farbe, nach den file_type-Rollen des eza-Themes
|
||||
// (~/src/mwxcol/themes/eza/theme.yml), damit dieselbe Datei in eza und dx
|
||||
// dieselbe Farbe hat.
|
||||
var byExt = map[string]*color.Color{}
|
||||
|
||||
func register(c *color.Color, exts ...string) {
|
||||
for _, e := range exts {
|
||||
byExt[e] = c
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// source — blau
|
||||
register(cBlue,
|
||||
"go", "c", "h", "cc", "cpp", "cxx", "hpp", "rs", "py", "pl", "pm", "t",
|
||||
"sh", "bash", "zsh", "fish", "js", "mjs", "cjs", "ts", "jsx", "tsx",
|
||||
"rb", "java", "kt", "swift", "m", "mm", "php", "lua", "sql", "r", "jl",
|
||||
"hs", "el", "vim", "scala", "cs", "dart", "zig", "asm", "s", "f90",
|
||||
"html", "htm", "css", "scss", "sass", "less", "vue", "svelte",
|
||||
"json", "yml", "yaml", "toml", "ini", "conf", "cfg", "xml", "plist",
|
||||
"md", "markdown", "tex", "awk", "sed")
|
||||
// compressed — orange
|
||||
register(cOrange,
|
||||
"zip", "tar", "gz", "tgz", "bz2", "tbz", "xz", "txz", "zst", "7z",
|
||||
"rar", "lz", "lzma", "z", "jar", "war", "dmg", "iso", "pkg", "deb", "rpm")
|
||||
// image — violett
|
||||
register(cViolet,
|
||||
"jpg", "jpeg", "png", "gif", "bmp", "tif", "tiff", "svg", "webp",
|
||||
"heic", "heif", "ico", "psd", "ai", "eps", "raw", "cr2", "nef", "dng",
|
||||
"stl", "obj3d")
|
||||
// video — pink
|
||||
register(cPink,
|
||||
"mp4", "mov", "avi", "mkv", "webm", "m4v", "mpg", "mpeg", "wmv", "flv", "m2ts")
|
||||
// music / lossless — grün
|
||||
register(cGreen,
|
||||
"mp3", "aac", "m4a", "ogg", "opus", "wma", "flac", "wav", "aiff", "aif", "alac")
|
||||
// crypto — rot
|
||||
register(cRed,
|
||||
"asc", "gpg", "pgp", "sig", "key", "pem", "crt", "cer", "p12", "kdbx")
|
||||
// document — weiß
|
||||
register(cWhite,
|
||||
"pdf", "doc", "docx", "odt", "xls", "xlsx", "ods", "ppt", "pptx", "odp",
|
||||
"epub", "rtf", "txt", "csv", "pages", "numbers")
|
||||
// compiled — grau
|
||||
register(cGrey,
|
||||
"o", "a", "so", "dylib", "obj", "lib", "class", "pyc", "pyo", "wasm", "d")
|
||||
// temp — dunkel
|
||||
register(cDarker,
|
||||
"tmp", "temp", "swp", "swo", "bak", "old", "orig", "rej", "lock",
|
||||
"part", "crdownload", "log")
|
||||
}
|
||||
|
||||
// build — gelb (README, Makefile, …)
|
||||
var byName = map[string]*color.Color{
|
||||
"makefile": cYellow, "gnumakefile": cYellow, "cmakelists.txt": cYellow,
|
||||
"dockerfile": cYellow, "justfile": cYellow, "rakefile": cYellow,
|
||||
"go.mod": cYellow, "go.sum": cYellow, "package.json": cYellow,
|
||||
"package-lock.json": cYellow, "cargo.toml": cYellow, "cargo.lock": cYellow,
|
||||
"license": cYellow, "copying": cYellow,
|
||||
".ds_store": cDarker, ".gitignore": cDark, ".gitattributes": cDark,
|
||||
}
|
||||
|
||||
func fileColor(name string, mode os.FileMode) *color.Color {
|
||||
if mode&0o111 != 0 { // ausführbar schlägt alles andere, wie in eza
|
||||
return cExecB
|
||||
}
|
||||
low := strings.ToLower(name)
|
||||
if c, ok := byName[low]; ok {
|
||||
return c
|
||||
}
|
||||
if strings.HasPrefix(low, "readme") || strings.HasPrefix(low, "makefile") {
|
||||
return cYellow
|
||||
}
|
||||
if strings.HasSuffix(name, "~") {
|
||||
return cDarker
|
||||
}
|
||||
if ext := strings.TrimPrefix(filepath.Ext(low), "."); ext != "" {
|
||||
if c, ok := byExt[ext]; ok {
|
||||
return c
|
||||
}
|
||||
}
|
||||
return cWhite
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module dx
|
||||
|
||||
go 1.26.1
|
||||
|
||||
require github.com/fatih/color v1.19.0
|
||||
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
|
||||
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
@@ -0,0 +1,501 @@
|
||||
// dx — count files and directories, mwx'2016
|
||||
//
|
||||
// Go-Portierung des Perl-Originals (dx 1.0.2), Ausgabe eingefärbt nach der
|
||||
// mwxcol-Palette (~/src/mwxcol). Ohne Terminal (Pipe, NO_COLOR, --color=never)
|
||||
// bleibt die Ausgabe unverändert einfarbig.
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
const version = "2.1.0"
|
||||
|
||||
// ------------------------------------------------------------------ Palette
|
||||
|
||||
type rgb struct{ r, g, b int }
|
||||
|
||||
var (
|
||||
white = rgb{0xf0, 0xf0, 0xea}
|
||||
yellow = rgb{0xf9, 0xe2, 0xaf}
|
||||
orange = rgb{0xfa, 0xb3, 0x87}
|
||||
red = rgb{0xf3, 0x8b, 0xa8}
|
||||
green = rgb{0x94, 0xe2, 0xd5}
|
||||
blue = rgb{0xb4, 0xbe, 0xfe}
|
||||
pink = rgb{0xf5, 0xb2, 0xf7}
|
||||
violet = rgb{0xcb, 0xa6, 0xf7}
|
||||
grey = rgb{0xaa, 0xaa, 0xbb}
|
||||
dark = rgb{0x77, 0x77, 0x88}
|
||||
darker = rgb{0x44, 0x44, 0x55}
|
||||
)
|
||||
|
||||
func plain(c rgb) *color.Color { return color.RGB(c.r, c.g, c.b) }
|
||||
func bold(c rgb) *color.Color { return color.New(color.Bold).AddRGB(c.r, c.g, c.b) }
|
||||
|
||||
// Rollen wie im eza-Theme, damit dx und eza dieselbe Datei gleich einfärben.
|
||||
var (
|
||||
cWhite = plain(white)
|
||||
cYellow = plain(yellow)
|
||||
cOrange = plain(orange)
|
||||
cRed = plain(red)
|
||||
cGreen = plain(green)
|
||||
cBlue = plain(blue)
|
||||
cPink = plain(pink)
|
||||
cViolet = plain(violet)
|
||||
cGrey = plain(grey)
|
||||
cDark = plain(dark)
|
||||
cDarker = plain(darker)
|
||||
cDirB = bold(blue) // Verzeichnis
|
||||
cExecB = bold(green) // ausführbar
|
||||
cWhiteB = bold(white)
|
||||
cYellowB = bold(yellow)
|
||||
cOrangeB = bold(orange)
|
||||
cRedB = bold(red)
|
||||
cGreenB = bold(green)
|
||||
cDarkB = bold(dark)
|
||||
)
|
||||
|
||||
// ------------------------------------------------------------------- Zählen
|
||||
|
||||
type entry struct {
|
||||
name string // Anzeigename, Verzeichnisse mit "/"
|
||||
col *color.Color
|
||||
n int64
|
||||
size int64
|
||||
agg bool // Sammelzeile aus -t, kein echter Eintrag
|
||||
}
|
||||
|
||||
type result struct {
|
||||
entries []entry
|
||||
totalN int64
|
||||
totalS int64
|
||||
unreadbl int64
|
||||
}
|
||||
|
||||
func scan(dir string, onlyDirs bool) (result, error) {
|
||||
des, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return result{}, err
|
||||
}
|
||||
|
||||
var res result
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
sem := make(chan struct{}, runtime.NumCPU())
|
||||
|
||||
for _, de := range des {
|
||||
name := de.Name()
|
||||
path := filepath.Join(dir, name)
|
||||
|
||||
// Perls -d/-f folgen Symlinks; ein toter Link fällt durch beide Tests.
|
||||
st, err := os.Stat(path)
|
||||
if err != nil {
|
||||
atomic.AddInt64(&res.unreadbl, 1)
|
||||
continue
|
||||
}
|
||||
isLink := de.Type()&os.ModeSymlink != 0
|
||||
|
||||
switch {
|
||||
case st.IsDir():
|
||||
wg.Go(func() {
|
||||
sem <- struct{}{}
|
||||
defer func() { <-sem }()
|
||||
|
||||
// WalkDir folgt Symlinks nicht — wie `find <pfad> -type f`.
|
||||
n, size := walk(path, &res.unreadbl)
|
||||
|
||||
col := cDirB
|
||||
if isLink {
|
||||
col = cPink
|
||||
}
|
||||
mu.Lock()
|
||||
res.entries = append(res.entries, entry{name: name + "/", col: col, n: n, size: size})
|
||||
res.totalN += n
|
||||
res.totalS += size
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
case st.Mode().IsRegular() && !onlyDirs:
|
||||
col := fileColor(name, st.Mode())
|
||||
if isLink {
|
||||
col = cPink
|
||||
}
|
||||
mu.Lock()
|
||||
res.entries = append(res.entries, entry{name: name, col: col, n: 1, size: st.Size()})
|
||||
res.totalN++
|
||||
res.totalS += st.Size()
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func walk(root string, unreadable *int64) (n, size int64) {
|
||||
filepath.WalkDir(root, func(_ string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
atomic.AddInt64(unreadable, 1)
|
||||
return nil
|
||||
}
|
||||
if !d.Type().IsRegular() { // wie find -type f
|
||||
return nil
|
||||
}
|
||||
info, err := d.Info()
|
||||
if err != nil {
|
||||
atomic.AddInt64(unreadable, 1)
|
||||
return nil
|
||||
}
|
||||
n++
|
||||
size += info.Size()
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------ Ausgabe
|
||||
|
||||
// Schwellen wie im Perl-Original; unterhalb von 10 der jeweiligen Einheit
|
||||
// jedoch mit einer Nachkommastelle — "1.9G" statt "2G".
|
||||
func formSize(b int64) string {
|
||||
const k = 1024
|
||||
f := float64(b)
|
||||
switch {
|
||||
case b > k*k*k*k:
|
||||
return scaled(f/k/k/k/k, "T")
|
||||
case b > k*k*k:
|
||||
return scaled(f/k/k/k, "G")
|
||||
case b > k*k:
|
||||
return scaled(f/k/k, "M")
|
||||
case b > k:
|
||||
return scaled(f/k, "K")
|
||||
default:
|
||||
return fmt.Sprintf("%.0f", f) // ganze Bytes, nie gebrochen
|
||||
}
|
||||
}
|
||||
|
||||
func scaled(v float64, unit string) string {
|
||||
if v < 9.95 {
|
||||
return fmt.Sprintf("%.1f%s", v, unit)
|
||||
}
|
||||
return fmt.Sprintf("%.0f%s", v, unit)
|
||||
}
|
||||
|
||||
// Größenrampe wie im eza-Theme: byte/kilo grün, mega gelb, giga orange, huge rot.
|
||||
func sizeColor(b int64, strong bool) *color.Color {
|
||||
switch {
|
||||
case b == 0:
|
||||
if strong {
|
||||
return cDarkB
|
||||
}
|
||||
return cDark
|
||||
case b > 1024*1024*1024*1024:
|
||||
if strong {
|
||||
return cRedB
|
||||
}
|
||||
return cRed
|
||||
case b > 1024*1024*1024:
|
||||
if strong {
|
||||
return cOrangeB
|
||||
}
|
||||
return cOrange
|
||||
case b > 1024*1024:
|
||||
if strong {
|
||||
return cYellowB
|
||||
}
|
||||
return cYellow
|
||||
default:
|
||||
if strong {
|
||||
return cGreenB
|
||||
}
|
||||
return cGreen
|
||||
}
|
||||
}
|
||||
|
||||
func countColor(n int64) *color.Color {
|
||||
if n == 0 {
|
||||
return cDark
|
||||
}
|
||||
return cGrey
|
||||
}
|
||||
|
||||
func pad(s string, w int) string {
|
||||
if n := w - utf8.RuneCountInString(s); n > 0 {
|
||||
return strings.Repeat(" ", n)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Anteilsbalken in Achtelschritten, feste Breite barCells.
|
||||
const barCells = 10
|
||||
|
||||
var eighths = []rune{' ', '▏', '▎', '▍', '▌', '▋', '▊', '▉'}
|
||||
|
||||
func bar(frac float64) string {
|
||||
if frac > 1 {
|
||||
frac = 1
|
||||
}
|
||||
if !(frac > 0) { // fängt auch NaN
|
||||
return strings.Repeat(" ", barCells)
|
||||
}
|
||||
units := int(frac*barCells*8 + 0.5)
|
||||
full, rem := units/8, units%8
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString(strings.Repeat("█", full))
|
||||
used := full
|
||||
if rem > 0 {
|
||||
b.WriteRune(eighths[rem])
|
||||
used++
|
||||
}
|
||||
b.WriteString(strings.Repeat(" ", barCells-used))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func percent(frac float64) string {
|
||||
p := frac * 100
|
||||
if p > 0 && p < 0.5 {
|
||||
return " <1%"
|
||||
}
|
||||
return fmt.Sprintf("%3.0f%%", p)
|
||||
}
|
||||
|
||||
func printDir(w *bufio.Writer, dir string, res result, o opts) {
|
||||
metric := func(e entry) int64 {
|
||||
if o.byCount {
|
||||
return e.n
|
||||
}
|
||||
return e.size
|
||||
}
|
||||
sort.SliceStable(res.entries, func(i, j int) bool {
|
||||
a, b := res.entries[i], res.entries[j]
|
||||
if ka, kb := metric(a), metric(b); ka != kb {
|
||||
return ka < kb
|
||||
}
|
||||
return a.name < b.name // Perl ließ Gleichstände der Hash-Ordnung
|
||||
})
|
||||
|
||||
// -t N: nur die N größten zeigen, der Rest wird zu einer Zeile.
|
||||
rows := res.entries
|
||||
var rest *entry
|
||||
if o.top > 0 && len(rows) > o.top {
|
||||
cut := len(rows) - o.top
|
||||
var agg entry
|
||||
for _, e := range rows[:cut] {
|
||||
agg.n += e.n
|
||||
agg.size += e.size
|
||||
}
|
||||
agg.name = fmt.Sprintf("… %d more", cut)
|
||||
agg.col, agg.agg = cDark, true
|
||||
rest, rows = &agg, rows[cut:]
|
||||
}
|
||||
if o.reverse {
|
||||
for i, j := 0, len(rows)-1; i < j; i, j = i+1, j-1 {
|
||||
rows[i], rows[j] = rows[j], rows[i]
|
||||
}
|
||||
}
|
||||
if rest != nil { // Restzeile bleibt am kleinen Ende
|
||||
if o.reverse {
|
||||
rows = append(rows, *rest)
|
||||
} else {
|
||||
rows = append([]entry{*rest}, rows...)
|
||||
}
|
||||
}
|
||||
|
||||
total := res.totalS
|
||||
if o.byCount {
|
||||
total = res.totalN
|
||||
}
|
||||
|
||||
ml := utf8.RuneCountInString(dir)
|
||||
for _, e := range rows {
|
||||
if n := utf8.RuneCountInString(e.name); n > ml {
|
||||
ml = n
|
||||
}
|
||||
}
|
||||
width := ml + 1
|
||||
|
||||
fmt.Fprintf(w, "%s%s %s %s\n",
|
||||
cDirB.Sprint(dir), pad(dir, width),
|
||||
cWhiteB.Sprintf("%8d", res.totalN),
|
||||
sizeColor(res.totalS, true).Sprintf("%8s", formSize(res.totalS)))
|
||||
|
||||
for _, e := range rows {
|
||||
fmt.Fprintf(w, " %s%s %s %s",
|
||||
e.col.Sprint(e.name), pad(e.name, width),
|
||||
countColor(e.n).Sprintf("%8d", e.n),
|
||||
sizeColor(e.size, false).Sprintf("%8s", formSize(e.size)))
|
||||
|
||||
if o.bar {
|
||||
var frac float64
|
||||
if total > 0 {
|
||||
frac = float64(metric(e)) / float64(total)
|
||||
}
|
||||
// Balken trägt die Farbe der Spalte, deren Anteil er zeigt.
|
||||
col := sizeColor(e.size, false)
|
||||
if o.byCount {
|
||||
col = countColor(e.n)
|
||||
}
|
||||
if e.agg { // Sammelzeile bleibt zurückhaltend
|
||||
col = cDark
|
||||
}
|
||||
fmt.Fprintf(w, " %s %s",
|
||||
col.Sprint(bar(frac)), cGrey.Sprint(percent(frac)))
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------- CLI
|
||||
|
||||
type opts struct {
|
||||
byCount bool // -n
|
||||
onlyDirs bool // -d
|
||||
reverse bool // -r
|
||||
top int // -t N, 0 = alle
|
||||
bar bool
|
||||
}
|
||||
|
||||
func usage(w *os.File) {
|
||||
fmt.Fprintf(w, `dx %s, count files and directories, mwx'2016
|
||||
usage: dx [-n] [-d] [-r] [-t N] [path]
|
||||
-n sort by number of files (default by size)
|
||||
-d count only directories
|
||||
-r reverse order (largest first)
|
||||
-t N show only the N largest, sum up the rest
|
||||
-h this help
|
||||
--bar / --no-bar share bar and percentage (default: when colorized)
|
||||
--color=auto|always|never colorize output (default auto)
|
||||
--version
|
||||
`, version)
|
||||
}
|
||||
|
||||
func die(format string, a ...any) {
|
||||
fmt.Fprintf(os.Stderr, "dx: "+format+"\n", a...)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
func main() {
|
||||
var o opts
|
||||
var barSet bool // sonst folgt der Balken der Farbentscheidung
|
||||
var dir string
|
||||
when := "auto"
|
||||
|
||||
args := os.Args[1:]
|
||||
for i := 0; i < len(args); i++ {
|
||||
a := args[i]
|
||||
switch {
|
||||
case a == "--":
|
||||
if i+1 < len(args) {
|
||||
dir = args[i+1]
|
||||
}
|
||||
i = len(args)
|
||||
case a == "--help":
|
||||
usage(os.Stdout)
|
||||
return
|
||||
case a == "--version":
|
||||
fmt.Printf("dx %s\n", version)
|
||||
return
|
||||
case a == "--number":
|
||||
o.byCount = true
|
||||
case a == "--dirs":
|
||||
o.onlyDirs = true
|
||||
case a == "--reverse":
|
||||
o.reverse = true
|
||||
case a == "--bar":
|
||||
o.bar, barSet = true, true
|
||||
case a == "--no-bar":
|
||||
o.bar, barSet = false, true
|
||||
case a == "--no-color":
|
||||
when = "never"
|
||||
case strings.HasPrefix(a, "--top="):
|
||||
o.top = mustNum(strings.TrimPrefix(a, "--top="))
|
||||
case strings.HasPrefix(a, "--color="):
|
||||
when = strings.TrimPrefix(a, "--color=")
|
||||
case len(a) > 1 && a[0] == '-':
|
||||
flags := a[1:]
|
||||
for j := 0; j < len(flags); j++ { // Bündelung wie Getopt::Std: -dn
|
||||
switch flags[j] {
|
||||
case 'n':
|
||||
o.byCount = true
|
||||
case 'd':
|
||||
o.onlyDirs = true
|
||||
case 'r':
|
||||
o.reverse = true
|
||||
case 'h':
|
||||
usage(os.Stdout)
|
||||
return
|
||||
case 't': // Wert klebt am Flag (-t5) oder folgt (-t 5)
|
||||
val := flags[j+1:]
|
||||
if val == "" {
|
||||
if i+1 >= len(args) {
|
||||
die("option requires an argument -- t")
|
||||
}
|
||||
i++
|
||||
val = args[i]
|
||||
}
|
||||
o.top = mustNum(val)
|
||||
j = len(flags)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "dx: unknown option -- %c\n", flags[j])
|
||||
usage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
default:
|
||||
dir = a
|
||||
}
|
||||
}
|
||||
|
||||
switch when {
|
||||
case "always":
|
||||
color.NoColor = false
|
||||
case "never":
|
||||
color.NoColor = true
|
||||
case "auto": // fatih/color entscheidet per isatty und NO_COLOR
|
||||
default:
|
||||
die("--color: expected auto|always|never, got %q", when)
|
||||
}
|
||||
if !barSet {
|
||||
o.bar = !color.NoColor
|
||||
}
|
||||
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
|
||||
res, err := scan(dir, o.onlyDirs)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "dx: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
w := bufio.NewWriter(os.Stdout)
|
||||
printDir(w, dir, res, o)
|
||||
w.Flush()
|
||||
|
||||
if res.unreadbl > 0 {
|
||||
fmt.Fprintln(os.Stderr, cDark.Sprintf("dx: %d unreadable entries skipped", res.unreadbl))
|
||||
}
|
||||
}
|
||||
|
||||
func mustNum(s string) int {
|
||||
n, err := strconv.Atoi(s)
|
||||
if err != nil || n < 1 {
|
||||
die("-t: expected a positive number, got %q", s)
|
||||
}
|
||||
return n
|
||||
}
|
||||
Reference in New Issue
Block a user