aboutsummaryrefslogtreecommitdiffstats
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/atlas.go181
-rw-r--r--cmd/cmd.go44
-rw-r--r--cmd/completions.go7
-rw-r--r--cmd/help.go29
-rw-r--r--cmd/index.go25
-rw-r--r--cmd/query.go50
-rw-r--r--cmd/server.go27
7 files changed, 165 insertions, 198 deletions
diff --git a/cmd/atlas.go b/cmd/atlas.go
deleted file mode 100644
index 422218b..0000000
--- a/cmd/atlas.go
+++ /dev/null
@@ -1,181 +0,0 @@
-package main
-
-import (
- "errors"
- "flag"
- "fmt"
- "io"
- "io/fs"
- "log/slog"
- "os"
- "runtime"
- "strings"
- "time"
-
- "github.com/adrg/xdg"
- "github.com/jpappel/atlas/pkg/data"
- "github.com/jpappel/atlas/pkg/query"
- "github.com/jpappel/atlas/pkg/shell"
-)
-
-const VERSION = "0.0.1"
-const ExitCommand = 2 // exit because of a command parsing error
-
-type GlobalFlags struct {
- IndexRoot string
- DBPath string
- LogLevel string
- LogJson bool
- NumWorkers uint
- DateFormat string
-}
-
-func addGlobalFlagUsage(fs *flag.FlagSet) func() {
- return func() {
- f := fs.Output()
- fmt.Fprintln(f, "Usage of", fs.Name())
- fs.PrintDefaults()
- fmt.Fprintln(f, "\nGlobal Flags:")
- flag.PrintDefaults()
- }
-}
-
-func printHelp() {
- fmt.Println("atlas is a note indexing and querying tool")
- fmt.Printf("\nUsage:\n %s [global-flags] <command>\n\n", os.Args[0])
- fmt.Println("Commands:")
- fmt.Println(" index - build, update, or modify an index")
- fmt.Println(" query - search against an index")
- fmt.Println(" shell - start a debug shell")
- fmt.Println(" server - start an http query server (EXPERIMENTAL)")
- fmt.Println(" help - print this help then exit")
-}
-
-func main() {
- home, _ := os.UserHomeDir()
- dataHome := xdg.DataHome
- if dataHome == "" {
- dataHome = strings.Join([]string{home, ".local", "share"}, string(os.PathSeparator))
- }
- dataHome += string(os.PathSeparator) + "atlas"
- if err := os.Mkdir(dataHome, 0755); errors.Is(err, fs.ErrExist) {
- } else if err != nil {
- panic(err)
- }
-
- globalFlags := GlobalFlags{}
- flag.StringVar(&globalFlags.IndexRoot, "root", xdg.UserDirs.Documents, "root `directory` for indexing")
- flag.StringVar(&globalFlags.DBPath, "db", dataHome+string(os.PathSeparator)+"default.db", "`path` to document database")
- flag.StringVar(&globalFlags.LogLevel, "logLevel", "error", "set log `level` (debug, info, warn, error)")
- flag.BoolVar(&globalFlags.LogJson, "logJson", false, "log to json")
- flag.UintVar(&globalFlags.NumWorkers, "numWorkers", uint(runtime.NumCPU()), "number of worker threads to use (defaults to core count)")
- flag.StringVar(&globalFlags.DateFormat, "dateFormat", time.RFC3339, "format for dates (see https://pkg.go.dev/time#Layout for more details)")
-
- indexFs := flag.NewFlagSet("index", flag.ExitOnError)
- queryFs := flag.NewFlagSet("query", flag.ExitOnError)
- shellFs := flag.NewFlagSet("debug", flag.ExitOnError)
- serverFs := flag.NewFlagSet("server", flag.ExitOnError)
-
- indexFs.Usage = addGlobalFlagUsage(indexFs)
- queryFs.Usage = addGlobalFlagUsage(queryFs)
- shellFs.Usage = addGlobalFlagUsage(shellFs)
- serverFs.Usage = addGlobalFlagUsage(serverFs)
-
- flag.Parse()
- args := flag.Args()
-
- queryFlags := QueryFlags{Outputer: query.DefaultOutput{}}
- indexFlags := IndexFlags{}
- serverFlags := ServerFlags{Port: 8080}
-
- if len(args) < 1 {
- fmt.Fprintln(os.Stderr, "No Command provided")
- printHelp()
- fmt.Fprintln(flag.CommandLine.Output(), "\nGlobal Flags:")
- flag.PrintDefaults()
- os.Exit(ExitCommand)
- }
- command := args[0]
-
- switch command {
- case "query", "q":
- setupQueryFlags(args[1:], queryFs, &queryFlags, globalFlags.DateFormat)
- case "index":
- setupIndexFlags(args[1:], indexFs, &indexFlags)
- case "server":
- setupServerFlags(args[1:], serverFs, &serverFlags)
- case "help":
- printHelp()
- flag.PrintDefaults()
- return
- case "shell":
- shellFs.Parse(args[1:])
- default:
- fmt.Fprintln(os.Stderr, "Unrecognized command: ", command)
- printHelp()
- os.Exit(ExitCommand)
- }
-
- slogLevel := &slog.LevelVar{}
- loggerOpts := &slog.HandlerOptions{Level: slogLevel}
- switch globalFlags.LogLevel {
- case "debug":
- slogLevel.Set(slog.LevelDebug)
- loggerOpts.AddSource = true
- case "info":
- slogLevel.Set(slog.LevelInfo)
- case "warn":
- slogLevel.Set(slog.LevelWarn)
- case "error":
- slogLevel.Set(slog.LevelError)
- default:
- fmt.Fprintln(os.Stderr, "Unrecognized log level:", globalFlags.LogLevel)
- os.Exit(ExitCommand)
- }
- var logHandler slog.Handler
- if globalFlags.LogJson {
- logHandler = slog.NewJSONHandler(os.Stderr, loggerOpts)
- } else {
- // strip time
- loggerOpts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr {
- if a.Key == slog.TimeKey && len(groups) == 0 {
- return slog.Attr{}
- }
- return a
- }
- logHandler = slog.NewTextHandler(os.Stderr, loggerOpts)
- }
- logger := slog.New(logHandler)
- slog.SetDefault(logger)
-
- querier := data.NewQuery(globalFlags.DBPath)
-
- // command specific
- var exitCode int
- switch command {
- case "query", "q":
- searchQuery := strings.Join(queryFs.Args(), " ")
- exitCode = int(runQuery(globalFlags, queryFlags, querier, searchQuery))
- case "index":
- exitCode = int(runIndex(globalFlags, indexFlags, querier))
- case "server":
- exitCode = int(runServer(serverFlags, querier))
- case "shell":
- state := make(shell.State)
- env := make(map[string]string)
-
- env["workers"] = fmt.Sprint(globalFlags.NumWorkers)
- env["db_path"] = globalFlags.DBPath
- env["index_root"] = globalFlags.IndexRoot
- env["version"] = VERSION
-
- interpreter := shell.NewInterpreter(state, env, globalFlags.NumWorkers, querier)
- if err := interpreter.Run(); err != nil && err != io.EOF {
- slog.Error("Fatal error occured", slog.String("err", err.Error()))
- exitCode = 1
- }
- }
-
- querier.Close()
- os.Exit(exitCode)
-}
diff --git a/cmd/cmd.go b/cmd/cmd.go
new file mode 100644
index 0000000..5a4f942
--- /dev/null
+++ b/cmd/cmd.go
@@ -0,0 +1,44 @@
+package cmd
+
+import (
+ "errors"
+ "flag"
+ "io/fs"
+ "os"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/adrg/xdg"
+)
+
+type GlobalFlags struct {
+ IndexRoot string
+ DBPath string
+ LogLevel string
+ LogJson bool
+ NumWorkers uint
+ DateFormat string
+ LogFile string
+}
+
+func SetupGlobalFlags(fs_ *flag.FlagSet, flags *GlobalFlags) {
+ home, _ := os.UserHomeDir()
+ dataHome := xdg.DataHome
+ if dataHome == "" {
+ dataHome = strings.Join([]string{home, ".local", "share"}, string(os.PathSeparator))
+ }
+ dataHome += string(os.PathSeparator) + "atlas"
+ if err := os.Mkdir(dataHome, 0755); errors.Is(err, fs.ErrExist) {
+ } else if err != nil {
+ panic(err)
+ }
+
+ flag.StringVar(&flags.IndexRoot, "root", xdg.UserDirs.Documents, "root `directory` for indexing")
+ flag.StringVar(&flags.DBPath, "db", dataHome+string(os.PathSeparator)+"default.db", "`path` to document database")
+ flag.StringVar(&flags.LogLevel, "logLevel", "error", "set log `level` (debug, info, warn, error)")
+ flag.BoolVar(&flags.LogJson, "logJson", false, "log to json")
+ flag.UintVar(&flags.NumWorkers, "numWorkers", uint(runtime.NumCPU()), "number of worker threads to use (defaults to core count)")
+ flag.StringVar(&flags.DateFormat, "dateFormat", time.RFC3339, "`format` for dates (see https://pkg.go.dev/time#Layout for more details)")
+ flag.StringVar(&flags.LogFile, "logFile", "", "`file` to log errors to, use '-' for stdout and empty for stderr")
+}
diff --git a/cmd/completions.go b/cmd/completions.go
new file mode 100644
index 0000000..e169952
--- /dev/null
+++ b/cmd/completions.go
@@ -0,0 +1,7 @@
+package cmd
+
+import "fmt"
+
+func ZshCompletions() {
+ fmt.Println("Not implemented yet!")
+}
diff --git a/cmd/help.go b/cmd/help.go
new file mode 100644
index 0000000..b3844ac
--- /dev/null
+++ b/cmd/help.go
@@ -0,0 +1,29 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+)
+
+var CommandHelp map[string]string
+
+func PrintHelp() {
+ fmt.Println("atlas is a note indexing and querying tool")
+ fmt.Printf("\nUsage:\n %s [global-flags] <command>\n\n", os.Args[0])
+ fmt.Println("Commands:")
+ fmt.Println(" index - build, update, or modify an index")
+ fmt.Println(" query - search against an index")
+ fmt.Println(" shell - start a debug shell")
+ fmt.Println(" server - start an http query server (EXPERIMENTAL)")
+ fmt.Println(" help - print this help then exit")
+}
+
+func init() {
+ CommandHelp = make(map[string]string)
+ CommandHelp["query"] = ""
+ CommandHelp["index"] = ""
+ CommandHelp["server"] = ""
+ CommandHelp["completions"] = ""
+ CommandHelp["shell"] = ""
+ CommandHelp["help"] = ""
+}
diff --git a/cmd/index.go b/cmd/index.go
index dde9550..5454a8a 100644
--- a/cmd/index.go
+++ b/cmd/index.go
@@ -1,4 +1,4 @@
-package main
+package cmd
import (
"context"
@@ -18,7 +18,7 @@ type IndexFlags struct {
index.ParseOpts
}
-func setupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) {
+func SetupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) {
flags.ParseLinks = true
flags.ParseMeta = true
fs.BoolVar(&flags.IgnoreDateError, "ignoreBadDates", false, "ignore malformed dates while indexing")
@@ -77,7 +77,7 @@ func setupIndexFlags(args []string, fs *flag.FlagSet, flags *IndexFlags) {
}
}
-func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte {
+func RunIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte {
switch iFlags.Subcommand {
case "build", "update":
@@ -99,19 +99,27 @@ func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte {
filteredFiles := idx.Filter(traversedFiles, gFlags.NumWorkers)
fmt.Print(", Filtered ", len(filteredFiles))
- idx.Documents = index.ParseDocs(filteredFiles, gFlags.NumWorkers, iFlags.ParseOpts)
+ var errCnt uint64
+ idx.Documents, errCnt = index.ParseDocs(filteredFiles, gFlags.NumWorkers, iFlags.ParseOpts)
fmt.Print(", Parsed ", len(idx.Documents), "\n")
+ if errCnt > 0 {
+ fmt.Printf("Encountered %d document parse errors", errCnt)
+ if !slog.Default().Enabled(context.Background(), slog.LevelWarn) {
+ fmt.Print(" (set log level to warn for more info)")
+ }
+ fmt.Println()
+ }
var err error
// switch in order to appease gopls...
switch iFlags.Subcommand {
- case "index":
+ case "build":
err = db.Put(idx)
case "update":
err = db.Update(idx)
}
if err != nil {
- fmt.Fprintln(os.Stderr, err)
+ fmt.Fprintln(os.Stderr, "Error modifying index:", err)
return 1
}
case "tidy":
@@ -120,9 +128,12 @@ func runIndex(gFlags GlobalFlags, iFlags IndexFlags, db *data.Query) byte {
return 1
}
default:
- fmt.Fprintln(os.Stderr, "Unrecognised index subcommands: ", iFlags.Subcommand)
+ fmt.Fprintln(os.Stderr, "Unrecognized index subcommands: ", iFlags.Subcommand)
return 2
}
return 0
}
+
+func init() {
+}
diff --git a/cmd/query.go b/cmd/query.go
index 649b9ea..d00a792 100644
--- a/cmd/query.go
+++ b/cmd/query.go
@@ -1,9 +1,12 @@
-package main
+package cmd
import (
"flag"
"fmt"
+ "log/slog"
"os"
+ "slices"
+ "strings"
"github.com/jpappel/atlas/pkg/data"
"github.com/jpappel/atlas/pkg/index"
@@ -16,9 +19,11 @@ type QueryFlags struct {
ListSeparator string
CustomFormat string
OptimizationLevel int
+ SortBy string
+ SortDesc bool
}
-func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFormat string) {
+func SetupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFormat string) {
// NOTE: providing `-outFormat` before `-outCustomFormat` might ignore user specified format
fs.Func("outFormat", "output `format` for queries (default, json, pathonly, custom)",
func(arg string) error {
@@ -39,6 +44,9 @@ func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFor
}
return fmt.Errorf("Unrecognized output format: %s", arg)
})
+
+ fs.StringVar(&flags.SortBy, "sortBy", "", "category to sort by (path,title,date,filetime,meta)")
+ fs.BoolVar(&flags.SortDesc, "sortDesc", false, "sort in descending order")
fs.StringVar(&flags.CustomFormat, "outCustomFormat", query.DefaultOutputFormat, "format string for --outFormat custom, see Output Format for more details")
fs.IntVar(&flags.OptimizationLevel, "optLevel", 0, "optimization `level` for queries, 0 is automatic, <0 to disable")
fs.StringVar(&flags.DocumentSeparator, "docSeparator", "\n", "separator for custom output format")
@@ -81,7 +89,7 @@ func setupQueryFlags(args []string, fs *flag.FlagSet, flags *QueryFlags, dateFor
fs.Parse(args)
}
-func runQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery string) byte {
+func RunQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery string) byte {
tokens := query.Lex(searchQuery)
clause, err := query.Parse(tokens)
if err != nil {
@@ -114,6 +122,42 @@ func runQuery(gFlags GlobalFlags, qFlags QueryFlags, db *data.Query, searchQuery
outputableResults = append(outputableResults, v)
}
+ var docCmp func(a, b *index.Document) int
+ descMod := 1
+ if qFlags.SortDesc {
+ descMod = -1
+ }
+ switch qFlags.SortBy {
+ case "":
+ case "path":
+ docCmp = func(a, b *index.Document) int {
+ return descMod * strings.Compare(a.Path, b.Path)
+ }
+ case "title":
+ docCmp = func(a, b *index.Document) int {
+ return descMod * strings.Compare(a.Title, b.Title)
+ }
+ case "date":
+ docCmp = func(a, b *index.Document) int {
+ return descMod * a.Date.Compare(b.Date)
+ }
+ case "filetime":
+ docCmp = func(a, b *index.Document) int {
+ return descMod * a.FileTime.Compare(b.FileTime)
+ }
+ case "meta":
+ docCmp = func(a, b *index.Document) int {
+ return descMod * strings.Compare(a.OtherMeta, b.OtherMeta)
+ }
+ default:
+ slog.Error("Unrecognized category to sort by, leaving documents unsorted")
+ qFlags.SortBy = ""
+ }
+
+ if qFlags.SortBy != "" {
+ slices.SortFunc(outputableResults, docCmp)
+ }
+
_, err = qFlags.Outputer.OutputTo(os.Stdout, outputableResults)
if err != nil {
fmt.Fprintln(os.Stderr, "Error while outputting results: ", err)
diff --git a/cmd/server.go b/cmd/server.go
index 21f4661..be072cf 100644
--- a/cmd/server.go
+++ b/cmd/server.go
@@ -1,4 +1,4 @@
-package main
+package cmd
import (
"context"
@@ -8,6 +8,7 @@ import (
"net/http"
"os"
"os/signal"
+ "strings"
"syscall"
"time"
@@ -20,17 +21,26 @@ type ServerFlags struct {
Port int
}
-func setupServerFlags(args []string, fs *flag.FlagSet, flags *ServerFlags) {
- fs.StringVar(&flags.Address, "address", "", "the address to listen on")
+func SetupServerFlags(args []string, fs *flag.FlagSet, flags *ServerFlags) {
+ fs.StringVar(&flags.Address, "address", "", "the address to listen on, prefix with 'unix:' to create a unixsocket")
fs.IntVar(&flags.Port, "port", 8080, "the port to bind to")
fs.Parse(args)
}
-func runServer(sFlags ServerFlags, db *data.Query) byte {
- addr := fmt.Sprintf("%s:%d", sFlags.Address, sFlags.Port)
+func RunServer(sFlags ServerFlags, db *data.Query) byte {
- s := http.Server{Addr: addr, Handler: server.New(db)}
+ var addr string
+ var s server.Server
+ if after, ok := strings.CutPrefix(sFlags.Address, "unix:"); ok {
+ slog.Debug("Preparing unix domain socket")
+ addr = after
+ s = &server.UnixServer{Addr: addr, Db: db}
+ } else {
+ slog.Debug("Preparing http server")
+ addr = fmt.Sprintf("%s:%d", sFlags.Address, sFlags.Port)
+ s = &http.Server{Addr: addr, Handler: server.NewMux(db)}
+ }
serverErrors := make(chan error, 1)
exit := make(chan os.Signal, 1)
@@ -42,13 +52,16 @@ func runServer(sFlags ServerFlags, db *data.Query) byte {
if err := s.ListenAndServe(); err != nil {
serverErrors <- err
}
+ close(serverErrors)
}(serverErrors)
select {
case <-exit:
slog.Info("Recieved signal to shutdown")
case err := <-serverErrors:
- slog.Error("Server error", slog.String("err", err.Error()))
+ if err != nil {
+ slog.Error("Server error", slog.String("err", err.Error()))
+ }
}
slog.Info("Shutting down server")