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