parent
a6d42ce5ca
commit
7d6c06b251
7 changed files with 273 additions and 135 deletions
@ -0,0 +1,199 @@ |
||||
package coreutils |
||||
|
||||
import ( |
||||
"bufio" |
||||
"crypto/md5" |
||||
"errors" |
||||
"fmt" |
||||
"hash" |
||||
"io" |
||||
"os" |
||||
"regexp" |
||||
|
||||
"github.com/alexflint/go-arg" |
||||
) |
||||
|
||||
var MD5Regex = regexp.MustCompile("^(?P<hash>[0-9a-f]{32}) (?P<filename>.*)$") |
||||
|
||||
func MD5Sum(r io.Reader) (hash.Hash, error) { |
||||
h := md5.New() |
||||
if _, err := io.Copy(h, r); err != nil { |
||||
return nil, err |
||||
} |
||||
return h, nil |
||||
} |
||||
|
||||
type CheckingResults struct { |
||||
ImproperlyFormattedCount uint |
||||
InvalidChecksumCount uint |
||||
FilesNotRead uint |
||||
} |
||||
|
||||
// SumFunc is a type of function that computes a hash.Hash for data in io.Reader
|
||||
type SumFunc func(io.Reader) (hash.Hash, error) |
||||
|
||||
// ImproperlyFormattedErr is an error return when a line for checking
|
||||
// has an incorrect number or set of characters
|
||||
var ImproperlyFormattedErr = errors.New("improperly formatted line") |
||||
|
||||
func printHash(h hash.Hash, filename string) { |
||||
fmt.Printf("%x %v\n", h.Sum(nil), filename) |
||||
} |
||||
|
||||
// PrintSumForFile prints checksum computed by f for file with filename
|
||||
func PrintSumForFile(filename string, f SumFunc) error { |
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
h, err := f(file) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
printHash(h, filename) |
||||
return nil |
||||
} |
||||
|
||||
// PrintSumForStdin prints checksum computed by f for data from stdin
|
||||
func PrintSumForStdin(f SumFunc) { |
||||
h, err := f(os.Stdin) |
||||
if err != nil { |
||||
PrintToStderr(err) |
||||
os.Exit(1) |
||||
} |
||||
printHash(h, "<stdin>") |
||||
os.Exit(0) |
||||
} |
||||
|
||||
func checkLine(line string, re *regexp.Regexp, f SumFunc) (valid bool, err error) { |
||||
matches := re.MatchString(line) |
||||
if !matches { |
||||
err = ImproperlyFormattedErr |
||||
valid = false |
||||
return |
||||
} |
||||
submatches := re.FindStringSubmatch(line) |
||||
hash := submatches[1] |
||||
filename := submatches[2] |
||||
|
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
valid = false |
||||
return |
||||
} |
||||
defer file.Close() |
||||
h, err := f(file) |
||||
if err != nil { |
||||
valid = false |
||||
return |
||||
} |
||||
|
||||
result := "OK" |
||||
valid = true |
||||
if fmt.Sprintf("%x", h.Sum(nil)) != hash { |
||||
result = "FAILED" |
||||
valid = false |
||||
} |
||||
fmt.Printf("%s: %s\n", filename, result) |
||||
|
||||
return |
||||
} |
||||
|
||||
// CheckSumsInReader checks checksums in r using f
|
||||
func CheckSumsInReader(r io.Reader, re *regexp.Regexp, f SumFunc) (*CheckingResults, error) { |
||||
scanner := bufio.NewScanner(r) |
||||
|
||||
results := CheckingResults{} |
||||
for scanner.Scan() { |
||||
valid, err := checkLine(scanner.Text(), re, f) |
||||
if err == ImproperlyFormattedErr { |
||||
results.ImproperlyFormattedCount++ |
||||
} else if err != nil { |
||||
PrintToStderr(err) |
||||
results.FilesNotRead++ |
||||
} else if !valid { |
||||
results.InvalidChecksumCount++ |
||||
} |
||||
} |
||||
return &results, scanner.Err() |
||||
} |
||||
|
||||
// CheckSumsInFile checks checksums in file with filename using f
|
||||
func CheckSumsInFile(filename string, re *regexp.Regexp, f SumFunc) (*CheckingResults, error) { |
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer file.Close() |
||||
|
||||
return CheckSumsInReader(file, re, f) |
||||
} |
||||
|
||||
// PrintCheckingResults prints the number of mismatched checksums and improperly formatted lines to os.Stderr
|
||||
func PrintCheckingResults(results CheckingResults) { |
||||
if results.InvalidChecksumCount > 0 { |
||||
PrintToStderr( |
||||
fmt.Sprintf("%v computed checksums did NOT match", results.InvalidChecksumCount), |
||||
) |
||||
} |
||||
if results.ImproperlyFormattedCount > 0 { |
||||
PrintToStderr( |
||||
fmt.Sprintf("%v lines are improperly formatted", results.ImproperlyFormattedCount), |
||||
) |
||||
} |
||||
if results.FilesNotRead > 0 { |
||||
PrintToStderr( |
||||
fmt.Sprintf("%v listed files could not be read", results.FilesNotRead), |
||||
) |
||||
} |
||||
} |
||||
|
||||
// SumMain is a function you're supposed to run in main of a hash sum utility
|
||||
func SumMain(re *regexp.Regexp, f SumFunc) { |
||||
var args struct { |
||||
Check bool `arg:"-c"` |
||||
Files []string `arg:"positional"` |
||||
} |
||||
|
||||
arg.MustParse(&args) |
||||
|
||||
exitCode := 0 |
||||
if len(args.Files) == 0 { |
||||
if !args.Check { |
||||
PrintSumForStdin(f) |
||||
} else { |
||||
results, err := CheckSumsInReader(os.Stdin, re, f) |
||||
if err != nil { |
||||
PrintToStderr(err) |
||||
exitCode = 1 |
||||
} |
||||
PrintCheckingResults(*results) |
||||
} |
||||
} |
||||
|
||||
totalResults := CheckingResults{} |
||||
for _, filename := range args.Files { |
||||
if !args.Check { |
||||
err := PrintSumForFile(filename, f) |
||||
if err != nil { |
||||
PrintToStderr(err) |
||||
exitCode = 1 |
||||
} |
||||
} else { |
||||
results, err := CheckSumsInFile(filename, re, f) |
||||
if err != nil { |
||||
PrintToStderr(err) |
||||
exitCode = 1 |
||||
} |
||||
if results != nil { |
||||
totalResults.ImproperlyFormattedCount += results.ImproperlyFormattedCount |
||||
totalResults.InvalidChecksumCount += results.InvalidChecksumCount |
||||
} |
||||
} |
||||
} |
||||
PrintCheckingResults(totalResults) |
||||
os.Exit(exitCode) |
||||
} |
@ -0,0 +1,69 @@ |
||||
package coreutils |
||||
|
||||
import ( |
||||
"bytes" |
||||
"github.com/stretchr/testify/assert" |
||||
"github.com/stretchr/testify/suite" |
||||
"path" |
||||
"strings" |
||||
"testing" |
||||
) |
||||
|
||||
type CheckLineTestSuite struct { |
||||
suite.Suite |
||||
} |
||||
|
||||
func (suite *CheckLineTestSuite) TestImproperlyFormattedLine() { |
||||
t := suite.T() |
||||
valid, err := checkLine("name chef", MD5Regex, MD5Sum) |
||||
assert.Equal(t, false, valid) |
||||
assert.Error(t, err) |
||||
assert.Equal(t, ImproperlyFormattedErr, err) |
||||
} |
||||
|
||||
func (suite *CheckLineTestSuite) TestMismatchedChecksum() { |
||||
t := suite.T() |
||||
line := "4a5fb9ebd6c8ea7efb53d071053ef778 " + path.Join("test_data", "some_file.txt") |
||||
valid, err := checkLine(line, MD5Regex, MD5Sum) |
||||
assert.Equal(t, false, valid) |
||||
assert.NoError(t, err) |
||||
} |
||||
|
||||
func (suite *CheckLineTestSuite) TestValid() { |
||||
t := suite.T() |
||||
line := "6a5fb9ebd6c8ea7efb53d071053ef778 " + path.Join("test_data", "some_file.txt") |
||||
valid, err := checkLine(line, MD5Regex, MD5Sum) |
||||
assert.Equal(t, true, valid) |
||||
assert.NoError(t, err) |
||||
} |
||||
|
||||
func TestCheckLineTestSuite(t *testing.T) { |
||||
suite.Run(t, new(CheckLineTestSuite)) |
||||
} |
||||
|
||||
type CheckSumsInReaderTestSuite struct { |
||||
suite.Suite |
||||
} |
||||
|
||||
func (suite *CheckSumsInReaderTestSuite) Test() { |
||||
t := suite.T() |
||||
buf := bytes.NewBufferString(strings.Join( |
||||
[]string{ |
||||
"name chef", |
||||
"4a5fb9ebd6c8ea7efb53d071053ef778 " + path.Join("test_data", "some_file.txt"), |
||||
"6a5fb9ebd6c8ea7efb53d071053ef778 " + path.Join("test_data", "some_file.txt"), |
||||
"4a5fb9ebd6c8ea7efb53d071053ef778 nonexistant_file", |
||||
}, |
||||
"\n", |
||||
)) |
||||
results, err := CheckSumsInReader(buf, MD5Regex, MD5Sum) |
||||
assert.NotNil(t, results) |
||||
assert.Equal(t, uint(1), results.ImproperlyFormattedCount) |
||||
assert.Equal(t, uint(1), results.FilesNotRead) |
||||
assert.Equal(t, uint(1), results.InvalidChecksumCount) |
||||
assert.NoError(t, err) |
||||
} |
||||
|
||||
func TestCheckSumsInReaderTestSuite(t *testing.T) { |
||||
suite.Run(t, new(CheckSumsInReaderTestSuite)) |
||||
} |
@ -1,141 +1,9 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"crypto/md5" |
||||
"errors" |
||||
"fmt" |
||||
"hash" |
||||
"io" |
||||
"os" |
||||
"strings" |
||||
|
||||
"github.com/alexflint/go-arg" |
||||
common "source.heropunch.io/tomo/go-coreutils" |
||||
) |
||||
|
||||
var ImproperlyFormattedErr = errors.New("improperly formatted line") |
||||
|
||||
func printSumForFile(filename string, sumFunc func(io.Reader) (hash.Hash, error)) error { |
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
h, err := sumFunc(file) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
printHash(h, filename) |
||||
return nil |
||||
} |
||||
|
||||
func printSumForStdin(sumFunc func(io.Reader) (hash.Hash, error)) { |
||||
h, err := sumFunc(os.Stdin) |
||||
if err != nil { |
||||
common.PrintToStderr(err) |
||||
os.Exit(1) |
||||
} |
||||
printHash(h, "<stdin>") |
||||
os.Exit(0) |
||||
} |
||||
|
||||
func md5sum(r io.Reader) (hash.Hash, error) { |
||||
h := md5.New() |
||||
if _, err := io.Copy(h, r); err != nil { |
||||
return nil, err |
||||
} |
||||
return h, nil |
||||
} |
||||
|
||||
func printHash(h hash.Hash, filename string) { |
||||
fmt.Printf("%x %v\n", h.Sum(nil), filename) |
||||
} |
||||
|
||||
func checkSumForFile(filename string) error { |
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
return checkSums(file) |
||||
} |
||||
|
||||
func checkSums(r io.Reader) error { |
||||
scanner := bufio.NewScanner(r) |
||||
|
||||
for scanner.Scan() { |
||||
err := checkMD5Sum(scanner.Text()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return scanner.Err() |
||||
} |
||||
|
||||
func checkMD5Sum(line string) error { |
||||
s := strings.SplitN(line, " ", 2) |
||||
hash := s[0] |
||||
filename := s[1] |
||||
|
||||
if filename == "" { |
||||
return ImproperlyFormattedErr |
||||
} |
||||
|
||||
file, err := os.Open(filename) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer file.Close() |
||||
|
||||
h, err := md5sum(file) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
result := "OK" |
||||
if fmt.Sprintf("%x", h.Sum(nil)) != hash { |
||||
result = "FAILED" |
||||
} |
||||
fmt.Printf("%s: %s\n", filename, result) |
||||
|
||||
return nil |
||||
} |
||||
|
||||
func main() { |
||||
var args struct { |
||||
Check bool `arg:"-c"` |
||||
Files []string `arg:"positional"` |
||||
} |
||||
|
||||
arg.MustParse(&args) |
||||
|
||||
exitCode := 0 |
||||
if len(args.Files) == 0 { |
||||
if !args.Check { |
||||
printSumForStdin(md5sum) |
||||
} else { |
||||
fmt.Println("Checking from stdin") |
||||
} |
||||
} |
||||
|
||||
for _, filename := range args.Files { |
||||
if !args.Check { |
||||
err := printSumForFile(filename, md5sum) |
||||
if err != nil { |
||||
common.PrintToStderr(err) |
||||
exitCode = 1 |
||||
} |
||||
} else { |
||||
err := checkSumForFile(filename) |
||||
if err != nil { |
||||
common.PrintToStderr(err) |
||||
exitCode = 1 |
||||
} |
||||
} |
||||
} |
||||
|
||||
os.Exit(exitCode) |
||||
common.SumMain(common.MD5Regex, common.MD5Sum) |
||||
} |
||||
|
@ -0,0 +1 @@ |
||||
blah blah |
Loading…
Reference in new issue