package main
import (
"bufio"
"fmt"
"os"
"tildegit.org/andinus/grus/lexical"
"tildegit.org/andinus/lynx"
)
func main() {
initGrus()
if len(os.Args) == 1 {
fmt.Println("Usage: grus <word> <dictionaries>")
os.Exit(1)
}
version := "v0.3.1"
// Print version if first argument is version.
if os.Args[1] == "version" {
fmt.Printf("Grus %s\n", version)
os.Exit(0)
}
// Define default environment variables.
envVar := make(map[string]bool)
envVar["GRUS_SEARCH_ALL"] = false
envVar["GRUS_ANAGRAMS"] = true
envVar["GRUS_STRICT_UNJUMBLE"] = false
envVar["GRUS_PRINT_PATH"] = false
// Check environment variables.
for k, _ := range envVar {
env := os.Getenv(k)
if env == "false" ||
env == "0" {
envVar[k] = false
} else if env == "true" ||
env == "1" {
envVar[k] = true
}
}
// Print environment variables if first argument is env.
if os.Args[1] == "env" {
for k, v := range envVar {
fmt.Printf("%s: %t\n", k, v)
}
os.Exit(0)
}
// Define default dictionaries.
dicts := []string{
"/usr/local/share/dict/words",
"/usr/local/share/dict/web2",
"/usr/share/dict/words",
"/usr/share/dict/web2",
"/usr/share/dict/special/4bsd",
"/usr/share/dict/special/math",
}
// User has specified dictionaries, prepend them to dicts
// list.
if len(os.Args) >= 3 {
dicts = append(os.Args[2:], dicts...)
}
// We use this to record if the word was unjumbled.
unjumbled := false
for k, dict := range dicts {
if _, err := os.Stat(dict); err != nil &&
!os.IsNotExist(err) {
// Error is not nil & also it's not path
// doesn't exist error. We do it this way to
// avoid another level of indentation.
panic(err)
} else if err != nil &&
os.IsNotExist(err) {
// If file doesn't exist then continue with
// next dictionary.
continue
}
// Print path to dictionary if printPath is true.
if envVar["GRUS_PRINT_PATH"] {
if k != 0 {
fmt.Println()
}
fmt.Println(dict)
}
file, err := os.Open(dict)
panicOnErr(err)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
// Filter words by comparing length first &
// run lexical.Sort on it only if the length
// is equal.
if len(scanner.Text()) == len(os.Args[1]) &&
lexical.Sort(scanner.Text()) == lexical.Sort(os.Args[1]) {
fmt.Println(scanner.Text())
// If the user doesn't want anagrams
// then exit the program.
if !envVar["GRUS_ANAGRAMS"] {
os.Exit(0)
}
unjumbled = true
}
}
panicOnErr(scanner.Err())
file.Close()
// If the user has asked to strictly unjumble then we
// cannot exit till it's unjumbled.
if envVar["GRUS_STRICT_UNJUMBLE"] &&
!unjumbled {
// If user has asked to strictly unjumble & we
// haven't done that yet & this is the last
// dictionary then we've failed to unjumble it
// & the program must exit with a non-zero
// exit code.
if k == len(dicts)-1 {
os.Exit(1)
}
// Cannot exit, must search next dictionary.
continue
}
// If user hasn't asked to search all dictionaries
// then exit the program.
if !envVar["GRUS_SEARCH_ALL"] {
os.Exit(0)
}
}
}
func initGrus() {
// We need less permissions on these conditions.
if len(os.Args) == 1 ||
os.Args[1] == "version" ||
os.Args[1] == "env" {
err := lynx.PledgePromises("stdio")
panicOnErr(err)
} else {
err := lynx.PledgePromises("unveil stdio rpath")
panicOnErr(err)
unveil()
// Drop unveil from promises.
err = lynx.PledgePromises("stdio rpath")
panicOnErr(err)
}
}
func unveil() {
paths := make(map[string]string)
paths["/usr/share/dict"] = "r"
paths["/usr/local/share/dict"] = "r"
// Unveil user defined dictionaries.
if len(os.Args) >= 3 {
for _, dict := range os.Args[2:] {
paths[dict] = "r"
}
}
// This will not return error if the file doesn't exist.
err := lynx.UnveilPaths(paths)
panicOnErr(err)
// Block further unveil calls.
err = lynx.UnveilBlock()
panicOnErr(err)
}
func panicOnErr(err error) {
if err != nil {
panic(err)
}
}