initial commit

This commit is contained in:
Timon Ringwald 2022-06-28 13:11:16 +02:00
parent bfed7a14c0
commit 0055f56291
12 changed files with 957 additions and 0 deletions

121
commands.go Normal file
View File

@ -0,0 +1,121 @@
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func help(args []string) {
fmt.Println()
fmt.Println(Translate("Commands:"))
fmt.Println(Translate("ls: list files"))
fmt.Println(Translate("cd: change directory"))
fmt.Println(Translate("rm: mark files for removal"))
fmt.Println(Translate("urm: unmark files for removal"))
fmt.Println(Translate("exit: delete marked files and exit"))
fmt.Println()
}
func ls(args []string) {
path := strings.Join(args, " ")
if path == "" {
path = "."
}
entries := getEntriesForPath(path)
if len(entries) == 0 {
showError("'%s' could not be found", path)
return
}
for _, entry := range entries {
fmt.Println()
fmt.Println(entry.StringRecursive(1))
}
}
func rm(args []string, removalMark bool) {
path := strings.Join(args, " ")
entries := getEntriesForPath(path)
if len(entries) == 0 {
showError("'%s' could not be found", path)
return
}
for _, entry := range entries {
entry.SetRemovalMark(removalMark)
}
}
func cd(args []string) {
path := "/"
if len(args) > 0 {
path = args[0]
}
entries := getEntriesForPath(path)
if len(entries) == 0 {
showError("'%s' could not be found", path)
return
} else if len(entries) > 1 {
b := new(strings.Builder)
for _, entry := range entries {
path := strings.TrimPrefix(entry.Path(), Current.Path()+"/")
path = strings.TrimPrefix(path, Root.Path())
b.WriteString(path)
b.WriteRune('\n')
}
showError("ambiguous path: %s", path)
fmt.Println(Translate("possible paths:\n%s", b.String()))
return
}
entry := entries[0]
if entry.IsDir() {
Current = entry
fmt.Println()
} else {
showError("'%s' is not a directory", entry.Name())
}
}
func exit() {
fmt.Println()
stats := new(RemovalStats)
Root.RemovalStats(stats)
if len(stats.Paths) == 0 {
s := bufio.NewScanner(os.Stdin)
s.Split(bufio.ScanRunes)
fmt.Print(Translate("exit? [y/N]: "))
if s.Scan() {
text := strings.ToLower(s.Text())
if text == "y" || text == "yes" {
os.Exit(0)
}
}
return
}
fmt.Println(Translate("The following items will be removed:"))
fmt.Println(stats)
s := bufio.NewScanner(os.Stdin)
s.Split(bufio.ScanRunes)
fmt.Print(Translate("Delete files? [y/N/c]: "))
if s.Scan() {
text := strings.ToLower(s.Text())
if text == "y" || text == "yes" {
removeMarkedEntries(Root)
os.Exit(0)
} else if text == "n" || text == "no" {
os.Exit(0)
}
}
}

183
dir_entry.go Normal file
View File

