Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
593a8ef24e | |||
d5b95cbec7 | |||
72b3740cd3 | |||
fc6a3c01be | |||
47a94e3f4e | |||
ca23fd7f48 | |||
5243a7c955 | |||
e80b9a2c4a |
100
README.org
100
README.org
|
@ -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
43
build/ci/drone.yml
Normal 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
2
go.mod
|
@ -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
2
go.sum
|
@ -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
121
grus.go
|
@ -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
187
main.go
Normal 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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// +build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
grus()
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user