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 |
package main |
||||||
|
|
||||||
import ( |
import ( |
||||||
"bufio" |
|
||||||
"crypto/md5" |
|
||||||
"errors" |
|
||||||
"fmt" |
|
||||||
"hash" |
|
||||||
"io" |
|
||||||
"os" |
|
||||||
"strings" |
|
||||||
|
|
||||||
"github.com/alexflint/go-arg" |
|
||||||
common "source.heropunch.io/tomo/go-coreutils" |
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() { |
func main() { |
||||||
var args struct { |
common.SumMain(common.MD5Regex, common.MD5Sum) |
||||||
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) |
|
||||||
} |
} |
||||||
|
@ -0,0 +1 @@ |
|||||||
|
blah blah |
Loading…
Reference in new issue