Skip to content
This repository has been archived by the owner on Feb 16, 2023. It is now read-only.

Commit

Permalink
rm: add rm implementation
Browse files Browse the repository at this point in the history
Updates: #114
  • Loading branch information
ericlagergren committed Jan 17, 2018
1 parent 3e72a7c commit 70e5762
Show file tree
Hide file tree
Showing 6 changed files with 453 additions and 83 deletions.
8 changes: 5 additions & 3 deletions coreutils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package coreutils

import (
"context"
"errors"
"io"
"sync"
Expand All @@ -18,17 +19,18 @@ func Register(name string, r Runnable) {
cmds[name] = r
}

type Runnable func(ctx Ctx, args ...string) error
type Runnable func(ctx Context, args ...string) error

type Ctx struct {
type Context struct {
context.Context
Dir string
GetEnv func(string) string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
}

func Run(ctx Ctx, name string, args ...string) error {
func Run(ctx Context, name string, args ...string) error {
cmdsMu.Lock()
fn := cmds[name]
cmdsMu.Unlock()
Expand Down
189 changes: 189 additions & 0 deletions rm/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package rm

import (
"errors"
"fmt"
"io"

coreutils "github.com/ericlagergren/go-coreutils"
flag "github.com/spf13/pflag"
)

func init() {
coreutils.Register("rm", run)
}

// Sentinal flags for default values or flags with single-character options and
// without multi-character options. (e.g., if we want -i but not --i.)
const (
uniNonChar = 0xFDD0
interDefault = string(uniNonChar + 1)
bad1 = string(uniNonChar + 2)
bad2 = string(uniNonChar + 3)
bad3 = string(uniNonChar + 4)
)

func newCommand() *cmd {
var c cmd
c.f.BoolVarP(&c.force, "force", "f", false, "ignore non-existent files and arguments; never prompt prior to removal")
c.f.BoolVarP(&c.moreInter, bad1, "i", false, "prompt before each removal")
c.f.BoolVarP(&c.lessInter, bad2, "I", false, "prompt (once) prior to removing more than three files or when removing recursively")
c.f.StringVar(&c.interactive, "interactive", interDefault, "prompt: 'never', 'once' (-i), 'always' (-I)")
c.f.BoolVar(&c.oneFileSystem, "one-file-system", false, "when recursing, skip directories that are on a different filesystem")
c.f.BoolVar(&c.preserveRoot, "preserve-root", true, "do not remove '/'")
c.f.BoolVar(&c.noPreserveRoot, "no-preserve-root", false, "do not special-case '/'")
c.f.BoolVarP(&c.recursive, "recursive", "r", false, "remove directories and their contents recursively")
c.f.BoolVarP(&c.recursive, bad3, "R", false, "remove directories and their contents recursively")
c.f.BoolVarP(&c.rmdir, "dir", "d", false, "remove empty directories")
c.f.BoolVarP(&c.verbose, "verbose", "v", false, "explain what's occurring")
c.f.BoolVar(&c.version, "version", false, "print version information and exit")
return &c
}

type cmd struct {
f flag.FlagSet
force bool
moreInter, lessInter bool
interactive string
preserveRoot bool
noPreserveRoot bool
oneFileSystem bool
recursive bool
rmdir bool
verbose bool
version bool
}

func run(ctx coreutils.Context, args ...string) error {
c := newCommand()
if err := c.f.Parse(args); err != nil {
return err
}

if c.version {
fmt.Fprintf(ctx.Stdout, "rm (go-coreutils) 1.0")
return nil
}

var opts RemoveOption
if c.noPreserveRoot && !c.preserveRoot {
opts |= NoPreserveRoot
}
if c.force {
opts |= Force
}
if c.recursive {
opts |= Recursive
}
if c.rmdir {
opts |= RemoveEmpty
}
if c.oneFileSystem {
opts |= OneFileSystem
}
if c.verbose {
opts |= Verbose
}
switch c.interactive {
case interDefault:
if c.moreInter {
opts |= PromptAlways
c.lessInter = false
}
case "never", "no", "none":
opts &= PromptAlways
case "once":
c.lessInter = true
opts &= IgnoreMissing
case "always", "yes", "":
opts |= PromptAlways
opts &= IgnoreMissing
default:
return errors.New("unknown interactive option: " + c.interactive)
}

if c.lessInter && (opts&Recursive != 0 || c.f.NArg() >= 3) {
n := c.f.NArg()
arg := "arguments"
adj := ""
if opts&Recursive != 0 {
adj = " recursively "
if n == 1 {
arg = "argument"
}
}
fmt.Fprintf(ctx.Stderr, "rm: remove %d %s%s? ", n, arg, adj)
switch yes, err := getYesNo(ctx.Stdin); {
case err != nil:
return err
case !yes:
return nil
}
}

r := NewRemover(opts)

if r.opts&PromptAlways != 0 {
r.Prompt = func(name string, opts PromptOption) bool {

wp := " "
if opts&WriteProtected != 0 {
wp = " write-protected "
}

msg := "rm: remove%s%s %q? "
typ := "file"
if opts&(Descend|Directory) != 0 {
typ = "directory"
if opts&Descend != 0 {
msg = "rm: descend into%s%s %q? "
}
}

fmt.Fprintf(ctx.Stderr, msg, wp, typ, name)
yes, err := getYesNo(ctx.Stdin)
return yes && err == nil
}
}

if r.Log != nil {
defer close(r.Log)
go func() {
for msg := range r.Log {
fmt.Fprintln(ctx.Stdout, msg)
}
}()
}

var nerrs int
for _, name := range c.f.Args() {
switch err := r.Remove(name); err.(type) {
case nil:
// OK
case rmError:
fmt.Fprintf(ctx.Stderr, "rm: %v\n", err)
default:
fmt.Fprintf(ctx.Stderr, "rm: %v\n", err)
return err
}
}
if nerrs > 0 {
return errNonFatal
}
return nil
}

var errNonFatal = errors.New("at least one non-fatal error occurred")

func getYesNo(r io.Reader) (yes bool, err error) {
var resp string
fmt.Fscanln(r, &resp)
switch resp {
case "yes", "Y", "y":
return true, nil
case "no", "N", "n":
return false, nil
default:
return false, errors.New("unknown response (must be 'yes' or 'no')")
}
}
25 changes: 25 additions & 0 deletions rm/internal/sys/sys_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// +build !windows

package sys

import (
"os"
"syscall"
)

var root *syscall.Stat_t

func init() {
if info, err := os.Lstat("/"); err == nil {
root = info.Sys().(*syscall.Stat_t)
}
}

func IsRoot(info os.FileInfo) bool {
stat := info.Sys().(*syscall.Stat_t)
return root.Ino == stat.Ino && root.Dev == stat.Dev
}

func DiffFS(orig, test os.FileInfo) bool {
return orig.Sys().(*syscall.Stat_t).Dev != test.Sys().(*syscall.Stat_t).Dev
}
8 changes: 8 additions & 0 deletions rm/internal/sys/sys_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// +build windows

package sys

import "os"

func IsRoot(_ os.FileInfo) bool { return false }
func DiffFS(_, _ os.FileInfo) bool { return false }
Loading

0 comments on commit 70e5762

Please sign in to comment.