Compare commits

...

8 Commits

Author SHA1 Message Date
593a8ef24e
Update to lynx v0.4.0
All checks were successful
continuous-integration/drone/push Build is passing
2020-04-17 18:41:09 +05:30
d5b95cbec7
Add drone ci config 2020-04-09 02:44:57 +05:30
72b3740cd3
Add v0.3.0 install instructions to readme 2020-04-08 18:25:36 +05:30
fc6a3c01be
Update install script 2020-04-08 18:24:22 +05:30
47a94e3f4e
Re-organize readme 2020-04-08 18:12:10 +05:30
ca23fd7f48
Bump grus version to v0.3.0 2020-04-08 18:08:41 +05:30
5243a7c955
Use stricter pledge promises if possible 2020-04-08 18:00:26 +05:30
e80b9a2c4a
Rethink grus design & add GRUS_STRICT_UNJUMBLE 2020-04-08 17:48:21 +05:30
9 changed files with 296 additions and 227 deletions

View File

@ -14,56 +14,10 @@ Grus is a simple word unjumbler written in Go.
*Tested on*:
- OpenBSD 6.6 (with /pledge/ & /unveil/)
* Documentation
| Demo Video | System Information |
|-------------+------------------------------------|
| [[https://diode.zone/videos/watch/515e2528-a731-4c73-a0da-4f8da21a90c0][Grus v0.2.0]] | OpenBSD 6.6 (with /pledge/ & /unveil/) |
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 |
|---------------------------+------------------------------------|
| =GRUS_SEARCH_ALL= | Search in all dictionaries |
| =GRUS_ANAGRAMS= | Print all anagrams |
| =GRUS_PRINT_PATH= =(v0.2.1+)= | Print dictionary path before words |
Set these environment variable to /1 / true/ to change behaviour.
** Default Dictionaries
These files will be checked by default (in order).
- =/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=
** 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
# print path to dictionary
GRUS_PRINT_PATH=1 grus word
#+END_SRC
* Installation
** Pre-built binaries
Pre-built binaries are available for OpenBSD, FreeBSD, NetBSD, DragonFly BSD,
@ -72,6 +26,10 @@ Linux & macOS.
This will just print the steps to install grus & you have to run those commands
manually. Piping directly to =sh= is not a good idea, don't run this unless you
understand what you're doing.
*** v0.3.0
#+BEGIN_SRC sh
curl -s https://tildegit.org/andinus/grus/raw/tag/v0.3.0/scripts/install.sh | sh
#+END_SRC
*** v0.2.0
#+BEGIN_SRC sh
curl -s https://tildegit.org/andinus/grus/raw/tag/v0.2.0/scripts/install.sh | sh
@ -91,6 +49,56 @@ I'm not allowed to distribute it, you can get it directly from GitHub.
curl -o /usr/local/share/dict/words \
https://raw.githubusercontent.com/dwyl/english-words/master/words.txt
#+END_SRC
* Documentation
*Note*: This is documentation for latest release, releases are tagged & so
previous documentation can be checked by browsing source at tags.
** Environment Variables
*** =GRUS_SEARCH_ALL=
Search in all dictionaries, by default Grus will exit after searching in first
dictionary.
*** =GRUS_ANAGRAMS=
Prints all anagrams if set to true, by default Grus will print all anagrams.
*** =GRUS_PRINT_PATH=
Prints path to dictionary if set to true, this is set to false by default.
*** =GRUS_STRICT_UNJUMBLE=
Overrides everything & will try to print at least one match, if it doesn't find
any then it will exit the program with a non-zero exit code. This will ignore
=GRUS_SEARCH_ALL= till it finds at least one match.
** Default Dictionaries
These files will be checked by default (in order).
- =/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=
** Examples
#+BEGIN_SRC sh
# print grus version
grus version
# print grus env
grus env
# unjumble word
grus word
# don't print all anagrams
GRUS_ANAGRAMS=false 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=true grus word /path/to/dict1 /path/to/dict2
# print path to dictionary
GRUS_PRINT_PATH=1 grus word
# find at least one match
GRUS_STRICT_UNJUMBLE=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

43
build/ci/drone.yml Normal file
View File

@ -0,0 +1,43 @@
---
kind: pipeline
name: testing
steps:
- name: vet
image: golang:1.13
commands:
- go vet ./...
- name: test
image: golang:1.13
commands:
- go test -v ./...
---
kind: pipeline
name: build
steps:
- name: openbsd-amd64
image: golang:1.13
environment:
GOARCH: amd64
GOOS: openbsd
commands:
- go build
- name: linux-amd64
image: golang:1.13
environment:
GOARCH: amd64
GOOS: linux
commands:
- go build
- name: darwin-amd64
image: golang:1.13
environment:
GOARCH: amd64
GOOS: darwin
commands:
- go build

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.13
require (
golang.org/x/sys v0.0.0-20200406113430-c6e801f48ba2
tildegit.org/andinus/lynx v0.2.0
tildegit.org/andinus/lynx v0.4.0
)

2
go.sum
View File

@ -5,3 +5,5 @@ 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=
tildegit.org/andinus/lynx v0.4.0 h1:bAxZLOdWy66+qJ3bDWjkbmJfCWTIOZ8hMGzYt7T7Bxk=
tildegit.org/andinus/lynx v0.4.0/go.mod h1:/PCNkKwfJ7pb6ziHa76a4gYp1R9S1Ro4ANjQwzSpBIk=

121
grus.go
View File

@ -1,121 +0,0 @@
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.1"
if os.Args[1] == "version" {
fmt.Printf("Grus %s\n", version)
os.Exit(0)
}
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...)
}
// 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
}
// Check if user wants to print dictionary path.
printPath := false
printPathEnv := os.Getenv("GRUS_PRINT_PATH")
if printPathEnv == "true" ||
printPathEnv == "1" {
printPath = 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
}
// Print path to dictionary if printPath is true.
if printPath {
fmt.Println(dict)
}
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)
}
}