@ -0,0 +1,183 @@
package main
import (
"errors"
"os"
"path/filepath"
"sort"
"strings"
)
type DirectoryEntry struct {
path string
size *uint64
modSize *uint64
entries []Entry
parent Entry
removal bool
noPerm bool
isMount bool
}
var _ Entry = &DirectoryEntry{}
func (e *DirectoryEntry) Name() string {
return filepath.Base(e.path)
}
func (e *DirectoryEntry) Path() string {
return e.path
}
func (e *DirectoryEntry) Size() uint64 {
if e.size == nil {
size := uint64(0)
for _, c := range e.Entries() {
size += c.Size()
}
e.size = &size
e.modSize = new(uint64)
*e.modSize = *e.size
}
return *e.size
}
func (e *DirectoryEntry) Entries() []Entry {
if e.entries == nil {
e.Scan(nil, nil)
}
return e.entries
}
func (e *DirectoryEntry) String() string {
if e.removal {
return ColorRed.Sprintf("%s %s (%s)", icon("folder"), e.Name(), fmtSize(e.SizeModified()))
}
if e.Size() == e.SizeModified() {
return ColorBlue.Sprintf("%s %s (%s)", icon("folder"), e.Name(), fmtSize(e.Size()))
} else {
return ColorBlue.Sprintf("%s %s (%s / %s)", icon("folder"), e.Name(), ColorRed.Sprintf(fmtSize(e.SizeModified())), fmtSize(e.Size()))
}
}
func (e *DirectoryEntry) stringRecursive(depth, maxDepth int, b *strings.Builder) {
if depth > maxDepth {
return
}
b.WriteString(strings.Repeat(" ", depth))
b.WriteString(e.String())
b.WriteRune('\n')
for _, entry := range e.Entries() {
entry.stringRecursive(depth+1, maxDepth, b)
}
}
func (e *DirectoryEntry) StringRecursive(depth int) string {
b := new(strings.Builder)
e.stringRecursive(0, depth, b)
return b.String()
}
func (e *DirectoryEntry) Scan(ch chan<- string, mounts map[string]struct{}) {
dirEntries, err := os.ReadDir(e.Path())
if errors.Is(err, os.ErrPermission) {
e.noPerm = true
e.entries = []Entry{}
return
} else if err != nil {
e.noPerm = true
e.entries = []Entry{}
return
}
e.entries = make([]Entry, 0, len(dirEntries))
for _, dirEntry := range dirEntries {
entry := NewEntryFromDirEntry(e, dirEntry)
if _, isMount := mounts[entry.Path()]; !isMount {
e.entries = append(e.entries, entry)
entry.Scan(ch, mounts)
}
}
if ch != nil {
ch <- e.Path()
}
sort.Slice(e.entries, func(i, j int) bool {
return e.entries[i].SizeModified() < e.entries[j].SizeModified()
})
}
func (e *DirectoryEntry) Parent() Entry {
return e.parent
}
func (e *DirectoryEntry) Entry(path string) Entry {
if path == "." || path == "" {
return e
}
splits := strings.Split(path, "/")
for _, entry := range e.Entries() {
if entry.Name() == splits[0] {
return entry.Entry(strings.Join(splits[1:], "/"))
}
}
return nil
}
func (e *DirectoryEntry) IsDir() bool {
return true
}
func (e *DirectoryEntry) SetRemovalMark(mark bool) {
e.removal = mark
for _, entry := range e.Entries() {
entry.SetRemovalMark(mark)
}
if e.Parent() != nil {
pentries := e.Parent().Entries()
sort.Slice(pentries, func(i, j int) bool {
return pentries[i].SizeModified() < pentries[j].SizeModified()
})
}
}
func (e *DirectoryEntry) RemovalMark() bool {
return e.removal
}
func (e *DirectoryEntry) SizeModified() uint64 {
if e.size == nil {
e.Size()
}
return *e.modSize
}
func (e *DirectoryEntry) modifySize(modifier uint64) {
*e.modSize += modifier
if e.parent != nil {
e.parent.modifySize(modifier)
}
}
func (e *DirectoryEntry) RemovalStats(stats *RemovalStats) {
if e.RemovalMark() {
stats.Dirs++
if e.Parent() != nil && !e.Parent().RemovalMark() {
stats.Paths = append(stats.Paths, e.Path())
}
}
for _, entry := range e.Entries() {
entry.RemovalStats(stats)
}
}

BIN
diskspace Executable file

Binary file not shown.

72
entry.go Normal file
View File

