initial commit [141.14.140.180,mike]

This commit is contained in:
2026-08-07 14:41:01 +02:00
commit 54c8494241
7 changed files with 718 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
.DS_Store
.AppleDouble
.LSOverride
._*
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
Executable
BIN
View File
Binary file not shown.
Executable
+95
View File
@@ -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
+93
View File
@@ -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
}
+11
View File
@@ -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
)
+9
View File
@@ -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=
+501
View File
@@ -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
}