diff --git a/README.md b/README.md new file mode 100644 index 0000000..5251104 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# Qwertyflip + +qwertyflip transforms input files by flipping their characters based on keyboard positions. + +You can run it as: + +```bash +./qwertyflip +``` + +where `command file` is a file containing a string of comma-separated transforms. The transforms can +be one of `H` for a horizontal keyboard flip, `V` for a vertical keyboard flip, or `+/-N` for a shift +by N positions. So the command `H,-3,V` would flip an input of `1` first horizontally to `0`, then +shift by -3 positions to `7`, then flip vertically to `m`. + +Output is produced to standard out. diff --git a/flip/flip.go b/flip/flip.go index c5fb0f3..30de648 100644 --- a/flip/flip.go +++ b/flip/flip.go @@ -18,8 +18,8 @@ const ( ROWS = 4 ) -func NewFlipper() Flipper { - flipper := Flipper{ +func NewFlipper() *Flipper { + flipper := &Flipper{ keyboard: "1234567890qwertyuiopasdfghjkl;zxcvbnm,./", keyMap: make(map[rune]int), builder: strings.Builder{}, @@ -77,18 +77,33 @@ func (f *Flipper) Shift(places int, line string) string { return f.builder.String() } -func (f *Flipper) RunCommand(command, line string) (string, error) { - components := strings.Split(command, ",") - for _, component := range components { - if component == "H" { - line = f.HorizontalFlip(line) - } else if component == "V" { - line = f.VerticalFlip(line) - } else if places, err := strconv.Atoi(component); err == nil { - line = f.Shift(places, line) +type flipperFunction func(string) string + +type Transform []flipperFunction + +func (f *Flipper) ParseCommand(command string) (Transform, error) { + parts := strings.Split(command, ",") + retval := make([]flipperFunction, len(parts)) + for i, part := range parts { + if part == "H" { + retval[i] = f.HorizontalFlip + } else if part == "V" { + retval[i] = f.VerticalFlip + } else if places, err := strconv.Atoi(part); err == nil { + retval[i] = func(line string) string { return f.Shift(places, line) } + } else if part == "" { + // just in case there's a trailing comma or similar + retval[i] = func(line string) string { return line } } else { - return "", fmt.Errorf("%s is an invalid command", component) + return nil, fmt.Errorf("%s is an invalid command", part) } } - return line, nil + return retval, nil +} + +func (t Transform) Apply(line string) string { + for _, funct := range t { + line = funct(line) + } + return line } diff --git a/flip/flip_test.go b/flip/flip_test.go index 18d0a02..cdb6a2a 100644 --- a/flip/flip_test.go +++ b/flip/flip_test.go @@ -86,16 +86,36 @@ func TestCommands(t *testing.T) { {"asdf", "V,-3"}: "890q", {"3 Dragons?", "V,-8,H"}: "h Dt84;c7?", {"ミaカbサc", "-28,H"}: "ミ,カ4サ6", + {"1", "H,-3,V"}: "m", + {"1", ""}: "1", } for test, expected := range tests { t.Run(test.line+"_"+test.command, func(t *testing.T) { - result, err := flipper.RunCommand(test.command, test.line) + transform, err := flipper.ParseCommand(test.command) if err != nil { t.Fatal(err) } + result := transform.Apply(test.line) if result != expected { t.Fatalf("Expected %s got %s", expected, result) } }) } } + +func TestBadCommands(t *testing.T) { + flipper := flip.NewFlipper() + tests := []string{ + "HOORAY", + "H,V,T", + "-30,-3f", + } + for _, test := range tests { + t.Run(test, func(t *testing.T) { + _, err := flipper.ParseCommand(test) + if err == nil { + t.Fatalf("Expected %s to error but it didn't", test) + } + }) + } +} diff --git a/main.go b/main.go index 893da02..e9be9c9 100644 --- a/main.go +++ b/main.go @@ -10,17 +10,18 @@ import ( "github.com/fastfadingviolets/qwertyflip/flip" ) -func getCommand(fromFile string) (string, error) { +func getTransform(flipper *flip.Flipper, fromFile string) (flip.Transform, error) { cmdFile, err := os.Open(fromFile) if err != nil { - return "", fmt.Errorf("Unable to open command file %s: %v", fromFile, err) + return nil, fmt.Errorf("Unable to open command file %s: %v", fromFile, err) } defer cmdFile.Close() bytes, err := io.ReadAll(cmdFile) if err != nil { - return "", fmt.Errorf("Unable to read command file %s: %v", fromFile, err) + return nil, fmt.Errorf("Unable to read command file %s: %v", fromFile, err) } - return strings.TrimSpace(string(bytes)), nil + cmdString := strings.TrimSpace(string(bytes)) + return flipper.ParseCommand(cmdString) } func main() { @@ -29,13 +30,13 @@ func main() { os.Exit(1) } commandFile, inputFile := os.Args[1], os.Args[2] - command, err := getCommand(commandFile) + flipper := flip.NewFlipper() + transform, err := getTransform(flipper, commandFile) if err != nil { fmt.Fprint(os.Stderr, err) os.Exit(1) } - flipper := flip.NewFlipper() file, err := os.Open(inputFile) if err != nil { fmt.Fprintf(os.Stderr, "Unable to open input file %s: %v", inputFile, err) @@ -44,11 +45,7 @@ func main() { defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { - line, err := flipper.RunCommand(command, scanner.Text()) - if err != nil { - fmt.Fprint(os.Stderr, err) - os.Exit(1) - } + line := transform.Apply(scanner.Text()) fmt.Println(line) } }