From da2255557fa395eeabeff9a4f2949582f8811980 Mon Sep 17 00:00:00 2001 From: Andinus Date: Mon, 6 Apr 2020 22:11:15 +0530 Subject: Initial grus version --- cmd/grus/env.go | 17 +++++++++++++ cmd/grus/grus.go | 58 ++++++++++++++++++++++++++++++++++++++++++++ cmd/grus/main_openbsd.go | 40 ++++++++++++++++++++++++++++++ cmd/grus/main_other.go | 7 ++++++ cmd/grus/usage.go | 10 ++++++++ go.sum | 8 ++++++ storage/getdir_unix.go | 37 ++++++++++++++++++++++++++++ storage/init.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ storage/storage.go | 44 +++++++++++++++++++++++++++++++++ 9 files changed, 284 insertions(+) create mode 100644 cmd/grus/env.go create mode 100644 cmd/grus/grus.go create mode 100644 cmd/grus/main_openbsd.go create mode 100644 cmd/grus/main_other.go create mode 100644 cmd/grus/usage.go create mode 100644 go.sum create mode 100644 storage/getdir_unix.go create mode 100644 storage/init.go create mode 100644 storage/storage.go 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 / ") + 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 +} -- cgit 1.4.1-2-gfad0