summary refs log tree commit diff stats
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go187
1 files changed, 187 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..13971e7
--- /dev/null
+++ b/main.go
@@ -0,0 +1,187 @@
+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)
+	}
+}