1
0
Fork 0

Compare commits

...

19 Commits

Author SHA1 Message Date
Andinus 593a8ef24e
Update to lynx v0.4.0
continuous-integration/drone/push Build is passing Details
2020-04-17 18:41:09 +05:30
Andinus d5b95cbec7
Add drone ci config 2020-04-09 02:44:57 +05:30
Andinus 72b3740cd3
Add v0.3.0 install instructions to readme 2020-04-08 18:25:36 +05:30
Andinus fc6a3c01be
Update install script 2020-04-08 18:24:22 +05:30
Andinus 47a94e3f4e
Re-organize readme 2020-04-08 18:12:10 +05:30
Andinus ca23fd7f48
Bump grus version to v0.3.0 2020-04-08 18:08:41 +05:30
Andinus 5243a7c955
Use stricter pledge promises if possible 2020-04-08 18:00:26 +05:30
Andinus e80b9a2c4a
Rethink grus design & add GRUS_STRICT_UNJUMBLE 2020-04-08 17:48:21 +05:30
Andinus cd94bf2bdd
Add GRUS_PRINT_PATH variable & bump version to v0.2.1 2020-04-08 15:43:11 +05:30
Andinus 9fc666f608
Add demo video to readme 2020-04-08 13:57:57 +05:30
Andinus 832ac493c7
Fix dict path in readme 2020-04-08 05:21:56 +05:30
Andinus ef6aa9a902
Add installation instructions to readme 2020-04-08 05:11:14 +05:30
Andinus a2f0bec9c5
Add install script 2020-04-08 05:10:28 +05:30
Andinus fa68976a3e
Initial rewrite of grus 2020-04-08 04:37:48 +05:30
Andinus a3ede2f5ed
Explain the rewrite in readme 2020-04-08 01:48:01 +05:30
Andinus 34adb3a7e6
Prepare for next rewrite 2020-04-08 01:44:15 +05:30
Andinus 47d22337b3
Remove drone-ci config 2020-04-08 00:08:30 +05:30
Andinus 1cd23814d9
Add tests for Sort and SlowSort
continuous-integration/drone/push Build is passing Details
2020-04-07 01:34:17 +05:30
Andinus 137618c299
Add installation instructions to readme
continuous-integration/drone/push Build is passing Details
2020-04-07 01:22:29 +05:30
22 changed files with 404 additions and 491 deletions

View File

@ -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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
View File

@ -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
View File

@ -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=

19
lexical/slowsort_test.go Normal file
View File

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

19
lexical/sort_test.go Normal file
View File

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

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,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.")

View File

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

View File

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

78
scripts/install.sh Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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