@ -0,0 +1,72 @@
package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
)
type Entry interface {
fmt.Stringer
stringRecursive(depth, maxDepth int, b *strings.Builder)
StringRecursive(depth int) string
Name() string
Path() string
Size() uint64
SizeModified() uint64
Entries() []Entry
Entry(path string) Entry
SetRemovalMark(mark bool)
RemovalMark() bool
RemovalStats(stats *RemovalStats)
modifySize(modifier uint64)
IsDir() bool
Parent() Entry
Scan(ch chan<- string, mounts map[string]struct{})
}
func NewEntryFromDirEntry(parentEntry Entry, dirEntry fs.DirEntry) Entry {
if dirEntry.IsDir() {
return &DirectoryEntry{
path: filepath.Join(parentEntry.Path(), dirEntry.Name()),
parent: parentEntry,
}
} else {
size := uint64(0)
noPerm := false
if fi, err := dirEntry.Info(); err == nil {
size = uint64(fi.Size())
} else {
noPerm = true
}
return &FileEntry{
path: filepath.Join(parentEntry.Path(), dirEntry.Name()),
size: &size,
noPerm: noPerm,
parent: parentEntry,
}
}
}
func NewEntry(path string) (Entry, error) {
path = filepath.Clean(path)
fi, err := os.Lstat(path)
if err != nil {
return nil, err
}
if fi.IsDir() {
return &DirectoryEntry{path: path}, nil
} else {
return &FileEntry{path: path}, nil
}
}

149
file_entry.go Normal file
View File

