From 56173fb6d4ad768b11b4d61d513c7f00118849df Mon Sep 17 00:00:00 2001 From: asdf Date: Sat, 21 Sep 2019 20:10:35 +1000 Subject: [PATCH] Refactor error messages and command line flags --- main.go | 125 +++++++++++++++++++++++++++++++++------------------ main_test.go | 26 ++++++++--- 2 files changed, 101 insertions(+), 50 deletions(-) diff --git a/main.go b/main.go index da7ae44..995a609 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,26 @@ /* gfu - gophermap format utility -`gfu` manipulates gophermaps (gopher menus). It is intended to be used as part of an automation chain for managing a gopherhole using maps as the main doctype, easily allowing any document to contain links. +`gfu` manipulates gophermaps (gopher menus). It is intended to be used as part +of an automation chain for managing a gopherhole using maps as the main +doctype, easily allowing any document to contain links. `gfu` can: -- Convert all lines in a file that are not valid gopher links into gopher info text (item type 'i') lines -- Deconstructing all gopher info text lines in a file back to plain text for easy editing +- Convert all lines in a file that are not valid gopher links into gopher info +text (item type 'i') lines +- Deconstruct all gopher info text lines in a file back to plain text for easy +editing There are also plans to include additional features, such as: - Adding the contents of a header file into the gophermap -- Adding the contents of a footer file into the gophermap -*Please note - Many servers already support includes, so the above may not be needed. If you are interested in this feature, you may want to check your server documentation first.* -* +- Adding the contents of a footer file into the gophermap *Please note - many +servers already support includes, so the above may not be needed. If you are +interested in this feature, you may want to check your server documentation +first.* Documentation -For information on using `gfu`, see the help information: - gfu --help +For information on using `gfu`, see the help information: gfu --help Information on building, downloading and installing `gfu` can be found at: https://tildegit.org/sloum/gfu @@ -32,19 +36,22 @@ import ( "flag" "fmt" "io" + "log" "os" "regexp" "strings" ) -var re = regexp.MustCompile(`.+\t.*\t.*\t.*`) +//command line flag global variables +var ( + deconstructInfoTextLines bool + header string + footer string + stdout bool +) -func errorExit(e error, msg string) { - if e != nil { - fmt.Print(msg) - os.Exit(1) - } -} +//regex global variable that identifies the format of gopher menu lines +var reGopherLines = regexp.MustCompile(`.+\t.*\t.*\t.*`) func buildInfoText(ln string) string { var out strings.Builder @@ -64,21 +71,35 @@ func deconstructInfoText(ln string) string { return "" } -func readFile(path string, build bool) bytes.Buffer { +//takes a string representing the file path, reads the file, then passes the +//data for processing. returns data as a bytes.Buffer and any error +//information. +func readFile(path string) (outFile bytes.Buffer, err error) { file, err := os.Open(path) - errorExit(err, fmt.Sprintf("Unable to open file for reading: %s\n", path)) + if err != nil { + return + } + defer file.Close() - return processFile(file, build) + + outFile, err = processFile(file) + if err != nil { + return + } + + return outFile, err } -func processFile(file io.Reader, build bool) bytes.Buffer { +//takes data as a bytes.Buffer, reads and processes each line according to the +//deconstructInfoTextLines flag. returns processed data as a bytes.Buffer and +//any error information. +func processFile(file io.Reader) (outFile bytes.Buffer, err error) { scanner := bufio.NewScanner(file) - var outFile bytes.Buffer - if build { + if !deconstructInfoTextLines { for scanner.Scan() { l := scanner.Text() - if exp := re.MatchString(l); exp { + if exp := reGopherLines.MatchString(l); exp { outFile.WriteString(l) } else { outFile.WriteString(buildInfoText(l)) @@ -88,7 +109,7 @@ func processFile(file io.Reader, build bool) bytes.Buffer { } else { for scanner.Scan() { l := scanner.Text() - if exp := re.MatchString(l); exp && l[0] == 'i' { + if exp := reGopherLines.MatchString(l); exp && l[0] == 'i' { outFile.WriteString(deconstructInfoText(l)) } else { outFile.WriteString(l) @@ -96,19 +117,34 @@ func processFile(file io.Reader, build bool) bytes.Buffer { outFile.WriteString("\n") } } - if err := scanner.Err(); err != nil { - errorExit(err, fmt.Sprintf("Error while reading file\n")) + if err = scanner.Err(); err != nil { + return } - return outFile + + return outFile, nil } -func writeFile(path string, outFile bytes.Buffer) { +//takes a file path as a string and some data as bytes.Buffer. writes the data +//to the specified file. returns any error information. +func writeFile(path string, outFile bytes.Buffer) (err error) { file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0) - errorExit(err, fmt.Sprintf("Unable to open file for writing: %s\n", path)) + if err != nil { + return + } defer file.Close() file.Write(outFile.Bytes()) + return nil } +//configure command line flags +func init() { + flag.BoolVar(&deconstructInfoTextLines, "d", false, "Deconstruct a gophermap's info text lines back to plain text") + flag.StringVar(&header, "head", "", "Path to a file containing header content") + flag.StringVar(&footer, "foot", "", "Path to a file containing footer content") + flag.BoolVar(&stdout, "stdout", false, "Instead of writing changes to a file, return them to stdout") +} + +//PrintHelp produces a nice display message when the --help flag is used func PrintHelp() { art := `gfu - gophermap formatting utility @@ -119,41 +155,44 @@ example: gfu -d ~/gopher/phlog/gophermap default Convert plain text lines to gophermap info text (item type 'i') lines ` - fmt.Fprint(os.Stderr, art) + fmt.Fprint(os.Stdout, art) flag.PrintDefaults() } func main() { - //command-line flags and responses - deconstructInfoTextLines := flag.Bool("d", false, "Deconstruct a gophermap's info text lines back to plain text") - header := flag.String("head", "", "Path to a file containing header content") - footer := flag.String("foot", "", "Path to a file containing footer content") - stdout := flag.Bool("stdout", false, "Instead of writing changes to a file, return them to stdout") + //process command line flags flag.Usage = PrintHelp - flag.Parse() args := flag.Args() if l := len(args); l != 1 { - fmt.Printf("Incorrect number of arguments. Expected 1, got %d\n", l) + fmt.Fprintf(os.Stderr, "Incorrect number of arguments. Expected 1, got %d\n", l) os.Exit(1) } - - //main program - if *header != "" { + if header != "" { fmt.Println("Header functionality is not built yet, proceeding with general gophermap conversion...") } - if *footer != "" { + if footer != "" { fmt.Println("Footer functionality is not built yet, proceeding with general gophermap conversion...") } - outFile := readFile(args[0], !*deconstructInfoTextLines) + //read and process file + outFile, err := readFile(args[0]) + if err != nil { + log.Fatalln("Error while reading file -", err) + } - if *stdout { + //output data to stdout or file + if stdout { fmt.Print(outFile.String()) } else { - writeFile(args[0], outFile) + err = writeFile(args[0], outFile) + if err != nil { + log.Fatalln("Error while writing file -", err) + } } + + //the end os.Exit(0) } diff --git a/main_test.go b/main_test.go index bc024fa..58d10b3 100644 --- a/main_test.go +++ b/main_test.go @@ -41,25 +41,25 @@ var processFileTestCases = []struct { var carriagereturnTestCases = []struct { testInput string expectedOutput string - build bool + deconstruct bool }{ { //Plaintext to gophermap "A test line with a carriage return\r\n", "iA test line with a carriage return false null.host 1\n", - true, + false, }, { //Gophermap to plaintext "iA test line with a cr false null.host 1\r\n", "A test line with a cr\n", - false, + true, }, { //HTML file "hhttp://tildegit.org/sloum/bombadillo url:http://tildegit.org/sloum/bombadillo colorfield.space 70\r\n", "hhttp://tildegit.org/sloum/bombadillo url:http://tildegit.org/sloum/bombadillo colorfield.space 70\n", - true, + false, }, } @@ -79,7 +79,11 @@ func TestBuildInfoText(t *testing.T) { func TestProcessFileBuild(t *testing.T) { for testNum, testCase := range processFileTestCases { - testOutput := processFile(strings.NewReader(testCase.plaintext), true) + deconstructInfoTextLines = false + testOutput, err := processFile(strings.NewReader(testCase.plaintext)) + if err != nil { + t.Errorf("Error occurred: %v", err) + } if testCase.gophermap != testOutput.String() { t.Errorf(`processFile build test case %d failed. Expected "%s", got "%s"`, testNum, @@ -93,7 +97,11 @@ func TestProcessFileBuild(t *testing.T) { func TestProcessFileDecon(t *testing.T) { for testNum, testCase := range processFileTestCases { - testOutput := processFile(strings.NewReader(testCase.gophermap), false) + deconstructInfoTextLines = true + testOutput, err := processFile(strings.NewReader(testCase.gophermap)) + if err != nil { + t.Errorf("Error occurred: %v", err) + } if testCase.plaintext != testOutput.String() { t.Errorf(`processFile decon test case %d failed. Expected "%s", got "%s"`, testNum, @@ -107,7 +115,11 @@ func TestProcessFileDecon(t *testing.T) { func TestCarriageReturnsAreExpunged(t *testing.T) { for testNum, testCase := range carriagereturnTestCases { - testOutput := processFile(strings.NewReader(testCase.testInput), testCase.build) + deconstructInfoTextLines = testCase.deconstruct + testOutput, err := processFile(strings.NewReader(testCase.testInput)) + if err != nil { + t.Errorf("Error occurred: %v", err) + } if testCase.expectedOutput != testOutput.String() { t.Errorf(`CR test case %d failed. Expected "%s", got "%s"`, testNum,