Compare commits
19 Commits
Author | SHA1 | Date |
---|---|---|
Andinus | 593a8ef24e | |
Andinus | d5b95cbec7 | |
Andinus | 72b3740cd3 | |
Andinus | fc6a3c01be | |
Andinus | 47a94e3f4e | |
Andinus | ca23fd7f48 | |
Andinus | 5243a7c955 | |
Andinus | e80b9a2c4a | |
Andinus | cd94bf2bdd | |
Andinus | 9fc666f608 | |
Andinus | 832ac493c7 | |
Andinus | ef6aa9a902 | |
Andinus | a2f0bec9c5 | |
Andinus | fa68976a3e | |
Andinus | a3ede2f5ed | |
Andinus | 34adb3a7e6 | |
Andinus | 47d22337b3 | |
Andinus | 1cd23814d9 | |
Andinus | 137618c299 |
98
README.org
98
README.org
|
@ -1,6 +1,8 @@
|
|||
#+HTML_HEAD: <link rel="stylesheet" href="../../static/style.css">
|
||||
#+HTML_HEAD: <link rel="icon" href="../../static/grus/favicon.png" type="image/png">
|
||||
#+EXPORT_FILE_NAME: index
|
||||
#+OPTIONS: toc:nil
|
||||
#+TOC: headlines 3
|
||||
#+TITLE: Grus
|
||||
|
||||
Grus is a simple word unjumbler written in Go.
|
||||
|
@ -9,13 +11,94 @@ Grus is a simple word unjumbler written in Go.
|
|||
| Source Code | [[https://tildegit.org/andinus/grus][Andinus / Grus]] |
|
||||
| GitHub (Mirror) | [[https://github.com/andinus/grus][Grus - GitHub]] |
|
||||
|
||||
* 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 grus's database
|
||||
*Tested on*:
|
||||
- OpenBSD 6.6 (with /pledge/ & /unveil/)
|
||||
|
||||
It returns unjumbled word along with all the anagrams.
|
||||
| Demo Video | System Information |
|
||||
|-------------+------------------------------------|
|
||||
| [[https://diode.zone/videos/watch/515e2528-a731-4c73-a0da-4f8da21a90c0][Grus v0.2.0]] | OpenBSD 6.6 (with /pledge/ & /unveil/) |
|
||||
|
||||
* Installation
|
||||
** Pre-built binaries
|
||||
Pre-built binaries are available for OpenBSD, FreeBSD, NetBSD, DragonFly BSD,
|
||||
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
|
||||
#+END_SRC
|
||||
** Post install
|
||||
You need to have a dictionary for grus to work, if you don't have one then you
|
||||
can download the Webster's Second International Dictionary, all 234,936 words
|
||||
worth. The 1934 copyright has lapsed.
|
||||
#+BEGIN_SRC sh
|
||||
curl -L -o /usr/local/share/dict/web2 \
|
||||
https://archive.org/download/grus-v0.2.0/web2
|
||||
#+END_SRC
|
||||
|
||||
There is also another big dictionary with around half a million english words.
|
||||
I'm not allowed to distribute it, you can get it directly from GitHub.
|
||||
#+BEGIN_SRC sh
|
||||
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
|
||||
|
@ -110,3 +193,8 @@ func Sort(word string) (sorted string) {
|
|||
|
||||
Instead of creating lots of small files, entries are stored in a sqlite3
|
||||
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 may be slower than previous
|
||||
version but this is simpler.
|
||||
|
|
|
@ -24,7 +24,7 @@ steps:
|
|||
GOARCH: amd64
|
||||
GOOS: openbsd
|
||||
commands:
|
||||
- go build ./cmd/grus
|
||||
- go build
|
||||
|
||||
- name: linux-amd64
|
||||
image: golang:1.13
|
||||
|
@ -32,7 +32,7 @@ steps:
|
|||
GOARCH: amd64
|
||||
GOOS: linux
|
||||
commands:
|
||||
- go build ./cmd/grus
|
||||
- go build
|
||||
|
||||
- name: darwin-amd64
|
||||
image: golang:1.13
|
||||
|
@ -40,4 +40,4 @@ steps:
|
|||
GOARCH: amd64
|
||||
GOOS: darwin
|
||||
commands:
|
||||
- go build ./cmd/grus
|
||||
- go build
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
package main
|
||||
|
||||
import "os"
|
||||
|
||||
// getEnv will check if the the key exists, if it does then it'll
|
||||
// return the value otherwise it will return fallback string.
|
||||
func getEnv(key, fallback string) string {
|
||||
// We use os.LookupEnv instead of using os.GetEnv and checking
|
||||
// if the length equals 0 because environment variable can be
|
||||
// set and be of length 0. User could've set key="" which
|
||||
// means the variable was set but the length is 0.
|
||||
value, exists := os.LookupEnv(key)
|
||||
if !exists {
|
||||
value = fallback
|
||||
}
|
||||
return value
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"tildegit.org/andinus/grus/lexical"
|
||||
"tildegit.org/andinus/grus/search"
|
||||
"tildegit.org/andinus/grus/storage"
|
||||
)
|
||||
|
||||
func grus() {
|
||||
version := "v0.1.0"
|
||||
|
||||
// Early Check: If command was not passed then print usage and
|
||||
// exit. Later command & service both are checked, this check
|
||||
// is for version command. If not checked then running grus
|
||||
// without any args will fail because os.Args[1] will panic
|
||||
// the program & produce runtime error.
|
||||
if len(os.Args) == 1 || len(os.Args[1]) == 0 {
|
||||
printUsage()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Running just `grus` would've paniced the program here if
|
||||
// length of os.Args was not checked beforehand because there
|
||||
// would be no os.Args[1].
|
||||
switch os.Args[1] {
|
||||
case "version", "v", "-version", "--version", "-v":
|
||||
fmt.Printf("Grus %s\n", version)
|
||||
os.Exit(0)
|
||||
case "help", "-help", "--help", "-h":
|
||||
printUsage()
|
||||
os.Exit(0)
|
||||
case "init", "i":
|
||||
db := storage.Init()
|
||||
db.Conn.Close()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Initialize the database connection.
|
||||
db := storage.InitConn()
|
||||
defer db.Conn.Close()
|
||||
|
||||
word := os.Args[1]
|
||||
sorted := lexical.Sort(word)
|
||||
|
||||
anagrams, err := search.Anagrams(sorted, db)
|
||||
if err == sql.ErrNoRows {
|
||||
fmt.Println("Word not found in database.")
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Fatalf("grus: Search failed :: %s", err)
|
||||
}
|
||||
for _, w := range anagrams {
|
||||
fmt.Println(w)
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
// +build openbsd
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
"tildegit.org/andinus/grus/storage"
|
||||
"tildegit.org/andinus/lynx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
unveil()
|
||||
grus()
|
||||
}
|
||||
|
||||
func unveil() {
|
||||
path := storage.GetDir()
|
||||
err := os.MkdirAll(path, os.ModePerm)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create directory: %s", path)
|
||||
}
|
||||
|
||||
paths := make(map[string]string)
|
||||
|
||||
paths[path] = "rwc"
|
||||
|
||||
err = lynx.UnveilPathsStrict(paths)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Block further unveil calls.
|
||||
err = unix.UnveilBlock()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
// +build !openbsd
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
grus()
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println("Usage: grus <word> / <command>")
|
||||
fmt.Println("\nCommands: ")
|
||||
fmt.Println(" help Print help")
|
||||
fmt.Println(" version Print Grus version")
|
||||
}
|
3
go.mod
3
go.mod
|
@ -3,7 +3,6 @@ module tildegit.org/andinus/grus
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
golang.org/x/sys v0.0.0-20200406113430-c6e801f48ba2
|
||||
tildegit.org/andinus/lynx v0.1.0
|
||||
tildegit.org/andinus/lynx v0.4.0
|
||||
)
|
||||
|
|
7
go.sum
7
go.sum
|
@ -1,8 +1,9 @@
|
|||
framagit.org/andinus/grus v0.0.0-20200323142459-7a9fbe3c72e7 h1:+TuTHGVgEbsqFnjuWI064YfaCWImWndppHIZh/bMAhY=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200406113430-c6e801f48ba2 h1:Z9pPywZscwuw0ijrLEbTzW9lppFgBY4HDgbvoDnreQs=
|
||||
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=
|
||||
tildegit.org/andinus/lynx v0.4.0 h1:bAxZLOdWy66+qJ3bDWjkbmJfCWTIOZ8hMGzYt7T7Bxk=
|
||||
tildegit.org/andinus/lynx v0.4.0/go.mod h1:/PCNkKwfJ7pb6ziHa76a4gYp1R9S1Ro4ANjQwzSpBIk=
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package lexical
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestSlowSort tests the SlowSort func.
|
||||
func TestSlowSort(t *testing.T) {
|
||||
words := make(map[string]string)
|
||||
|
||||
words["dcba"] = "abcd"
|
||||
words["zyx"] = "xyz"
|
||||
|
||||
for word, sorted := range words {
|
||||
s := SlowSort(word)
|
||||
if s != sorted {
|
||||
t.Errorf("Sort func failed, got %s, want %s",
|
||||
s, sorted)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package lexical
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestSort tests the Sort func.
|
||||
func TestSort(t *testing.T) {
|
||||
words := make(map[string]string)
|
||||
|
||||
words["dcba"] = "abcd"
|
||||
words["zyx"] = "xyz"
|
||||
|
||||
for word, sorted := range words {
|
||||
s := Sort(word)
|
||||
if s != sorted {
|
||||
t.Errorf("Sort func failed, got %s, want %s",
|
||||
s, sorted)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,56 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
import sys
|
||||
import sqlite3
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='grus-add')
|
||||
parser.add_argument('-f', '--file', type=str, required=False,
|
||||
help='file containing list of strings')
|
||||
parser.add_argument('-w', '--word', type=str, required=False,
|
||||
help='file containing list of strings')
|
||||
parser.add_argument('-d', '--db', type=str, required=True,
|
||||
help='database file')
|
||||
args = parser.parse_args()
|
||||
|
||||
if __name__ == '__main__':
|
||||
if args.file == None and args.word == None:
|
||||
print("-f or -w required")
|
||||
print("run grus-add --help to print help")
|
||||
sys.exit(1)
|
||||
|
||||
conn = sqlite3.connect(args.db)
|
||||
curs = conn.cursor()
|
||||
|
||||
stmt = """CREATE TABLE IF NOT EXISTS words (
|
||||
word TEXT PRIMARY KEY NOT NULL,
|
||||
sorted TEXT NOT NULL);"""
|
||||
curs.execute(stmt)
|
||||
conn.commit()
|
||||
|
||||
stmt = """INSERT INTO words(word, sorted)
|
||||
VALUES(?, ?);"""
|
||||
|
||||
if args.file != None:
|
||||
rows = []
|
||||
with open(args.file) as words:
|
||||
for word in words:
|
||||
word = word.strip()
|
||||
lexical = ''.join(sorted(word))
|
||||
rows.append((word, lexical))
|
||||
print(len(rows))
|
||||
sys.stdout.write('\x1b[1A')
|
||||
sys.stdout.write('\x1b[2K')
|
||||
curs.executemany(stmt, rows)
|
||||
|
||||
elif args.word != None:
|
||||
word = args.word.strip()
|
||||
lexical = ''.join(sorted(word))
|
||||
curs.execute(stmt, (word, lexical))
|
||||
|
||||
conn.commit()
|
||||
|
||||
curs.close()
|
||||
conn.close()
|
||||
|
||||
print("Database initialized.")
|
13
scripts/init
13
scripts/init
|
@ -1,13 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Download the list of words.
|
||||
curl -o /tmp/words.txt \
|
||||
https://raw.githubusercontent.com/dwyl/english-words/master/words.txt
|
||||
|
||||
# Make the script executable.
|
||||
chmod +x grus-add
|
||||
|
||||
# Add those words to the database.
|
||||
./grus-add \
|
||||
-d $HOME/.local/share/grus/grus.db \
|
||||
-f /tmp/words.txt
|
|
@ -1,56 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
openbsdH="25a8dc77cda3d225c85d3f0cb318d01c17546c1c4a8c789a318f832ce1948bc3"
|
||||
|
||||
earlyCheck(){
|
||||
os=`uname`
|
||||
os=`echo $os | tr "[:upper:]" "[:lower:"]`
|
||||
|
||||
case $os in
|
||||
*openbsd* ) ;;
|
||||
*)
|
||||
echo "Pre-built binary not available for your os"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cpu=`uname -m`
|
||||
cpu=`echo $cpu | tr "[:upper:]" "[:lower:"]`
|
||||
|
||||
case $cpu in
|
||||
*amd*64* | *x86*64* ) ;;
|
||||
*)
|
||||
echo "Pre-built binary not available for your cpu"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
getURL(){
|
||||
url="https://archive.org/download/grus-v0.1.0/grus-v0.1.0-$os-$cpu"
|
||||
}
|
||||
|
||||
printURL(){
|
||||
echo "You can get the Pre-built binary here:"
|
||||
echo "$url"
|
||||
echo
|
||||
echo "Run these commands to install it on your device."
|
||||
echo "# curl -L -o /usr/local/bin/grus $url"
|
||||
echo "# chmod +x /usr/local/bin/grus"
|
||||
echo
|
||||
echo "This is sha256 hash for grus built for: $os $cpu"
|
||||
case $os in
|
||||
*openbsd* )
|
||||
echo "$openbsdH"
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
echo "Verify the hash by running sha256 on grus binary."
|
||||
echo "$ sha256 /usr/local/bin/grus"
|
||||
}
|
||||
|
||||
echo "Grus v0.1.0"
|
||||
echo
|
||||
earlyCheck
|
||||
getURL
|
||||
printURL
|
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
|
||||
freebsdH="f77989e2d243dc32ff1ab5aa91b48e2abe72a3daba86b781e7905fd4d6a9bd1a"
|
||||
openbsdH="0251e6cecb77aa03ac02d4d67ad9d4cd164d199e1a8a3ef4dd8b711fa43a7298"
|
||||
linuxH="99c35fd048d02fc7136271a7ab2cbe582810d426e2b2bf9687ede6f2b8011960"
|
||||
netbsdH="f686dae688db4dd1a4733ae0be06b588d47f5d8fac6e23dc11f8175d5dcff53c"
|
||||
dragonflyH="c7dbf94b379f9b85956cf09fa015e6eab4ee40dd60fe747cde1c2f15cb172ad2"
|
||||
darwinH="81db0127149b96558ed1d0e556ebe1a653a2e38a0f212fc23257bb9bf0ab2ead"
|
||||
|
||||
earlyCheck(){
|
||||
os=`uname`
|
||||
os=`echo $os | tr "[:upper:]" "[:lower:"]`
|
||||
|
||||
case $os in
|
||||
# Not sure about uname output on DragonFly BSD.
|
||||
*openbsd* | *linux* | *freebsd* | *netbsd* | *dragonfly* | *dragonflybsd* | *darwin* ) ;;
|
||||
*)
|
||||
echo "Pre-built binary not available for your os"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
cpu=`uname -m`
|
||||
cpu=`echo $cpu | tr "[:upper:]" "[:lower:"]`
|
||||
|
||||
case $cpu in
|
||||
*amd*64* | *x86*64* ) ;;
|
||||
*)
|
||||
echo "Pre-built binary not available for your cpu"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
getURL(){
|
||||
url="https://archive.org/download/grus-v0.3.0/grus-v0.3.0-$os-$cpu"
|
||||
}
|
||||
|
||||
printURL(){
|
||||
echo "You can get the pre-built binary here:"
|
||||
echo "$url"
|
||||
echo
|
||||
echo "Run these commands to install it on your device."
|
||||
echo "# curl -L -o /usr/local/bin/grus $url"
|
||||
echo "# chmod +x /usr/local/bin/grus"
|
||||
echo
|
||||
echo "This is sha256 hash for grus built for: $os $cpu"
|
||||
case $os in
|
||||
*openbsd* )
|
||||
echo "$openbsdH"
|
||||
;;
|
||||
*netbsd* )
|
||||
echo "$netbsdH"
|
||||
;;
|
||||
*dragonflybsd* | *dragonfly* )
|
||||
echo "$dragonflyH"
|
||||
;;
|
||||
*darwin* )
|
||||
echo "$darwinH"
|
||||
;;
|
||||
*freebsd* )
|
||||
echo "$freebsdH"
|
||||
;;
|
||||
*linux* )
|
||||
echo "$linuxH"
|
||||
;;
|
||||
esac
|
||||
echo
|
||||
echo "Verify the hash by running sha256 on grus binary."
|
||||
echo "$ sha256 /usr/local/bin/grus"
|
||||
echo
|
||||
}
|
||||
|
||||
echo "Grus v0.3.0"
|
||||
echo
|
||||
earlyCheck
|
||||
getURL
|
||||
printURL
|
|
@ -1,35 +0,0 @@
|
|||
package search
|
||||
|
||||
import "tildegit.org/andinus/grus/storage"
|
||||
|
||||
// Anagrams will search for unjumbled words in database, given sorted
|
||||
// word along with all the anagrams.
|
||||
func Anagrams(sorted string, db *storage.DB) (anagrams []string, err error) {
|
||||
db.Mu.RLock()
|
||||
defer db.Mu.RUnlock()
|
||||
|
||||
stmt, err := db.Conn.Prepare("SELECT word FROM words WHERE sorted = ?")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
rows, err := stmt.Query(sorted)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var word string
|
||||
err = rows.Scan(&word)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
anagrams = append(anagrams, word)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package search
|
||||
|
||||
import "tildegit.org/andinus/grus/storage"
|
||||
|
||||
// Word will search for unjumbled words in database, given sorted word.
|
||||
func Word(sorted string, db *storage.DB) (word string, err error) {
|
||||
db.Mu.RLock()
|
||||
defer db.Mu.RUnlock()
|
||||
|
||||
stmt, err := db.Conn.Prepare("SELECT word FROM words WHERE sorted = ?")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer stmt.Close()
|
||||
|
||||
err = stmt.QueryRow(sorted).Scan(&word)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
// +build darwin
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetDir returns grus data directory. Default data directory on
|
||||
// macOS is $HOME/Library.
|
||||
func GetDir() string {
|
||||
cacheDir := fmt.Sprintf("%s/%s",
|
||||
os.Getenv("HOME"),
|
||||
"Library")
|
||||
|
||||
// Grus cache directory is cacheDir/grus
|
||||
grusCacheDir := fmt.Sprintf("%s/%s", cacheDir,
|
||||
"grus")
|
||||
|
||||
return grusCacheDir
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
// +build linux netbsd openbsd freebsd dragonfly
|
||||
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetDir returns grus data directory. Check if the user has set
|
||||
// XDG_DATA_HOME is set & if that is not set then assume it to be the
|
||||
// default value which is $HOME/.local/share according to XDG Base
|
||||
// Directory Specification.
|
||||
func GetDir() (grusCacheDir string) {
|
||||
cacheDir := SysDir()
|
||||
|
||||
// Grus cache directory is cacheDir/grus.
|
||||
grusCacheDir = fmt.Sprintf("%s/%s", cacheDir,
|
||||
"grus")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SysDir returns the system data directory, this is useful for unveil in
|
||||
// OpenBSD.
|
||||
func SysDir() (cacheDir string) {
|
||||
cacheDir = os.Getenv("XDG_DATA_HOME")
|
||||
if len(cacheDir) == 0 {
|
||||
cacheDir = fmt.Sprintf("%s/%s/%s", os.Getenv("HOME"),
|
||||
".local", "share")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
// initErr will log the error and close the database connection if
|
||||
// necessary.
|
||||
func initErr(db *DB, err error) {
|
||||
if db.Conn != nil {
|
||||
db.Conn.Close()
|
||||
}
|
||||
log.Fatalf("Initialization Error :: %s", err.Error())
|
||||
}
|
||||
|
||||
func initDB(db *DB) {
|
||||
var err error
|
||||
|
||||
db.Path = fmt.Sprintf("%s/grus.db", GetDir())
|
||||
|
||||
db.Conn, err = sql.Open("sqlite3", db.Path)
|
||||
if err != nil {
|
||||
log.Printf("storage/init.go: %s\n",
|
||||
"Failed to open database connection")
|
||||
initErr(db, err)
|
||||
}
|
||||
|
||||
sqlstmt := []string{
|
||||
`CREATE TABLE IF NOT EXISTS words (
|
||||
word TEXT PRIMARY KEY NOT NULL,
|
||||
sorted TEXT NOT NULL);`,
|
||||
`INSERT INTO words(word, lexical)
|
||||
values("grus", "grsu");`,
|
||||
}
|
||||
|
||||
// We range over statements and execute them one by one, this
|
||||
// is during initialization so it doesn't matter if it takes
|
||||
// few more ms. This way we know which statement caused the
|
||||
// program to fail.
|
||||
for _, s := range sqlstmt {
|
||||
stmt, err := db.Conn.Prepare(s)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("storage/init.go: %s\n",
|
||||
"failed to prepare statement")
|
||||
log.Println(s)
|
||||
initErr(db, err)
|
||||
}
|
||||
|
||||
_, err = stmt.Exec()
|
||||
stmt.Close()
|
||||
if err != nil {
|
||||
log.Printf("storage/init.go: %s\n",
|
||||
"failed to execute statement")
|
||||
log.Println(s)
|
||||
initErr(db, err)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// DB holds the database connection, mutex & path.
|
||||
type DB struct {
|
||||
Path string
|
||||
Mu *sync.RWMutex
|
||||
Conn *sql.DB
|
||||
}
|
||||
|
||||
// Init initializes the database.
|
||||
func Init() *DB {
|
||||
db := DB{
|
||||
Mu: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
initDB(&db)
|
||||
return &db
|
||||
}
|
||||
|
||||
// InitConn initializes database connection.
|
||||
func InitConn() *DB {
|
||||
var err error
|
||||
db := DB{
|
||||
Mu: new(sync.RWMutex),
|
||||
}
|
||||
|
||||
db.Path = fmt.Sprintf("%s/grus.db", GetDir())
|
||||
|
||||
db.Conn, err = sql.Open("sqlite3", db.Path)
|
||||
if err != nil {
|
||||
log.Printf("storage/init.go: %s\n",
|
||||
"Failed to open database connection")
|
||||
initErr(&db, err)
|
||||
}
|
||||
|
||||
return &db
|
||||
}
|
Loading…
Reference in New Issue