aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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
-rw-r--r--main.go (renamed from cmd/atlas.go)125
-rw-r--r--makefile4
-rw-r--r--pkg/data/update.go2
-rw-r--r--pkg/index/filters.go4
-rw-r--r--pkg/index/index.go9
-rw-r--r--pkg/query/errors.go2
-rw-r--r--pkg/query/parser.go5
-rw-r--r--pkg/server/server.go8
-rw-r--r--pkg/server/unix.go89
15 files changed, 341 insertions, 89 deletions
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")
diff --git a/cmd/atlas.go b/main.go
index 422218b..e847a67 100644
--- a/cmd/atlas.go
+++ b/main.go
@@ -1,35 +1,25 @@
package main
import (
- "errors"
"flag"
"fmt"
"io"
- "io/fs"
"log/slog"
+ "maps"
"os"
- "runtime"
+ "slices"
"strings"
- "time"
- "github.com/adrg/xdg"
+ "github.com/jpappel/atlas/cmd"
"github.com/jpappel/atlas/pkg/data"
"github.com/jpappel/atlas/pkg/query"
"github.com/jpappel/atlas/pkg/shell"
+ "github.com/jpappel/atlas/pkg/util"
)
-const VERSION = "0.0.1"
+const VERSION = "0.4.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()
@@ -40,57 +30,30 @@ func addGlobalFlagUsage(fs *flag.FlagSet) func() {
}
}
-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)")
+ globalFlags := cmd.GlobalFlags{}
+ cmd.SetupGlobalFlags(flag.CommandLine, &globalFlags)
indexFs := flag.NewFlagSet("index", flag.ExitOnError)
queryFs := flag.NewFlagSet("query", flag.ExitOnError)
shellFs := flag.NewFlagSet("debug", flag.ExitOnError)
serverFs := flag.NewFlagSet("server", flag.ExitOnError)
+ completionsFs := flag.NewFlagSet("completions", flag.ContinueOnError)
- indexFs.Usage = addGlobalFlagUsage(indexFs)
- queryFs.Usage = addGlobalFlagUsage(queryFs)
+ // set default usage for flagsets without subcommands
shellFs.Usage = addGlobalFlagUsage(shellFs)
serverFs.Usage = addGlobalFlagUsage(serverFs)
flag.Parse()
args := flag.Args()
- queryFlags := QueryFlags{Outputer: query.DefaultOutput{}}
- indexFlags := IndexFlags{}
- serverFlags := ServerFlags{Port: 8080}
+ queryFlags := cmd.QueryFlags{Outputer: query.DefaultOutput{}}
+ indexFlags := cmd.IndexFlags{}
+ serverFlags := cmd.ServerFlags{Port: 8080}
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "No Command provided")
- printHelp()
+ cmd.PrintHelp()
fmt.Fprintln(flag.CommandLine.Output(), "\nGlobal Flags:")
flag.PrintDefaults()
os.Exit(ExitCommand)
@@ -99,20 +62,30 @@ func main() {
switch command {
case "query", "q":
- setupQueryFlags(args[1:], queryFs, &queryFlags, globalFlags.DateFormat)
- case "index":
- setupIndexFlags(args[1:], indexFs, &indexFlags)
+ cmd.SetupQueryFlags(args[1:], queryFs, &queryFlags, globalFlags.DateFormat)
+ case "index", "i":
+ cmd.SetupIndexFlags(args[1:], indexFs, &indexFlags)
case "server":
- setupServerFlags(args[1:], serverFs, &serverFlags)
+ cmd.SetupServerFlags(args[1:], serverFs, &serverFlags)
+ case "completions":
+ completionsFs.Parse(args[1:])
case "help":
- printHelp()
+ cmd.PrintHelp()
flag.PrintDefaults()
return
case "shell":
shellFs.Parse(args[1:])
default:
fmt.Fprintln(os.Stderr, "Unrecognized command: ", command)
- printHelp()
+ suggestedCommand, ok := util.Nearest(
+ command,
+ slices.Collect(maps.Keys(cmd.CommandHelp)),
+ util.LevensteinDistance, 3,
+ )
+ if ok {
+ fmt.Fprintf(os.Stderr, "Did you mean %s?\n\n", suggestedCommand)
+ }
+ cmd.PrintHelp()
os.Exit(ExitCommand)
}
@@ -132,9 +105,26 @@ func main() {
fmt.Fprintln(os.Stderr, "Unrecognized log level:", globalFlags.LogLevel)
os.Exit(ExitCommand)
}
+
+ var logFile *os.File
+ var err error
+ switch globalFlags.LogFile {
+ case "":
+ logFile = os.Stderr
+ case "-":
+ logFile = os.Stdout
+ default:
+ logFile, err = os.Create(globalFlags.LogFile)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Cannot use log file `%s`: %s", globalFlags.LogFile, err)
+ os.Exit(1)
+ }
+ defer logFile.Close()
+ }
+
var logHandler slog.Handler
if globalFlags.LogJson {
- logHandler = slog.NewJSONHandler(os.Stderr, loggerOpts)
+ logHandler = slog.NewJSONHandler(logFile, loggerOpts)
} else {
// strip time
loggerOpts.ReplaceAttr = func(groups []string, a slog.Attr) slog.Attr {
@@ -143,7 +133,7 @@ func main() {
}
return a
}
- logHandler = slog.NewTextHandler(os.Stderr, loggerOpts)
+ logHandler = slog.NewTextHandler(logFile, loggerOpts)
}
logger := slog.New(logHandler)
slog.SetDefault(logger)
@@ -155,11 +145,22 @@ func main() {
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))
+ exitCode = int(cmd.RunQuery(globalFlags, queryFlags, querier, searchQuery))
+ case "index", "i":
+ exitCode = int(cmd.RunIndex(globalFlags, indexFlags, querier))
case "server":
- exitCode = int(runServer(serverFlags, querier))
+ exitCode = int(cmd.RunServer(serverFlags, querier))
+ case "completions":
+ lang := completionsFs.Arg(0)
+ switch lang {
+ case "zsh":
+ cmd.ZshCompletions()
+ default:
+ fmt.Fprintf(os.Stderr, "Unrecognized completion language `%s`\n", lang)
+ fmt.Fprintf(os.Stderr, "Usage %s completions <language>\n", os.Args[0])
+ fmt.Fprintln(os.Stderr, "Supported languages: zsh")
+ exitCode = 2
+ }
case "shell":
state := make(shell.State)
env := make(map[string]string)
diff --git a/makefile b/makefile
index c778a21..718df92 100644
--- a/makefile
+++ b/makefile
@@ -1,5 +1,5 @@
BINS := atlas
-SRC := $(wildcard cmd/*.go) $(wildcard pkg/*/*.go)
+SRC := main.go $(wildcard cmd/*.go) $(wildcard pkg/*/*.go)
INSTALL_PATH := ~/.local/bin
.PHONY: all install uninstall test info clean
@@ -7,7 +7,7 @@ INSTALL_PATH := ~/.local/bin
all: $(BINS)
atlas: $(SRC)
- go build -o $@ $(wildcard ./cmd/*.go)
+ go build -o $@ $<
test:
go test ./...
diff --git a/pkg/data/update.go b/pkg/data/update.go
index a8a4b3d..1a50563 100644
--- a/pkg/data/update.go
+++ b/pkg/data/update.go
@@ -201,6 +201,7 @@ func (u *UpdateMany) documents() (bool, error) {
SELECT path FROM temp.updateDocs
)`)
if err != nil {
+ slog.Debug("Failed to remove missing files from index")
return false, err
}
@@ -215,6 +216,7 @@ func (u *UpdateMany) documents() (bool, error) {
WHERE excluded.fileTime > Documents.fileTime
`)
if err != nil {
+ slog.Debug("Failed document upsert")
return false, err
}
diff --git a/pkg/index/filters.go b/pkg/index/filters.go
index 920d5df..bd9df54 100644
--- a/pkg/index/filters.go
+++ b/pkg/index/filters.go
@@ -50,10 +50,10 @@ func ParseFilter(s string) (DocFilter, error) {
}
return NewMaxFilesizeFilter(size), nil
case "ExcludeName", "ExcludeFilename":
- // FIXME: support escaped commas
+ // TODO: support escaped commas
return NewExcludeFilenameFilter(strings.Split(param, ",")), nil
case "IncludeName", "IncludeFilename":
- // FIXME: support escaped commas
+ // TODO: support escaped commas
return NewIncludeFilenameFilter(strings.Split(param, ",")), nil
case "ExcludeParent":
return NewExcludeParentFilter(param), nil
diff --git a/pkg/index/index.go b/pkg/index/index.go
index cfa4138..50f642e 100644
--- a/pkg/index/index.go
+++ b/pkg/index/index.go
@@ -12,6 +12,7 @@ import (
"slices"
"strings"
"sync"
+ "sync/atomic"
"time"
"github.com/goccy/go-yaml"
@@ -383,21 +384,23 @@ func ParseDoc(path string, opts ParseOpts) (*Document, error) {
return doc, nil
}
-func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) map[string]*Document {
+func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) (map[string]*Document, uint64) {
jobs := make(chan string, numWorkers)
results := make(chan *Document, numWorkers)
docs := make(map[string]*Document, len(paths))
wg := &sync.WaitGroup{}
+ errCnt := &atomic.Uint64{}
wg.Add(int(numWorkers))
for range numWorkers {
go func(jobs <-chan string, results chan<- *Document, wg *sync.WaitGroup) {
for path := range jobs {
doc, err := ParseDoc(path, opts)
if err != nil {
- slog.Error("Error occured while parsing file",
+ slog.Warn("Error occured while parsing file",
slog.String("path", path), slog.String("err", err.Error()),
)
+ errCnt.Add(1)
continue
}
@@ -423,7 +426,7 @@ func ParseDocs(paths []string, numWorkers uint, opts ParseOpts) map[string]*Docu
docs[doc.Path] = doc
}
- return docs
+ return docs, errCnt.Load()
}
func init() {
diff --git a/pkg/query/errors.go b/pkg/query/errors.go
index 35f8c19..889d40d 100644
--- a/pkg/query/errors.go
+++ b/pkg/query/errors.go
@@ -6,7 +6,7 @@ import (
)
var ErrQueryFormat = errors.New("Incorrect query format")
-var ErrDatetimeTokenParse = errors.New("Unrecognized format for datetime token")
+var ErrDatetimeTokenParse = errors.New("Unrecognized format for datetime")
// output errors
var ErrUnrecognizedOutputToken = errors.New("Unrecognized output token")
diff --git a/pkg/query/parser.go b/pkg/query/parser.go
index e22b4c3..53681f6 100644
--- a/pkg/query/parser.go
+++ b/pkg/query/parser.go
@@ -549,7 +549,10 @@ func Parse(tokens []Token) (*Clause, error) {
var t time.Time
var err error
if t, err = util.ParseDateTime(token.Value); err != nil {
- return nil, ErrDatetimeTokenParse
+ return nil, fmt.Errorf("Cannot parse time `%s`, %v",
+ token.Value,
+ ErrDatetimeTokenParse,
+ )
}
clause.Statements[len(clause.Statements)-1].Value = DatetimeValue{t}
diff --git a/pkg/server/server.go b/pkg/server/server.go
index c08d5a4..a7a5395 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -2,6 +2,7 @@ package server
import (
"bytes"
+ "context"
"io"
"log/slog"
"net/http"
@@ -14,6 +15,11 @@ import (
"github.com/jpappel/atlas/pkg/query"
)
+type Server interface {
+ ListenAndServe() error
+ Shutdown(context.Context) error
+}
+
func info(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`
<h1>Atlas Server</h1>
@@ -22,7 +28,7 @@ func info(w http.ResponseWriter, r *http.Request) {
`))
}
-func New(db *data.Query) *http.ServeMux {
+func NewMux(db *data.Query) *http.ServeMux {
mux := http.NewServeMux()
outputBufPool := &sync.Pool{}
diff --git a/pkg/server/unix.go b/pkg/server/unix.go
new file mode 100644
index 0000000..87b425e
--- /dev/null
+++ b/pkg/server/unix.go
@@ -0,0 +1,89 @@
+package server
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "log/slog"
+ "net"
+ "os"
+ "sync"
+
+ "github.com/jpappel/atlas/pkg/data"
+)
+
+// datagram based unix server
+type UnixServer struct {
+ Addr string
+ Db *data.Query
+ shouldClose chan struct{}
+ bufPool *sync.Pool
+}
+
+func (s *UnixServer) ListenAndServe() error {
+ slog.Info("Listening on", slog.String("addr", s.Addr))
+ conn, err := net.ListenUnixgram(
+ "unixgram",
+ &net.UnixAddr{Name: s.Addr, Net: "Unix"},
+ )
+ if err != nil {
+ return err
+ }
+ defer conn.Close()
+ defer os.RemoveAll(s.Addr)
+ slog.Debug("Accepted connection")
+
+ // slog.Info("New Connection",
+ // slog.String("addr", conn.RemoteAddr().String()),
+ // )
+
+ s.bufPool = &sync.Pool{}
+ s.bufPool.New = func() any {
+ return &bytes.Buffer{}
+ }
+ s.handleConn(conn)
+
+ return nil
+}
+
+func (s UnixServer) handleConn(conn *net.UnixConn) {
+ // buf, ok := s.bufPool.Get().(*bytes.Buffer)
+ // if !ok {
+ // panic("Expected *bytes.Buffer in pool")
+ // }
+ buf := make([]byte, 1024)
+
+ n, err := conn.Read(buf)
+ if err != nil {
+ panic(err)
+ }
+ buf = buf[:n]
+
+ fmt.Println(string(buf))
+
+ // if _, err := io.Copy(buf, conn); err != nil {
+ // panic(err)
+ // }
+ // defer buf.Reset()
+ //
+ // io.Copy(os.Stdout, buf)
+
+ // artifact, err := query.Compile(buf.String(), 0, 1)
+ // if err != nil {
+ // panic(err)
+ // }
+ //
+ // docs, err := s.Db.Execute(artifact)
+ // if err != nil {
+ // panic(err)
+ // }
+ //
+ // _, err = query.DefaultOutput{}.OutputTo(conn, slices.Collect(maps.Values(docs)))
+ // if err != nil {
+ // panic(err)
+ // }
+}
+
+func (s *UnixServer) Shutdown(ctx context.Context) error {
+ return nil
+}