007 Project 7: Build an ls Clone

007 Build an ls Clone

This project builds a practical ls-style CLI with flags for hidden files, long format, and sorting.

path -> read entries -> filter -> sort -> format -> print

Full main.go

package main

import (
    "flag"
    "fmt"
    "io/fs"
    "os"
    "path/filepath"
    "sort"
    "strings"
    "time"
)

type item struct {
    Name string
    Info fs.FileInfo
}

func main() {
    showAll := flag.Bool("a", false, "show hidden files")
    longFmt := flag.Bool("l", false, "long listing format")
    sortBy := flag.String("sort", "name", "name|size|time")
    flag.Parse()

    path := "."
    if flag.NArg() > 0 {
        path = flag.Arg(0)
    }

    entries, err := os.ReadDir(path)
    if err != nil {
        fmt.Fprintf(os.Stderr, "read dir failed: %v\n", err)
        os.Exit(1)
    }

    var items []item
    for _, e := range entries {
        name := e.Name()
        if !*showAll && strings.HasPrefix(name, ".") {
            continue
        }
        info, err := e.Info()
        if err != nil {
            continue
        }
        items = append(items, item{Name: name, Info: info})
    }

    sort.Slice(items, func(i, j int) bool {
        switch *sortBy {
        case "size":
            return items[i].Info.Size() < items[j].Info.Size()
        case "time":
            return items[i].Info.ModTime().Before(items[j].Info.ModTime())
        default:
            return strings.ToLower(items[i].Name) < strings.ToLower(items[j].Name)
        }
    })

    for _, it := range items {
        if *longFmt {
            mode := it.Info.Mode().String()
            sz := it.Info.Size()
            mod := it.Info.ModTime().Format(time.DateTime)
            name := it.Name
            if it.Info.IsDir() {
                name += string(filepath.Separator)
            }
            fmt.Printf("%s %10d %s %s\n", mode, sz, mod, name)
        } else {
            fmt.Println(it.Name)
        }
    }
}

Run

go run .
go run . -a -l -sort size /var/log

Step-by-Step Explanation

  1. Parse command-line flags and validate inputs early.
  2. Keep the core operation in a small, testable function.
  3. Process data as a stream when possible to reduce memory use.
  4. Print stable output and meaningful exit codes.
  5. Add one extension feature and test edge cases.

Code Anatomy

  • main handles flags, orchestration, and errors.
  • Worker/helper functions hold business logic.
  • Output section should be deterministic for scripting and CI usage.

Learning Goals

  • Write composable Unix-style Go tools.
  • Improve error messages and operator experience.
  • Practice iterative improvement over one clear baseline.