Skip to content

Commit

Permalink
Added grep (#28)
Browse files Browse the repository at this point in the history
Fixes #27.

Basic features of grep including line numbers, stdin, files, case
insensitivity, and inversion are supported.

Additionally, I've refactored base to support the common pattern of
running for multiple files or stdin if none are specified. This may
cause a refactor in `wc`, and `cat` -- possibly others.
  • Loading branch information
josephlewis42 authored Dec 23, 2023
1 parent e0bfcd4 commit 5f08a52
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 3 deletions.
48 changes: 45 additions & 3 deletions commands/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (

"github.com/fatih/color"
fcolor "github.com/fatih/color"
getopt "github.com/pborman/getopt/v2"
"github.com/josephlewis42/honeyssh/core/vos"
getopt "github.com/pborman/getopt/v2"
)

// Always generate golden files on generate, a diff indicates a problem and
Expand Down Expand Up @@ -200,7 +200,7 @@ func (s *SimpleCommand) RunEachArg(virtOS vos.VOS, callback func(string) error)

for _, arg := range s.Flags().Args() {
if err := callback(arg); err != nil {
fmt.Fprintf(virtOS.Stderr(), "%s: %s\n", s.Flags().Program(), err.Error())
s.LogProgramError(virtOS, err)
anyErrored = true
}
}
Expand All @@ -216,13 +216,55 @@ func (s *SimpleCommand) RunEachArg(virtOS vos.VOS, callback func(string) error)
func (s *SimpleCommand) RunE(virtOS vos.VOS, callback func() error) int {
return s.Run(virtOS, func() int {
if err := callback(); err != nil {
fmt.Fprintf(virtOS.Stderr(), "%s: %s\n", s.Flags().Program(), err.Error())
s.LogProgramError(virtOS, err)
return 1
}
return 0
})
}

// RunEachFileOrStdin runs the callback for every supplied arg, or stdin
func (s *SimpleCommand) RunEachFileOrStdin(virtOS vos.VOS, files []string, callback func(name string, fd io.Reader) error) int {
return s.Run(virtOS, func() int {
anyErrored := false

openCallback := func(name string) error {
fd, err := virtOS.Open(name)
if err != nil {
return err
}

defer fd.Close()
return callback(name, fd)
}

for _, arg := range files {
if err := openCallback(arg); err != nil {
s.LogProgramError(virtOS, err)
anyErrored = true
}

}

if len(files) == 0 {
if err := callback("-", virtOS.Stdin()); err != nil {
s.LogProgramError(virtOS, err)
anyErrored = true
}
}

if anyErrored {
return 1
}
return 0
})
}

// Log a program error to stderr in the form "program name: error message"
func (s *SimpleCommand) LogProgramError(virtOS vos.VOS, err error) {
fmt.Fprintf(virtOS.Stderr(), "%s: %s\n", s.Flags().Program(), err.Error())
}

const (
colorAlways = "always"
colorAuto = "auto"
Expand Down
80 changes: 80 additions & 0 deletions commands/grep.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package commands

import (
"bufio"
"errors"
"fmt"
"io"
"regexp"

"github.com/josephlewis42/honeyssh/core/vos"
)

// Grep implements the POSIX grep command.
//
// https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/
func Grep(virtOS vos.VOS) int {
cmd := &SimpleCommand{
Use: "grep [-iv] PATTERN [FILE]...",
Short: "Search files for text matching a pattern.",
}

invert := cmd.Flags().Bool('v', "Select lines not matching any of the specified patterns.")
ignoreCase := cmd.Flags().Bool('i', "Perform pattern matching in searches without regard to case.")
showLineNumbers := cmd.Flags().Bool('n', "Show line numbers.")

return cmd.Run(virtOS, func() int {
args := cmd.Flags().Args()
if len(args) == 0 {
cmd.LogProgramError(virtOS, errors.New("missing argument PATTERN"))
return 1
}

// NOTE: Officially, the PATTERN argument supports multiple patterns delimited by newlines.
// It's a very rare case so we'll ignore it here.
pattern := args[0]
if *ignoreCase {
pattern = "(?i)" + pattern
}
regex, err := regexp.Compile(pattern)
if err != nil {
cmd.LogProgramError(virtOS, err)
return 2
}

files := args[1:]
showFileName := len(files) > 1
return cmd.RunEachFileOrStdin(virtOS, files, func(name string, fd io.Reader) error {
w := virtOS.Stdout()

scanner := bufio.NewScanner(fd)
lineNo := 1
for scanner.Scan() {
line := scanner.Bytes()
lineMatches := regex.Match(line)

// Write output
if (lineMatches && !*invert) || (!lineMatches && *invert) {
if showFileName {
fmt.Fprintf(w, "%s:", name)
}

if *showLineNumbers {
fmt.Fprintf(w, "%d:", lineNo)
}

fmt.Fprintf(w, "%s\n", line)
}
lineNo++
}

return nil
})
})
}

var _ vos.ProcessFunc = Grep

func init() {
addBinCmd("grep", Grep)
}

0 comments on commit 5f08a52

Please sign in to comment.