1
0
Fork 0

Initial grus version

This commit is contained in:
Andinus 2020-04-06 22:11:15 +05:30
parent 69e919cd4e
commit da2255557f
Signed by: andinus
GPG Key ID: B67D55D482A799FD
9 changed files with 284 additions and 0 deletions

17
cmd/grus/env.go Normal file
View File

@ -0,0 +1,17 @@
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
}

58
cmd/grus/grus.go Normal file
View File

@ -0,0 +1,58 @@
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)
out, err := search.Word(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)
}
fmt.Println(out)
}

40
cmd/grus/main_openbsd.go Normal file
View File

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

7
cmd/grus/main_other.go Normal file
View File

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

10
cmd/grus/usage.go Normal file
View File

@ -0,0 +1,10 @@
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")
}

8
go.sum Normal file
View File

@ -0,0 +1,8 @@
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=

37
storage/getdir_unix.go Normal file
View File

@ -0,0 +1,37 @@
// +build linux netbsd openbsd freebsd dragonfly
package storage
import (
"fmt"
"os"
)
// GetDir returns grus data directory. Check if the user has set
// GRUS_DIR, if not then check if 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() string {
cacheDir := SysDir()
// Grus cache directory is cacheDir/grus.
grusCacheDir := fmt.Sprintf("%s/%s", cacheDir,
"grus")
return grusCacheDir
}
// SysDir returns the system data directory, this is useful for unveil in
// OpenBSD.
func SysDir() string {
cacheDir := os.Getenv("GRUS_DIR")
if len(cacheDir) == 0 {
cacheDir = os.Getenv("XDG_DATA_HOME")
}
if len(cacheDir) == 0 {
cacheDir = fmt.Sprintf("%s/%s/%s", os.Getenv("HOME"),
".local", "share")
}
return cacheDir
}

63
storage/init.go Normal file
View File

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

44
storage/storage.go Normal file
View File

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