@ -0,0 +1,149 @@
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
)
type FileEntry struct {
path string
size *uint64
modSize *uint64
parent Entry
removal bool
noPerm bool
isMount bool
isExec bool
}
var _ Entry = &FileEntry{}
func (e *FileEntry) Name() string {
return filepath.Base(e.path)
}
func (e *FileEntry) Path() string {
return e.path
}
func (e *FileEntry) Size() uint64 {
if e.size == nil {
e.Scan(nil, nil)
}
return *e.size
}
func (e *FileEntry) Entries() []Entry {
return []Entry{}
}
func (e *FileEntry) String() string {
var ic string
if e.isExec {
ic = icon("app")
} else {
ic = icon(filepath.Ext(e.Path()))
}
if e.removal {
return ColorRed.Sprintf(
"%s %s (%s / %s)",
ic, e.Name(), fmtSize(e.SizeModified()), fmtSize(e.Size()),
)
}
if e.isExec {
return ColorGreen.Sprintf("%s %s (%s)", ic, e.Name(), fmtSize(e.Size()))
}
return fmt.Sprintf("%s %s (%s)", ic, e.Name(), fmtSize(e.Size()))
}
func (e *FileEntry) stringRecursive(depth, maxDepth int, b *strings.Builder) {
if depth > maxDepth {
return
}
b.WriteString(strings.Repeat(" ", depth))
b.WriteString(e.String())
b.WriteRune('\n')
}
func (e *FileEntry) StringRecursive(depth int) string {
b := new(strings.Builder)
e.stringRecursive(0, depth, b)
return b.String()
}
func (e *FileEntry) Scan(ch chan<- string, mounts map[string]struct{}) {
if ch != nil {
ch <- e.Path()
}
s := uint64(0)
if fi, err := os.Lstat(e.Path()); err == nil {
s = uint64(fi.Size())
e.isExec = fi.Mode()&0x41 > 0 // 0x41 == 0b001000001 == rwXrwxrwX
} else if errors.Is(err, os.ErrPermission) {
e.noPerm = true
}
e.size = &s
e.modSize = new(uint64)
*e.modSize = *e.size
}
func (e *FileEntry) Parent() Entry {
return e.parent
}
func (e *FileEntry) Entry(path string) Entry {
return e
}
func (e *FileEntry) IsDir() bool {
return false
}
func (e *FileEntry) SetRemovalMark(mark bool) {
e.removal = mark
if mark {
e.modifySize(-*e.size)
} else {
e.modifySize(*e.size)
}
}
func (e *FileEntry) RemovalMark() bool {
return e.removal
}
func (e *FileEntry) SizeModified() uint64 {
if e.size == nil {
e.Size()
}
return *e.modSize
}
func (e *FileEntry) modifySize(modifier uint64) {
*e.modSize += modifier
if e.parent != nil {
e.parent.modifySize(modifier)
}
}
func (e *FileEntry) RemovalStats(stats *RemovalStats) {
if e.RemovalMark() {
stats.Files++
stats.Bytes += *e.size
if e.Parent() != nil && !e.Parent().RemovalMark() {
stats.Paths = append(stats.Paths, e.Path())
}
}
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module git.tordarus.net/Tordarus/diskspace
go 1.18
require (
github.com/fatih/color v1.13.0
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
)
require (
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
)

16
go.sum Normal file
View File

@ -0,0 +1,16 @@
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=

185
main.go Normal file
View File

@ -0,0 +1,185 @@
package main
import (
"bufio"
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"github.com/fatih/color"
)
var (
Current Entry
Root Entry
ColorRed = color.New(color.FgRed)
ColorBlue = color.New(color.FgBlue)
ColorGreen = color.New(color.FgGreen)
IconTheme = flag.String("i", "unicode", "icon theme (currently supported: default, unicode, nerd)")
)
func main() {
flag.Parse()
rootPath := flag.Arg(0)
mounts, err := Mounts()
if err != nil {
fmt.Fprintln(os.Stderr, "Mount points could not be read! All mount points will be calculated as well")
}
if abs, err := filepath.Abs(rootPath); err == nil {
rootPath = abs
} else {
panic(err)
}
root, err := NewEntry(rootPath)
if err != nil {
panic(err)
}
start := time.Now()
ScanEntryLive(root, mounts)
fmt.Println(Translate("Scanning took %s", time.Since(start)))
fmt.Println()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
for range c {
fmt.Println()
showError("Use command 'exit' for exiting properly")
fmt.Print("> ")
}
}()
Root = root
Current = root
s := bufio.NewScanner(os.Stdin)
showCurrentDir := true
for {
if showCurrentDir {
fmt.Println(Current.StringRecursive(1))
}
fmt.Print("> ")
s.Scan()
input := s.Text()
showCurrentDir = handleInput(input)
}
}
func handleInput(input string) bool {
cmd := strings.Split(strings.TrimSpace(input), " ")
switch cmd[0] {
case "ls":
ls(cmd[1:])
return false
case "cd":
cd(cmd[1:])
case "rm":
rm(cmd[1:], true)
case "urm":
rm(cmd[1:], false)
case "exit":
exit()
case "help":
help(cmd[1:])
}
return true
}
func showError(str string, args ...interface{}) {
fmt.Println()
ColorRed.Println(Translate(str, args...))
fmt.Println()
}
func entryByPath(path string) Entry {
if !strings.HasPrefix(path, Root.Path()) {
return nil
}
path = strings.TrimPrefix(path, Root.Path())
if path == "/" || path == "" {
return Root
} else {
path = strings.TrimPrefix(path, "/")
}
return Root.Entry(path)
}
func getEntriesForPath(path string) []Entry {
path = filepath.Clean(path)
if filepath.IsAbs(path) {
path = filepath.Join(Root.Path(), path)
} else {
path = filepath.Join(Current.Path(), path)
}
paths, err := filepath.Glob(path)
if err != nil {
panic(err)
}
entries := make([]Entry, 0, len(paths))
for _, path := range paths {
if entry := entryByPath(path); entry != nil {
entries = append(entries, entry)
}
}
return entries
}
func ScanEntryLive(entry Entry, mounts map[string]struct{}) {
ch := make(chan string, 10)
wg := new(sync.WaitGroup)
// TODO cap string size to terminal width
//width, _, _ := terminal.GetSize(int(os.Stdin.Fd()))
wg.Add(1)
go func(ch <-chan string) {
defer wg.Done()
for path := range ch {
os.Stdout.Write([]byte{'\r'})
clearEOL()
fmt.Print(Translate("Scanning ") + path)
}
os.Stdout.Write([]byte{'\r'})
clearEOL()
}(ch)
entry.Scan(ch, mounts)
close(ch)
wg.Wait()
}
func removeMarkedEntries(entry Entry) {
if entry.RemovalMark() {
err := os.RemoveAll(entry.Path())
if err != nil {
ColorRed.Println(Translate("Could not delete '%s': %s", entry.Path(), err.Error()))
}
} else {
for _, entry := range entry.Entries() {
removeMarkedEntries(entry)
}
}
}

