summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndinus <andinus@nand.sh>2020-04-06 22:11:15 +0530
committerAndinus <andinus@nand.sh>2020-04-06 22:11:15 +0530
commitda2255557fa395eeabeff9a4f2949582f8811980 (patch)
treec48de12ff76b54f67303585190773e6551609f1d
parent69e919cd4e887a606dc1c007c2ce1f36b35477d1 (diff)
downloadgrus-da2255557fa395eeabeff9a4f2949582f8811980.tar.gz
Initial grus version
-rw-r--r--cmd/grus/env.go17
-rw-r--r--cmd/grus/grus.go58
-rw-r--r--cmd/grus/main_openbsd.go40
-rw-r--r--cmd/grus/main_other.go7
-rw-r--r--cmd/grus/usage.go10
-rw-r--r--go.sum8
-rw-r--r--storage/getdir_unix.go37
-rw-r--r--storage/init.go63
-rw-r--r--storage/storage.go44
9 files changed, 284 insertions, 0 deletions
diff --git a/cmd/grus/env.go b/cmd/grus/env.go
new file mode 100644
index 0000000..4d72030
--- /dev/null
+++ b/cmd/grus/env.go
@@ -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
+}
diff --git a/cmd/grus/grus.go b/cmd/grus/grus.go
new file mode 100644
index 0000000..a68db97
--- /dev/null
+++ b/cmd/grus/grus.go
@@ -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)
+}
diff --git a/cmd/grus/main_openbsd.go b/cmd/grus/main_openbsd.go
new file mode 100644
index 0000000..5222bbe
--- /dev/null
+++ b/cmd/grus/main_openbsd.go
@@ -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)
+	}
+}
diff --git a/cmd/grus/main_other.go b/cmd/grus/main_other.go
new file mode 100644
index 0000000..88824ad
--- /dev/null
+++ b/cmd/grus/main_other.go
@@ -0,0 +1,7 @@
+// +build !openbsd
+
+package main
+
+func main() {
+	grus()
+}
diff --git a/cmd/grus/usage.go b/cmd/grus/usage.go
new file mode 100644
index 0000000..1749ba7
--- /dev/null
+++ b/cmd/grus/usage.go
@@ -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")
+}
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..ef77d7c
--- /dev/null
+++ b/go.sum
@@ -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=
diff --git a/storage/getdir_unix.go b/storage/getdir_unix.go
new file mode 100644
index 0000000..29bedbe
--- /dev/null
+++ b/storage/getdir_unix.go
@@ -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
+}
diff --git a/storage/init.go b/storage/init.go
new file mode 100644
index 0000000..9894c5b
--- /dev/null
+++ b/storage/init.go
@@ -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)
+		}
+	}
+}
diff --git a/storage/storage.go b/storage/storage.go
new file mode 100644
index 0000000..9aa1a57
--- /dev/null
+++ b/storage/storage.go
@@ -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
+}