I implemented the caesar cipher along with a writer/reader and a command line interface.
package caesar
import "fmt"
// ShiftOutOfRangeError denotes a shift which is as great or greater than the character count of the alphabet, zero or negative.
type ShiftOutOfRangeError int
// Error return the formated error message.
func (s ShiftOutOfRangeError) Error() string {
return fmt.Sprintf("caesar: shift %d is out of range", s)
}
// Cipher denotes the caesar cipher with mutable shift.
type Cipher struct {
Shift int
}
const (
// letterCount defines the count of characters in the latin alphabet.
letterCount = 26
minShift = -letterCount - 1
maxShift = letterCount - 1
)
// NewCipher initializes a new Cipher instance.
// If there is an error, it will be of type ShiftOutOfRangeError.
func NewCipher(shift int) (Cipher, error) {
if shift < minShift {
return Cipher{minShift}, ShiftOutOfRangeError(shift)
} else if shift > maxShift {
return Cipher{maxShift}, ShiftOutOfRangeError(shift)
}
return Cipher{shift}, nil
}
// Rotate replaces the character with that one the provided places down the alphabet.
// Ignores non-ASCII letters.
func (cipher Cipher) Rotate(c byte) byte {
// ignore non-ASCII letters
if c < 'A' || c > 'z' {
return c
}
isUpper := c < 'a'
c += byte(cipher.Shift)
if (isUpper && c > 'Z') || c > 'z' {
c -= letterCount
} else if c < 'A' || (!isUpper && c < 'a') {
c += letterCount
}
return c
}
// RotateText replaces all characters with that one the provided places down the alphabet.
func (cipher Cipher) RotateText(s string) string {
b := []byte(s)
cipher.RotateBytes(b)
return string(b)
}
// RotateBytes replaces all as characters interpreted bytes with that one the provided places down the alphabet.
func (cipher Cipher) RotateBytes(b []byte) {
for i := 0; i < len(b); i++ {
b[i] = cipher.Rotate(b[i])
}
}
package caesar
import "io"
// Reader rotates all read bytes with the caesar cipher.
type Reader struct {
r io.Reader
Cipher Cipher
}
// NewReader initalizes a new Reader instance.
func NewReader(r io.Reader, c Cipher) Reader {
return Reader{r, c}
}
// Read rotates the by the underlying io.Reader read bytes.
func (r Reader) Read(p []byte) (n int, err error) {
n, err = r.r.Read(p)
r.Cipher.RotateBytes(p)
return
}
package caesar
import "io"
// Writer rotates all written bytes with the caesar cipher.
type Writer struct {
w io.Writer
Cipher Cipher
}
// NewWriter initializes a new Writer instance.
func NewWriter(w io.Writer, c Cipher) Writer {
return Writer{w, c}
}
// Write forwards the rotated bytes to the underlying io.Writer.
func (w Writer) Write(p []byte) (int, error) {
w.Cipher.RotateBytes(p)
return w.w.Write(p)
}
package main
import (
"fmt"
"io"
"os"
"strconv"
"github.com/r3turnz/caesar"
)
func fatalf(format string, a ...interface{}) {
fmt.Fprintf(os.Stderr, format, a...)
os.Exit(1)
}
func printHelp() {
fmt.Printf("Usage: %s -h shift\n"+
"Read from stdin and write with caesar cipher encoded characters to stdout.\n", os.Args[0])
}
func main() {
if len(os.Args) > 1 && ("-h" == os.Args[1] || "--help" == os.Args[1]) {
printHelp()
os.Exit(1)
}
var shift int
if len(os.Args) > 1 {
var err error
shift, err = strconv.Atoi(os.Args[1])
if err != nil {
fatalf("Shift %q is not a valid integer", os.Args[1])
}
} else {
shift = 13
}
cipher, err := caesar.NewCipher(shift)
if err != nil {
err := err.(caesar.ShiftOutOfRangeError)
fatalf("Shift %q is out of range\n", err)
}
out := caesar.NewWriter(os.Stdout, cipher)
io.Copy(out, os.Stdin)
}
I am quite new to go and any improvment suggestions are welcome!
Cipher('Z', 1)should be'A', but it is actually'Y'. \$\endgroup\$