summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndinus <andinus@nand.sh>2020-04-08 04:31:21 +0530
committerAndinus <andinus@nand.sh>2020-04-08 04:37:48 +0530
commitfa68976a3e7adb8c2c3327c6afc44fef24297337 (patch)
treebe78b3586c60a47845f2c334478628a86fda1d14
parenta3ede2f5edd006affda8ab386dee3fd44d61c473 (diff)
downloadgrus-fa68976a3e7adb8c2c3327c6afc44fef24297337.tar.gz
Initial rewrite of grus
-rw-r--r--README.org40
-rw-r--r--go.sum2
-rw-r--r--grus.go106
-rw-r--r--main_openbsd.go44
-rw-r--r--main_other.go7
5 files changed, 193 insertions, 6 deletions
diff --git a/README.org b/README.org
index 2aad5e9..859005d 100644
--- a/README.org
+++ b/README.org
@@ -10,12 +10,40 @@ Grus is a simple word unjumbler written in Go.
 | GitHub (Mirror) | [[https://github.com/andinus/grus][Grus - GitHub]]  |
 
 *Tested on*:
-- OpenBSD 6.6 (with /unveil/)
+- OpenBSD 6.6 (with /pledge/ & /unveil/)
 
-* Working
-- Grus takes a word as input from the user
-- Input is ordered in [[https://wikipedia.org/wiki/Lexicographical_order][lexical order]]
-- Ordered input is searched in dictionary
+* Documentation
+Grus stops the search as soon as it unjumbles the word, so no anagrams are
+returned & maybe all dictionaries were not searched. However, this behaviour can
+be changed with two environment variables documented below.
+
+*Note*: If grus couldn't unjumble the word with first dictionary then it'll search
+in next dictionary, search stops once the word gets unjumbled.
+
+| Environment variable | Explanation                | Non-default values |
+|----------------------+----------------------------+--------------------|
+| =GRUS_SEARCH_ALL=      | Search in all dictionaries | 1 / true           |
+| =GRUS_ANAGRAMS=        | Print all anagrams         | 1 / true           |
+** Examples
+#+BEGIN_SRC sh
+# unjumble word
+grus word
+
+# print all anagrams
+GRUS_ANAGRAMS=true grus word
+
+# search for word in all dictionaries
+GRUS_SEARCH_ALL=true grus word
+
+# search for word in custom dictionaries too
+grus word /path/to/dict1 /path/to/dict2
+
+# search for word in all dictionaries
+GRUS_SEARCH_ALL=1 grus word /path/to/dict1 /path/to/dict2
+
+# search for word in all dictionaries & print all anagrams
+GRUS_SEARCH_ALL=1 GRUS_ANAGRAMS=1 grus word
+#+END_SRC
 * History
 Initial version of Grus was just a simple shell script that used the slowest
 method of unjumbling words, it checked every permutation of the word with all
@@ -113,5 +141,5 @@ database.
 
 This was true till v0.1.0, v0.2.0 was rewritten & it dropped the use of database
 or any form of pre-parsing the dictionary. Instead it would look through each
-line of dictionary & unjumble the word, while this is a lot slower than previous
+line of dictionary & unjumble the word, while this may be slower than previous
 version but this is simpler.
diff --git a/go.sum b/go.sum
index dbd60ff..8d3b70d 100644
--- a/go.sum
+++ b/go.sum
@@ -3,3 +3,5 @@ golang.org/x/sys v0.0.0-20200406113430-c6e801f48ba2 h1:Z9pPywZscwuw0ijrLEbTzW9lp
 golang.org/x/sys v0.0.0-20200406113430-c6e801f48ba2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 tildegit.org/andinus/lynx v0.1.0 h1:7YjyF8h7MBGKRgQZT0j0I3uHRPf3mI2GMiDujXVlLS0=
 tildegit.org/andinus/lynx v0.1.0/go.mod h1:/PCNkKwfJ7pb6ziHa76a4gYp1R9S1Ro4ANjQwzSpBIk=
+tildegit.org/andinus/lynx v0.2.0 h1:cBoAWqC/osZJE4VPdB0HhIEpMIC4A4eI9nEbHR/9Qvk=
+tildegit.org/andinus/lynx v0.2.0/go.mod h1:/PCNkKwfJ7pb6ziHa76a4gYp1R9S1Ro4ANjQwzSpBIk=
diff --git a/grus.go b/grus.go
new file mode 100644
index 0000000..d4a63ad
--- /dev/null
+++ b/grus.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+
+	"tildegit.org/andinus/grus/lexical"
+)
+
+func grus() {
+	if len(os.Args) == 1 {
+		fmt.Println("Usage: grus <word> <dictionaries>")
+		os.Exit(1)
+	}
+
+	version := "v0.2.0"
+
+	if os.Args[1] == "version" {
+		fmt.Printf("Grus %s\n", version)
+		os.Exit(0)
+	}
+
+	dicts := []string{
+		"/usr/local/share/dict/words",
+		"/usr/share/dict/words",
+		"/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...)
+	}
+
+	// Check if user has asked to search in all dictionaries.
+	searchAll := false
+	searchAllEnv := os.Getenv("GRUS_SEARCH_ALL")
+	if searchAllEnv == "true" ||
+		searchAllEnv == "1" {
+		searchAll = true
+	}
+
+	// Check if user wants anagrams.
+	anagrams := false
+	anagramsEnv := os.Getenv("GRUS_ANAGRAMS")
+	if anagramsEnv == "true" ||
+		anagramsEnv == "1" {
+		anagrams = true
+	}
+
+	for _, 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
+		}
+
+		file, err := os.Open(dict)
+		panicOnErr(err)
+		defer file.Close()
+
+		// We use this to record if the word was unjumbled.
+		unjumbled := false
+
+		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 !anagrams {
+					os.Exit(0)
+				}
+				unjumbled = true
+			}
+		}
+		panicOnErr(scanner.Err())
+
+		// If word was unjumbled & user hasn't asked to search
+		// in all dictionaries then exit the program otherwise
+		// keep searching in other dictionaries.
+		if unjumbled &&
+			!searchAll {
+			os.Exit(0)
+		}
+	}
+}
+
+func panicOnErr(err error) {
+	if err != nil {
+		panic(err)
+	}
+}
diff --git a/main_openbsd.go b/main_openbsd.go
new file mode 100644
index 0000000..7d466ee
--- /dev/null
+++ b/main_openbsd.go
@@ -0,0 +1,44 @@
+// +build openbsd
+
+package main
+
+import (
+	"os"
+
+	"golang.org/x/sys/unix"
+	"tildegit.org/andinus/lynx"
+)
+
+func main() {
+	err := unix.PledgePromises("unveil stdio rpath")
+	panicOnErr(err)
+
+	unveil()
+
+	// Drop unveil from promises.
+	err = unix.PledgePromises("stdio rpath")
+	panicOnErr(err)
+
+	grus()
+}
+
+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)
+}
diff --git a/main_other.go b/main_other.go
new file mode 100644
index 0000000..88824ad
--- /dev/null
+++ b/main_other.go
@@ -0,0 +1,7 @@
+// +build !openbsd
+
+package main
+
+func main() {
+	grus()
+}