32
mount.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"bufio"
"bytes"
"os/exec"
"regexp"
)
var (
mountLineRegex = regexp.MustCompile(`^.*? on (.*?) type (.*?) \((?:.*?[,)])*$`)
)
func Mounts() (map[string]struct{}, error) {
mounts := map[string]struct{}{}
out, err := exec.Command("mount").CombinedOutput()
if err != nil {
return nil, err
}
s := bufio.NewScanner(bytes.NewReader(out))
for s.Scan() {
matches := mountLineRegex.FindAllStringSubmatch(s.Text(), -1)
if len(matches) == 0 {
continue
}
mounts[matches[0][1]] = struct{}{}
}
return mounts, nil
}

34
stats.go Normal file
View File

@ -0,0 +1,34 @@
package main
import (
"strings"
)
type RemovalStats struct {
Files int
Dirs int
Bytes uint64
Paths []string
}
func (s *RemovalStats) String() string {
b := new(strings.Builder)
for _, path := range s.Paths {
b.WriteString(path)
b.WriteRune('\n')
}
b.WriteRune('\n')
b.WriteString(Translate("Files: %d", s.Files))
b.WriteRune('\n')
b.WriteString(Translate("Directories: %d", s.Dirs))
b.WriteRune('\n')
b.WriteString(Translate("Total size: %s", fmtSize(s.Bytes)))
b.WriteRune('\n')
return b.String()
}

73
translate.go Normal file
View File

@ -0,0 +1,73 @@
package main
import (
"fmt"
"os"
"strings"
)
var Languages = map[string]map[string]string{
"de": map[string]string{
"Scanning ": "Verarbeite ",
"Scanning took %s": "Verarbeitung dauerte %s",
"'%s' is not a directory": "'%s' ist kein Verzeichnis",
"The following items will be removed:": "Die folgenden Elemente werden entfernt:",
"Files: %d": "Dateien: %d",
"Directories: %d": "Ordner: %d",
"Total size: %s": "Gesamtgröße: %s",
"'%s' could not be found": "'%s' konnte nicht gefunden werden",
"Could not delete '%s': %s": "'%s' konnte nicht gelöscht werden: %s",
"Delete files? [y/N/c]: ": "Dateien löschen? [y/N/c]: ",
"ambiguous path: %s": "Nicht eindeutiger Pfad: %s",
"possible paths:\n%s": "Mögliche Pfade:\n%s",
"Commands:": "Befehle:",
"ls: list files": "ls: Dateien auflisten",
"cd: change directory": "cd: Verzeichnis wechseln",
"rm: mark files for removal": "rm: Markiere Dateien zur Löschung",
"urm: unmark files for removal": "urm: Entmarkiere Dateien zur Löschung",
"exit: delete marked files and exit": "exit: Lösche markierte Dateien und beende Programm",
"Use command 'exit' for exiting properly": "Nutze den Befehl 'exit', um das Programm ordnungsgemäß zu beenden",
"exit? [y/N]: ": "Beenden? [y/N]: ",
},
"ja": map[string]string{
"Scanning ": "処理 ",
"Scanning took %s": "処理が %s かかりました",
"'%s' is not a directory": "「%s」はディレクトリではありません",
"The following items will be removed:": "このファイルは削除します:",
"Files: %d": "ファイル: %d",
"Directories: %d": "デイレクトリ: %d",
"Total size: %s": "全額: %s",
"'%s' could not be found": "「%s」が見つかりませんでした",
"Could not delete '%s': %s": "「%s」が削除できませんでした: %s",
"Delete files? [y/N/c]: ": "削除してほしいですか? [y/N/c]: ",
"ambiguous path: %s": "あいまいなパス: %s",
"possible paths:\n%s": "可能なパス:\n%s",
"Commands:": "コマンド:",
"ls: list files": "ls: ファイルを並べます",
"cd: change directory": "cd: ディレクトリを変更",
"rm: mark files for removal": "rm: ファイルに削除のマークを付けます",
"urm: unmark files for removal": "urm: ファイルに削除のマークを除きます",
"exit: delete marked files and exit": "exit: 削除のマークされたファイルを削除して終了",
"Use command 'exit' for exiting properly": "「exit」コマンドを使って終了します",
"exit? [y/N]: ": "終了してほしいですか? [y/N]: ",
},
}
var langmap map[string]string
func Translate(str string, args ...interface{}) string {
if langmap == nil {
langVar, _ := os.LookupEnv("LANG")
langmap = map[string]string{}
for lname, lmap := range Languages {
if strings.HasPrefix(langVar, lname) {
langmap = lmap
}
}
}
if translatedText, ok := langmap[str]; ok {
return fmt.Sprintf(translatedText, args...)
}
return fmt.Sprintf(str, args...)
}

