Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ func FlagDefinition(cmd *cobra.Command, flags *module.Flags) {
cmd.PersistentFlags().BoolVarP(&flags.ShowHidden, "all", "a", false, "List all files and directories")
cmd.PersistentFlags().BoolVarP(&flags.ShowTreeView, "tree", "t", false, "Tree view of the directory")
cmd.PersistentFlags().BoolVarP(&flags.HideIcon, "icon", "i", false, "Disable icons (Enabled by default)")
cmd.PersistentFlags().BoolVarP(&flags.ShowReadableSize, "readable", "r", false, "Show human-readable size for each file and directory")
}
20 changes: 20 additions & 0 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,26 @@ func TestRootCmd_ValidPathWithFlags(t *testing.T) {
}
}

func TestRootCmd_ReadableFlag(t *testing.T) {
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte("package main"), 0o644); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}

output := captureStdout(t, func() {
cmd := newRootCmd()
cmd.SetArgs([]string{"-r", "--icon", dir})

if err := cmd.Execute(); err != nil {
t.Fatalf("Execute() error = %v, want nil", err)
}
})

if !strings.Contains(output, "main.go (12 B)") {
t.Fatalf("Execute() output = %q, want readable file size", output)
}
}

func captureStdout(t *testing.T, run func()) string {
t.Helper()

Expand Down
7 changes: 4 additions & 3 deletions module/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package module

// Flags struct - represents flags for the application.
type Flags struct {
ShowHidden bool
ShowTreeView bool
HideIcon bool
ShowHidden bool
ShowTreeView bool
HideIcon bool
ShowReadableSize bool
}

// Options struct - Contains configuration options for directory listing.
Expand Down
31 changes: 18 additions & 13 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,7 @@ func listDirectory(options module.Options, output io.Writer) (fileCount, dirCoun
fileCount++
}

var formatted string
if !options.Flags.HideIcon {
formatted = printWithIconAndPrefix("", file)
} else {
formatted = printFilesAndFolderWithoutIcons("", file)
}
formatted := formatFileWithOptions("", file, options.Flags)

entries = append(entries, formatted)

Expand Down Expand Up @@ -152,7 +147,7 @@ func printFilesAndDirectoriesTreeFormat(files []os.FileInfo, options module.Opti
isLast := i == lastVisibleIndex
prefix, childIndent := calculateIndent(indent, isLast)

if err := printFileWithPrefix(output, prefix, file, options.Flags.HideIcon); err != nil {
if err := printFileWithPrefix(output, prefix, file, options.Flags); err != nil {
return 0, 0, err
}

Expand Down Expand Up @@ -185,14 +180,24 @@ func calculateIndent(indent string, isLast bool) (prefix, childIndent string) {
}

// printFileWithPrefix prints the file with the given prefix and icon settings
func printFileWithPrefix(output io.Writer, prefix string, file os.FileInfo, hideIcon bool) error {
if hideIcon {
_, err := fmt.Fprintln(output, printFilesAndFolderWithoutIcons(prefix, file))
return err
func printFileWithPrefix(output io.Writer, prefix string, file os.FileInfo, flags module.Flags) error {
_, err := fmt.Fprintln(output, formatFileWithOptions(prefix, file, flags))
return err
}

func formatFileWithOptions(prefix string, file os.FileInfo, flags module.Flags) string {
var formatted string
if flags.HideIcon {
formatted = printFilesAndFolderWithoutIcons(prefix, file)
} else {
formatted = printWithIconAndPrefix(prefix, file)
}

_, err := fmt.Fprintln(output, printWithIconAndPrefix(prefix, file))
return err
if flags.ShowReadableSize {
formatted = appendReadableSize(formatted, file.Size())
}

return formatted
}

// processDirectory recursively processes a subdirectory
Expand Down
39 changes: 34 additions & 5 deletions service/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ func TestDispatcher_ListDirectory(t *testing.T) {
flags: module.Flags{HideIcon: true, ShowHidden: true},
wantContains: []string{".", ".hidden", "alpha.go", "subpkg", "1 directories, 2 files"},
},
{
name: "shows readable file and directory sizes",
flags: module.Flags{HideIcon: true, ShowReadableSize: true},
wantContains: []string{"alpha.go (12 B)", "subpkg (", "1 directories, 1 files"},
wantMissing: []string{".hidden"},
},
}

for _, tt := range tests {
Expand All @@ -55,7 +61,7 @@ func TestDispatcher_ListDirectory(t *testing.T) {
t.Fatalf("dispatcher() error = %v, want nil", err)
}

got := output.String()
got := stripANSI(output.String())
for _, want := range tt.wantContains {
if !strings.Contains(got, want) {
t.Fatalf("dispatcher() output = %q, want to contain %q", got, want)
Expand All @@ -80,22 +86,45 @@ func TestDispatcher_TreeDirectory(t *testing.T) {
err := dispatcher(module.Options{
Directory: dir,
Flags: module.Flags{
HideIcon: true,
ShowTreeView: true,
HideIcon: true,
ShowTreeView: true,
ShowReadableSize: true,
},
}, &output)
if err != nil {
t.Fatalf("dispatcher() error = %v, want nil", err)
}

got := output.String()
for _, want := range []string{"└── ", "subpkg", "nested.go", "1 directories, 2 files"} {
got := stripANSI(output.String())
for _, want := range []string{"└── ", "subpkg (", "nested.go (14 B)", "1 directories, 2 files"} {
if !strings.Contains(got, want) {
t.Fatalf("dispatcher() output = %q, want to contain %q", got, want)
}
}
}

func TestHumanReadableSize(t *testing.T) {
tests := []struct {
name string
size int64
want string
}{
{name: "zero bytes", size: 0, want: "0 B"},
{name: "bytes", size: 42, want: "42 B"},
{name: "kilobytes", size: 2048, want: "2.0 KB"},
{name: "megabytes", size: 1536 * 1024, want: "1.5 MB"},
{name: "negative size", size: -1, want: "0 B"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := humanReadableSize(tt.size); got != tt.want {
t.Fatalf("humanReadableSize(%d) = %q, want %q", tt.size, got, tt.want)
}
})
}
}

func TestReadDirectory_FilePath(t *testing.T) {
path := filepath.Join(t.TempDir(), "regular.txt")
mustWriteFile(t, path, "content")
Expand Down
23 changes: 23 additions & 0 deletions service/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,29 @@ func printFilesAndFolderWithoutIcons(prefix string, file os.FileInfo) string {
return format
}

func appendReadableSize(formatted string, size int64) string {
return fmt.Sprintf("%s (%s)", formatted, humanReadableSize(size))
}

func humanReadableSize(size int64) string {
if size < 0 {
size = 0
}
if size < 1024 {
return fmt.Sprintf("%d B", size)
}

units := []string{"KB", "MB", "GB", "TB", "PB"}
value := float64(size)
unitIndex := -1
for value >= 1024 && unitIndex < len(units)-1 {
value /= 1024
unitIndex++
}

return fmt.Sprintf("%.1f %s", value, units[unitIndex])
}

// sortSlice func - sorts a slice of os.FileInfo objects alphabetically by file name.
// It modifies the original slice in place.
func sortSlice(files []os.FileInfo) {
Expand Down
Loading