diff --git a/misc.go b/misc.go index 4aa8761..0f18f7a 100644 --- a/misc.go +++ b/misc.go @@ -2,7 +2,6 @@ package main import ( "bufio" - "errors" "fmt" "io" "path/filepath" @@ -136,23 +135,36 @@ func splitWord(s string) (word, rest string) { return } -var reComment = regexp.MustCompile(`#.*$`) -var reTrailingSpace = regexp.MustCompile(`\s+$`) - +// This function reads whitespace separated string pairs at each line. Single +// or double quotes can be used to escape whitespaces. Hash characters can be +// used to add a comment until the end of line. Indentation and trailing space +// is trimmed. Empty lines are skipped. func readPairs(r io.Reader) ([][]string, error) { var pairs [][]string s := bufio.NewScanner(r) for s.Scan() { line := s.Text() - line = reComment.ReplaceAllString(line, "") - line = reTrailingSpace.ReplaceAllString(line, "") + squote, dquote := false, false + for i := 0; i < len(line); i++ { + if line[i] == '\'' && !dquote { + squote = !squote + } else if line[i] == '"' && !squote { + dquote = !dquote + } + if !squote && !dquote && line[i] == '#' { + line = line[:i] + break + } + } + + line = strings.TrimSpace(line) if line == "" { continue } - squote, dquote := false, false + squote, dquote = false, false pair := strings.FieldsFunc(line, func(r rune) bool { if r == '\'' && !dquote { squote = !squote @@ -163,12 +175,11 @@ func readPairs(r io.Reader) ([][]string, error) { }) if len(pair) != 2 { - return nil, errors.New(fmt.Sprintf("expected pair but found: %s", s.Text())) - continue + return nil, fmt.Errorf("expected pair but found: %s", s.Text()) } for i := 0; i < len(pair); i++ { - squote, dquote := false, false + squote, dquote = false, false buf := make([]rune, 0, len(pair[i])) for _, r := range pair[i] { if r == '\'' && !dquote { diff --git a/misc_test.go b/misc_test.go index 2541e04..6962b3b 100644 --- a/misc_test.go +++ b/misc_test.go @@ -3,6 +3,7 @@ package main import ( "os" "reflect" + "strings" "testing" ) @@ -165,6 +166,28 @@ func TestSplitWord(t *testing.T) { } } +func TestReadPairs(t *testing.T) { + tests := []struct { + s string + exp [][]string + }{ + {"foo bar", [][]string{[]string{"foo", "bar"}}}, + {"foo bar ", [][]string{[]string{"foo", "bar"}}}, + {" foo bar", [][]string{[]string{"foo", "bar"}}}, + {" foo bar ", [][]string{[]string{"foo", "bar"}}}, + {"foo bar#baz", [][]string{[]string{"foo", "bar"}}}, + {"foo bar #baz", [][]string{[]string{"foo", "bar"}}}, + {`'foo#baz' bar`, [][]string{[]string{"foo#baz", "bar"}}}, + {`"foo#baz" bar`, [][]string{[]string{"foo#baz", "bar"}}}, + } + + for _, test := range tests { + if got, _ := readPairs(strings.NewReader(test.s)); !reflect.DeepEqual(got, test.exp) { + t.Errorf("at input '%v' expected '%v' but got '%v'", test.s, test.exp, got) + } + } +} + func TestHumanize(t *testing.T) { tests := []struct { i int64