77
utils.go Normal file
View File

@ -0,0 +1,77 @@
package main
import (
"math"
"os"
"strconv"
)
func fmtSize(size uint64) string {
sf := float64(size)
if size/1000000000000 >= 1 {
return strconv.FormatFloat(round(sf/1000000000000), 'G', -1, 64) + " TB"
} else if size/1000000000 >= 1 {
return strconv.FormatFloat(round(sf/1000000000), 'G', -1, 64) + " GB"
} else if size/1000000 >= 1 {
return strconv.FormatFloat(round(sf/1000000), 'G', -1, 64) + " MB"
} else if size/1000 >= 1 {
return strconv.FormatFloat(round(sf/1000), 'G', -1, 64) + " KB"
} else {
return strconv.FormatFloat(round(sf), 'G', -1, 64) + " B"
}
}
func round(value float64) float64 {
return math.Round(value*100) / 100
}
func icon(ext string) string {
switch *IconTheme {
default:
fallthrough
case "unicode":
switch ext {
case "folder":
return "📂"
default:
return "📄"
}
case "nerd":
switch ext {
case "app":
return "ﬓ"
case "folder":
return ""
case ".doc", ".docx", ".odt", ".rtx", ".pdf", ".tex":
return ""
case ".xls", ".xlsx", ".xlsm", ".dex", ".ods", ".csv":
return ""
case ".jpg", ".jpeg", ".png", ".gif", ".ppm", ".svg", ".tiff", ".bmp", ".webp", ".blend", ".obj":
return ""
case ".go", ".c", ".cpp", ".rs", ".h", ".java", ".asm", ".js", ".html", ".css", ".ts", ".sql":
return ""
case ".mp3", ".flac", ".m4a", ".opus", ".wav", ".wma":
return ""
case ".mp4", ".webm", ".mkv", ".flv", ".ogg", ".ogv", ".gifv", ".avi", ".mov", ".wmv", ".mp2", ".mpg", ".mpv", ".mpe", ".mpeg", ".m2v", ".3gp":
return ""
case ".txt":
return ""
case ".zip", ".rar", ".7z", ".tar", ".bz2", ".gz", ".xz", ".tgz", ".tbz2", ".txz":
return ""
case ".iso":
return "﫭"
case ".apk":
return ""
case ".jar":
return ""
case ".json", ".yaml", ".yml":
return ""
default:
return ""
}
}
}
func clearEOL() {
os.Stdout.Write([]byte{0x1b, 0x5b, 0x4b})
}