187
main.go Normal file
View File

@ -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)
}
}

View File

@ -1,44 +0,0 @@
// +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)
}

View File

@ -1,7 +0,0 @@
// +build !openbsd
package main
func main() {
grus()
}

View File

@ -1,11 +1,11 @@
#!/bin/sh
freebsdH="3393230e78fd495dad4193dfdc7aafab11c007de058790aa0dbfd2e7ddd84689"
openbsdH="01b9fddd004b588a07f8fbec756cfc2cc7c07a51d765969e7a79af34dc93bee5"
linuxH="b12957117a839a741c85401ca0305db9d48b623c6e8754b81002cb3c588d9bf3"
netbsdH="67e0efe184b0327700ba67ddc662ba3174a7484de7d8223581367d3fdc21e5b8"
dragonflyH="d7501082472fd0b4b6686d2ec6667bf6965087e5e4fe8c8b04372123e51df5b3"
darwinH="4b96dbd72f0816fa9e89a9c2a249df4a7cb3a043d5a87fb8e6d948f17846b56d"
freebsdH="f77989e2d243dc32ff1ab5aa91b48e2abe72a3daba86b781e7905fd4d6a9bd1a"
openbsdH="0251e6cecb77aa03ac02d4d67ad9d4cd164d199e1a8a3ef4dd8b711fa43a7298"
linuxH="99c35fd048d02fc7136271a7ab2cbe582810d426e2b2bf9687ede6f2b8011960"
netbsdH="f686dae688db4dd1a4733ae0be06b588d47f5d8fac6e23dc11f8175d5dcff53c"
dragonflyH="c7dbf94b379f9b85956cf09fa015e6eab4ee40dd60fe747cde1c2f15cb172ad2"
darwinH="81db0127149b96558ed1d0e556ebe1a653a2e38a0f212fc23257bb9bf0ab2ead"
earlyCheck(){
os=`uname`
@ -33,7 +33,7 @@ earlyCheck(){
}
getURL(){
url="https://archive.org/download/grus-v0.2.0/grus-v0.2.0-$os-$cpu"
url="https://archive.org/download/grus-v0.3.0/grus-v0.3.0-$os-$cpu"
}
printURL(){
@ -68,9 +68,10 @@ printURL(){
echo
echo "Verify the hash by running sha256 on grus binary."
echo "$ sha256 /usr/local/bin/grus"
echo
}
echo "Grus v0.2.0"
echo "Grus v0.3.0"
echo
earlyCheck
getURL