From f81ce78e6fdc6b852b32c5a3da1cca5978c3a345 Mon Sep 17 00:00:00 2001 From: chaos Date: Tue, 19 Nov 2024 19:37:54 +0000 Subject: [PATCH] update --- Cargo.toml | 4 + gojq-extended/.gitignore | 1 - gojq-extended/LICENSE | 22 -- gojq-extended/LICENSE.orig | 21 -- gojq-extended/cli.go | 476 ------------------------------------- gojq-extended/color.go | 64 ----- gojq-extended/encoder.go | 267 --------------------- gojq-extended/error.go | 225 ------------------ gojq-extended/flags.go | 211 ---------------- gojq-extended/go.mod | 15 -- gojq-extended/go.sum | 14 -- gojq-extended/inputs.go | 375 ----------------------------- gojq-extended/marshaler.go | 27 --- gojq-extended/run.go | 13 - gojq-extended/stream.go | 113 --------- journal/src/helpers.rs | 111 +++++++++ journal/src/lib.rs | 3 +- journal/src/types.rs | 150 ++++++++++++ journal/src/types/mod.rs | 9 - journal/src/types/types.rs | 74 ------ journal_cli/src/args.rs | 9 + journal_cli/src/main.rs | 49 ++++ src/main.rs | 17 -- 23 files changed, 325 insertions(+), 1945 deletions(-) delete mode 100644 gojq-extended/.gitignore delete mode 100644 gojq-extended/LICENSE delete mode 100644 gojq-extended/LICENSE.orig delete mode 100644 gojq-extended/cli.go delete mode 100644 gojq-extended/color.go delete mode 100644 gojq-extended/encoder.go delete mode 100644 gojq-extended/error.go delete mode 100644 gojq-extended/flags.go delete mode 100644 gojq-extended/go.mod delete mode 100644 gojq-extended/go.sum delete mode 100644 gojq-extended/inputs.go delete mode 100644 gojq-extended/marshaler.go delete mode 100644 gojq-extended/run.go delete mode 100644 gojq-extended/stream.go create mode 100644 journal/src/helpers.rs create mode 100644 journal/src/types.rs delete mode 100644 journal/src/types/mod.rs delete mode 100644 journal/src/types/types.rs create mode 100644 journal_cli/src/args.rs create mode 100644 journal_cli/src/main.rs delete mode 100644 src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 6bddf08..9b04141 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,10 @@ name = "psychonaut_journal_cli" version = "0.1.0" edition = "2021" +[[bin]] +name = "journal-cli" +path = "journal_cli/src/main.rs" + [dependencies] journal = { path = "./journal" } chrono = { version = "0.4.38", features = ["serde"] } diff --git a/gojq-extended/.gitignore b/gojq-extended/.gitignore deleted file mode 100644 index 86adbca..0000000 --- a/gojq-extended/.gitignore +++ /dev/null @@ -1 +0,0 @@ -gojq-extended \ No newline at end of file diff --git a/gojq-extended/LICENSE b/gojq-extended/LICENSE deleted file mode 100644 index 8dacc6c..0000000 --- a/gojq-extended/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2024 chaos -Copyright (c) 2019-2024 itchyny - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/gojq-extended/LICENSE.orig b/gojq-extended/LICENSE.orig deleted file mode 100644 index fe59004..0000000 --- a/gojq-extended/LICENSE.orig +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2019-2024 itchyny - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/gojq-extended/cli.go b/gojq-extended/cli.go deleted file mode 100644 index 06e774f..0000000 --- a/gojq-extended/cli.go +++ /dev/null @@ -1,476 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "io" - "os" - "runtime" - "strings" - - "github.com/mattn/go-isatty" - - "github.com/itchyny/gojq" -) - -const name = "gojq" - -const version = "0.12.16" - -var revision = "HEAD" - -const ( - exitCodeOK = iota - exitCodeFalsyErr - exitCodeFlagParseErr - exitCodeCompileErr - exitCodeNoValueErr - exitCodeDefaultErr -) - -type cli struct { - inStream io.Reader - outStream io.Writer - errStream io.Writer - - outputRaw bool - outputRaw0 bool - outputJoin bool - outputCompact bool - outputIndent *int - outputTab bool - inputRaw bool - inputStream bool - inputSlurp bool - - argnames []string - argvalues []any - - exitCodeError error -} - -type flagopts struct { - OutputRaw bool `short:"r" long:"raw-output" description:"output raw strings"` - OutputRaw0 bool `long:"raw-output0" description:"implies -r with NUL character delimiter"` - OutputJoin bool `short:"j" long:"join-output" description:"implies -r with no newline delimiter"` - OutputCompact bool `short:"c" long:"compact-output" description:"output without pretty-printing"` - OutputIndent *int `long:"indent" description:"number of spaces for indentation"` - OutputTab bool `long:"tab" description:"use tabs for indentation"` - OutputColor bool `short:"C" long:"color-output" description:"output with colors even if piped"` - OutputMono bool `short:"M" long:"monochrome-output" description:"output without colors"` - InputNull bool `short:"n" long:"null-input" description:"use null as input value"` - InputRaw bool `short:"R" long:"raw-input" description:"read input as raw strings"` - InputStream bool `long:"stream" description:"parse input in stream fashion"` - InputSlurp bool `short:"s" long:"slurp" description:"read all inputs into an array"` - FromFile bool `short:"f" long:"from-file" description:"load query from file"` - ModulePaths []string `short:"L" description:"directory to search modules from"` - Arg map[string]string `long:"arg" description:"set a string value to a variable"` - ArgJSON map[string]string `long:"argjson" description:"set a JSON value to a variable"` - SlurpFile map[string]string `long:"slurpfile" description:"set the JSON contents of a file to a variable"` - RawFile map[string]string `long:"rawfile" description:"set the contents of a file to a variable"` - Args []any `long:"args" positional:"" description:"consume remaining arguments as positional string values"` - JSONArgs []any `long:"jsonargs" positional:"" description:"consume remaining arguments as positional JSON values"` - ExitStatus bool `short:"e" long:"exit-status" description:"exit 1 when the last value is false or null"` - Version bool `short:"v" long:"version" description:"display version information"` - Help bool `short:"h" long:"help" description:"display this help information"` -} - -var addDefaultModulePaths = true - -func (cli *cli) run(args []string) int { - if err := cli.runInternal(args); err != nil { - if _, ok := err.(interface{ isEmptyError() }); !ok { - fmt.Fprintf(cli.errStream, "%s: %s\n", name, err) - } - if err, ok := err.(interface{ ExitCode() int }); ok { - return err.ExitCode() - } - return exitCodeDefaultErr - } - return exitCodeOK -} - -func (cli *cli) runInternal(args []string) (err error) { - var opts flagopts - args, err = parseFlags(args, &opts) - if err != nil { - return &flagParseError{err} - } - if opts.Help { - fmt.Fprintf(cli.outStream, `%[1]s - Go implementation of jq - -Version: %s (rev: %s/%s) - -Synopsis: - %% echo '{"foo": 128}' | %[1]s '.foo' - -Usage: - %[1]s [OPTIONS] - -`, - name, version, revision, runtime.Version()) - fmt.Fprintln(cli.outStream, formatFlags(&opts)) - return nil - } - if opts.Version { - fmt.Fprintf(cli.outStream, "%s %s (rev: %s/%s)\n", name, version, revision, runtime.Version()) - return nil - } - cli.outputRaw, cli.outputRaw0, cli.outputJoin, - cli.outputCompact, cli.outputIndent, cli.outputTab = - opts.OutputRaw, opts.OutputRaw0, opts.OutputJoin, - opts.OutputCompact, opts.OutputIndent, opts.OutputTab - defer func(x bool) { noColor = x }(noColor) - if opts.OutputColor || opts.OutputMono { - noColor = opts.OutputMono - } else if os.Getenv("NO_COLOR") != "" || os.Getenv("TERM") == "dumb" { - noColor = true - } else { - f, ok := cli.outStream.(interface{ Fd() uintptr }) - noColor = !(ok && (isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd()))) - } - if !noColor { - if colors := os.Getenv("GOJQ_COLORS"); colors != "" { - if err := setColors(colors); err != nil { - return err - } - } - } - if i := cli.outputIndent; i != nil { - if *i > 9 { - return fmt.Errorf("too many indentation count: %d", *i) - } else if *i < 0 { - return fmt.Errorf("negative indentation count: %d", *i) - } - } - - cli.inputRaw, cli.inputStream, cli.inputSlurp = - opts.InputRaw, opts.InputStream, opts.InputSlurp - for k, v := range opts.Arg { - cli.argnames = append(cli.argnames, "$"+k) - cli.argvalues = append(cli.argvalues, v) - } - for k, v := range opts.ArgJSON { - val, _ := newJSONInputIter(strings.NewReader(v), "$"+k).Next() - if err, ok := val.(error); ok { - return err - } - cli.argnames = append(cli.argnames, "$"+k) - cli.argvalues = append(cli.argvalues, val) - } - for k, v := range opts.SlurpFile { - val, err := slurpFile(v) - if err != nil { - return err - } - cli.argnames = append(cli.argnames, "$"+k) - cli.argvalues = append(cli.argvalues, val) - } - for k, v := range opts.RawFile { - val, err := os.ReadFile(v) - if err != nil { - return err - } - cli.argnames = append(cli.argnames, "$"+k) - cli.argvalues = append(cli.argvalues, string(val)) - } - named := make(map[string]any, len(cli.argnames)) - for i, name := range cli.argnames { - named[name[1:]] = cli.argvalues[i] - } - positional := opts.Args - for i, v := range opts.JSONArgs { - if v != nil { - val, _ := newJSONInputIter(strings.NewReader(v.(string)), "--jsonargs").Next() - if err, ok := val.(error); ok { - return err - } - if i < len(positional) { - positional[i] = val - } else { - positional = append(positional, val) - } - } - } - cli.argnames = append(cli.argnames, "$ARGS") - cli.argvalues = append(cli.argvalues, map[string]any{ - "named": named, - "positional": positional, - }) - var arg, fname string - if opts.FromFile { - if len(args) == 0 { - return errors.New("expected a query file for flag `-f'") - } - src, err := os.ReadFile(args[0]) - if err != nil { - return err - } - arg, args, fname = string(src), args[1:], args[0] - } else if len(args) == 0 { - arg = "." - } else { - arg, args, fname = strings.TrimSpace(args[0]), args[1:], "" - } - if opts.ExitStatus { - cli.exitCodeError = &exitCodeError{exitCodeNoValueErr} - defer func() { - if _, ok := err.(interface{ ExitCode() int }); !ok { - err = cli.exitCodeError - } - }() - } - query, err := gojq.Parse(arg) - if err != nil { - return &queryParseError{fname, arg, err} - } - modulePaths := opts.ModulePaths - if len(modulePaths) == 0 && addDefaultModulePaths { - modulePaths = []string{"~/.jq", "$ORIGIN/../lib/gojq", "$ORIGIN/../lib"} - } - iter := cli.createInputIter(args) - defer iter.Close() - code, err := gojq.Compile(query, - gojq.WithModuleLoader(gojq.NewModuleLoader(modulePaths)), - gojq.WithEnvironLoader(os.Environ), - gojq.WithVariables(cli.argnames), - gojq.WithFunction("debug", 0, 0, - func(errStream io.Writer) func(any, []any) any { - indent := 2 - if cli.outputCompact { - indent = 0 - } else if cli.outputTab { - indent = 1 - } else if i := cli.outputIndent; i != nil { - indent = *i - } - - return func(v any, _ []any) any { - if err := newEncoder(false, indent). - marshal([]any{"DEBUG:", v}, cli.errStream); err != nil { - return err - } - if _, err := cli.errStream.Write([]byte{'\n'}); err != nil { - return err - } - return v - } - }(cli.errStream), - ), - - - gojq.WithFunction("stderr", 0, 0, - func(errStream io.Writer) func(any, []any) any { - return func(v any, _ []any) any { - if err := (&rawMarshaler{m: newEncoder(false, 0)}). - marshal(v, cli.errStream); err != nil { - return err - } - return v - } - }(cli.errStream), - ), - gojq.WithFunction("_readFile", 1, 1, - func(_input any, args []any) any { - filename := args[0].(string) - - info, err := os.Stat(filename) - if err != nil { - return fmt.Errorf("file not found") - } - - if info.IsDir() { - return fmt.Errorf("file is a directory") - } - - content, err := os.ReadFile(filename) - if err != nil { - return fmt.Errorf("file could not be read") - } - return string(content) - }, - ), - gojq.WithFunction("_writeFileString", 1, 1, - func(input any, args []any) any { - filename := args[0].(string) - content := "" - - switch input := input.(type) { - case string: - content = input - default: - return fmt.Errorf("invalid type passed to _writeFileString") - } - - file, err := os.Create(filename) - if err != nil { - return err - } - - _, err = file.Write([]byte(content)) - if err != nil { - return err - } - err = file.Close() - if err != nil { - return err - } - - return nil - }, - ), - gojq.WithFunction("input_filename", 0, 0, - func(iter inputIter) func(any, []any) any { - return func(any, []any) any { - if fname := iter.Name(); fname != "" && (len(args) > 0 || !opts.InputNull) { - return fname - } - return nil - } - }(iter), - ), - gojq.WithInputIter(iter), - ) - if err != nil { - if err, ok := err.(interface { - QueryParseError() (string, string, error) - }); ok { - name, query, err := err.QueryParseError() - return &queryParseError{name, query, err} - } - if err, ok := err.(interface { - JSONParseError() (string, string, error) - }); ok { - fname, contents, err := err.JSONParseError() - return &compileError{&jsonParseError{fname, contents, 0, err}} - } - return &compileError{err} - } - if opts.InputNull { - iter = newNullInputIter() - } - return cli.process(iter, code) -} - -func slurpFile(name string) (any, error) { - iter := newSlurpInputIter( - newFilesInputIter(newJSONInputIter, []string{name}, nil), - ) - defer iter.Close() - val, _ := iter.Next() - if err, ok := val.(error); ok { - return nil, err - } - return val, nil -} - -func (cli *cli) createInputIter(args []string) (iter inputIter) { - var newIter func(io.Reader, string) inputIter - switch { - case cli.inputRaw: - if cli.inputSlurp { - newIter = newReadAllIter - } else { - newIter = newRawInputIter - } - case cli.inputStream: - newIter = newStreamInputIter - default: - newIter = newJSONInputIter - } - if cli.inputSlurp { - defer func() { - if cli.inputRaw { - iter = newSlurpRawInputIter(iter) - } else { - iter = newSlurpInputIter(iter) - } - }() - } - if len(args) == 0 { - return newIter(cli.inStream, "") - } - return newFilesInputIter(newIter, args, cli.inStream) -} - -func (cli *cli) process(iter inputIter, code *gojq.Code) error { - var err error - for { - v, ok := iter.Next() - if !ok { - break - } - if e, ok := v.(error); ok { - fmt.Fprintf(cli.errStream, "%s: %s\n", name, e) - err = e - continue - } - if e := cli.printValues(code.Run(v, cli.argvalues...)); e != nil { - if e, ok := e.(*gojq.HaltError); ok { - if v := e.Value(); v != nil { - if str, ok := v.(string); ok { - cli.errStream.Write([]byte(str)) - } else { - bs, _ := gojq.Marshal(v) - cli.errStream.Write(bs) - cli.errStream.Write([]byte{'\n'}) - } - } - err = e - break - } - fmt.Fprintf(cli.errStream, "%s: %s\n", name, e) - err = e - } - } - if err != nil { - return &emptyError{err} - } - return nil -} - -func (cli *cli) printValues(iter gojq.Iter) error { - m := cli.createMarshaler() - for { - v, ok := iter.Next() - if !ok { - break - } - if err, ok := v.(error); ok { - return err - } - - if err := m.marshal(v, cli.outStream); err != nil { - return err - } - if cli.exitCodeError != nil { - if v == nil || v == false { - cli.exitCodeError = &exitCodeError{exitCodeFalsyErr} - } else { - cli.exitCodeError = &exitCodeError{exitCodeOK} - } - } - if cli.outputRaw0 { - cli.outStream.Write([]byte{'\x00'}) - } else if !cli.outputJoin { - cli.outStream.Write([]byte{'\n'}) - } - } - return nil -} - -func (cli *cli) createMarshaler() marshaler { - indent := 2 - if cli.outputCompact { - indent = 0 - } else if cli.outputTab { - indent = 1 - } else if i := cli.outputIndent; i != nil { - indent = *i - } - f := newEncoder(cli.outputTab, indent) - if cli.outputRaw || cli.outputRaw0 || cli.outputJoin { - return &rawMarshaler{f, cli.outputRaw0} - } - return f -} diff --git a/gojq-extended/color.go b/gojq-extended/color.go deleted file mode 100644 index 4f08a40..0000000 --- a/gojq-extended/color.go +++ /dev/null @@ -1,64 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "strings" -) - -var noColor bool - -func newColor(c string) []byte { - return []byte("\x1b[" + c + "m") -} - -func setColor(buf *bytes.Buffer, color []byte) { - if !noColor { - buf.Write(color) - } -} - -var ( - resetColor = newColor("0") // Reset - nullColor = newColor("90") // Bright black - falseColor = newColor("33") // Yellow - trueColor = newColor("33") // Yellow - numberColor = newColor("36") // Cyan - stringColor = newColor("32") // Green - objectKeyColor = newColor("34;1") // Bold Blue - arrayColor = []byte(nil) // No color - objectColor = []byte(nil) // No color -) - -func validColor(x string) bool { - var num bool - for _, c := range x { - if '0' <= c && c <= '9' { - num = true - } else if c == ';' && num { - num = false - } else { - return false - } - } - return num -} - -func setColors(colors string) error { - var color string - for _, target := range []*[]byte{ - &nullColor, &falseColor, &trueColor, &numberColor, - &stringColor, &objectKeyColor, &arrayColor, &objectColor, - } { - color, colors, _ = strings.Cut(colors, ":") - if color != "" { - if !validColor(color) { - return fmt.Errorf("invalid color: %q", color) - } - *target = newColor(color) - } else { - *target = nil - } - } - return nil -} diff --git a/gojq-extended/encoder.go b/gojq-extended/encoder.go deleted file mode 100644 index a96ae21..0000000 --- a/gojq-extended/encoder.go +++ /dev/null @@ -1,267 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "io" - "math" - "math/big" - "sort" - "strconv" - "unicode/utf8" -) - -type encoder struct { - out io.Writer - w *bytes.Buffer - tab bool - indent int - depth int - buf [64]byte -} - -func newEncoder(tab bool, indent int) *encoder { - // reuse the buffer in multiple calls of marshal - return &encoder{w: new(bytes.Buffer), tab: tab, indent: indent} -} - -func (e *encoder) flush() error { - _, err := e.out.Write(e.w.Bytes()) - e.w.Reset() - return err -} - -func (e *encoder) marshal(v any, w io.Writer) error { - e.out = w - err := e.encode(v) - if ferr := e.flush(); ferr != nil && err == nil { - err = ferr - } - return err -} - -func (e *encoder) encode(v any) error { - switch v := v.(type) { - case nil: - e.write([]byte("null"), nullColor) - case bool: - if v { - e.write([]byte("true"), trueColor) - } else { - e.write([]byte("false"), falseColor) - } - case int: - e.write(strconv.AppendInt(e.buf[:0], int64(v), 10), numberColor) - case float64: - e.encodeFloat64(v) - case *big.Int: - e.write(v.Append(e.buf[:0], 10), numberColor) - case string: - e.encodeString(v, stringColor) - case []any: - if err := e.encodeArray(v); err != nil { - return err - } - case map[string]any: - if err := e.encodeObject(v); err != nil { - return err - } - default: - panic(fmt.Sprintf("invalid type: %[1]T (%[1]v)", v)) - } - if e.w.Len() > 8*1024 { - return e.flush() - } - return nil -} - -// ref: floatEncoder in encoding/json -func (e *encoder) encodeFloat64(f float64) { - if math.IsNaN(f) { - e.write([]byte("null"), nullColor) - return - } - f = min(max(f, -math.MaxFloat64), math.MaxFloat64) - format := byte('f') - if x := math.Abs(f); x != 0 && x < 1e-6 || x >= 1e21 { - format = 'e' - } - buf := strconv.AppendFloat(e.buf[:0], f, format, -1, 64) - if format == 'e' { - // clean up e-09 to e-9 - if n := len(buf); n >= 4 && buf[n-4] == 'e' && buf[n-3] == '-' && buf[n-2] == '0' { - buf[n-2] = buf[n-1] - buf = buf[:n-1] - } - } - e.write(buf, numberColor) -} - -// ref: encodeState#string in encoding/json -func (e *encoder) encodeString(s string, color []byte) { - if color != nil { - setColor(e.w, color) - } - e.w.WriteByte('"') - start := 0 - for i := 0; i < len(s); { - if b := s[i]; b < utf8.RuneSelf { - if ' ' <= b && b <= '~' && b != '"' && b != '\\' { - i++ - continue - } - if start < i { - e.w.WriteString(s[start:i]) - } - switch b { - case '"': - e.w.WriteString(`\"`) - case '\\': - e.w.WriteString(`\\`) - case '\b': - e.w.WriteString(`\b`) - case '\f': - e.w.WriteString(`\f`) - case '\n': - e.w.WriteString(`\n`) - case '\r': - e.w.WriteString(`\r`) - case '\t': - e.w.WriteString(`\t`) - default: - const hex = "0123456789abcdef" - e.w.WriteString(`\u00`) - e.w.WriteByte(hex[b>>4]) - e.w.WriteByte(hex[b&0xF]) - } - i++ - start = i - continue - } - c, size := utf8.DecodeRuneInString(s[i:]) - if c == utf8.RuneError && size == 1 { - if start < i { - e.w.WriteString(s[start:i]) - } - e.w.WriteString(`\ufffd`) - i += size - start = i - continue - } - i += size - } - if start < len(s) { - e.w.WriteString(s[start:]) - } - e.w.WriteByte('"') - if color != nil { - setColor(e.w, resetColor) - } -} - -func (e *encoder) encodeArray(vs []any) error { - e.writeByte('[', arrayColor) - e.depth += e.indent - for i, v := range vs { - if i > 0 { - e.writeByte(',', arrayColor) - } - if e.indent != 0 { - e.writeIndent() - } - if err := e.encode(v); err != nil { - return err - } - } - e.depth -= e.indent - if len(vs) > 0 && e.indent != 0 { - e.writeIndent() - } - e.writeByte(']', arrayColor) - return nil -} - -func (e *encoder) encodeObject(vs map[string]any) error { - e.writeByte('{', objectColor) - e.depth += e.indent - type keyVal struct { - key string - val any - } - kvs := make([]keyVal, len(vs)) - var i int - for k, v := range vs { - kvs[i] = keyVal{k, v} - i++ - } - sort.Slice(kvs, func(i, j int) bool { - return kvs[i].key < kvs[j].key - }) - for i, kv := range kvs { - if i > 0 { - e.writeByte(',', objectColor) - } - if e.indent != 0 { - e.writeIndent() - } - e.encodeString(kv.key, objectKeyColor) - e.writeByte(':', objectColor) - if e.indent != 0 { - e.w.WriteByte(' ') - } - if err := e.encode(kv.val); err != nil { - return err - } - } - e.depth -= e.indent - if len(vs) > 0 && e.indent != 0 { - e.writeIndent() - } - e.writeByte('}', objectColor) - return nil -} - -func (e *encoder) writeIndent() { - e.w.WriteByte('\n') - if n := e.depth; n > 0 { - if e.tab { - e.writeIndentInternal(n, "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t") - } else { - e.writeIndentInternal(n, " ") - } - } -} - -func (e *encoder) writeIndentInternal(n int, spaces string) { - if l := len(spaces); n <= l { - e.w.WriteString(spaces[:n]) - } else { - e.w.WriteString(spaces) - for n -= l; n > 0; n, l = n-l, l*2 { - if n < l { - l = n - } - e.w.Write(e.w.Bytes()[e.w.Len()-l:]) - } - } -} - -func (e *encoder) writeByte(b byte, color []byte) { - if color == nil { - e.w.WriteByte(b) - } else { - setColor(e.w, color) - e.w.WriteByte(b) - setColor(e.w, resetColor) - } -} - -func (e *encoder) write(bs []byte, color []byte) { - if color == nil { - e.w.Write(bs) - } else { - setColor(e.w, color) - e.w.Write(bs) - setColor(e.w, resetColor) - } -} diff --git a/gojq-extended/error.go b/gojq-extended/error.go deleted file mode 100644 index 0219f78..0000000 --- a/gojq-extended/error.go +++ /dev/null @@ -1,225 +0,0 @@ -package main - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "strconv" - "strings" - "unicode/utf8" - - "github.com/mattn/go-runewidth" - - "github.com/itchyny/gojq" -) - -type emptyError struct { - err error -} - -func (*emptyError) Error() string { - return "" -} - -func (*emptyError) isEmptyError() {} - -func (err *emptyError) ExitCode() int { - if err, ok := err.err.(interface{ ExitCode() int }); ok { - return err.ExitCode() - } - return exitCodeDefaultErr -} - -type exitCodeError struct { - code int -} - -func (err *exitCodeError) Error() string { - return "exit code: " + strconv.Itoa(err.code) -} - -func (*exitCodeError) isEmptyError() {} - -func (err *exitCodeError) ExitCode() int { - return err.code -} - -type flagParseError struct { - err error -} - -func (err *flagParseError) Error() string { - return err.err.Error() -} - -func (*flagParseError) ExitCode() int { - return exitCodeFlagParseErr -} - -type compileError struct { - err error -} - -func (err *compileError) Error() string { - return "compile error: " + err.err.Error() -} - -func (*compileError) ExitCode() int { - return exitCodeCompileErr -} - -type queryParseError struct { - fname, contents string - err error -} - -func (err *queryParseError) Error() string { - var offset int - var e *gojq.ParseError - if errors.As(err.err, &e) { - offset = e.Offset - len(e.Token) + 1 - } - linestr, line, column := getLineByOffset(err.contents, offset) - if err.fname != "" || containsNewline(err.contents) { - return fmt.Sprintf("invalid query: %s:%d\n%s %s", - err.fname, line, formatLineInfo(linestr, line, column), err.err) - } - return fmt.Sprintf("invalid query: %s\n %s\n %*c %s", - err.contents, linestr, column+1, '^', err.err) -} - -func (*queryParseError) ExitCode() int { - return exitCodeCompileErr -} - -type jsonParseError struct { - fname, contents string - line int - err error -} - -func (err *jsonParseError) Error() string { - var offset int - if err.err == io.ErrUnexpectedEOF { - offset = len(err.contents) + 1 - } else if e, ok := err.err.(*json.SyntaxError); ok { - offset = int(e.Offset) - } - linestr, line, column := getLineByOffset(err.contents, offset) - if line += err.line; line > 1 { - return fmt.Sprintf("invalid json: %s:%d\n%s %s", - err.fname, line, formatLineInfo(linestr, line, column), err.err) - } - return fmt.Sprintf("invalid json: %s\n %s\n %*c %s", - err.fname, linestr, column+1, '^', err.err) -} - -func getLineByOffset(str string, offset int) (linestr string, line, column int) { - ss := &stringScanner{str, 0} - for { - str, start, ok := ss.next() - if !ok { - offset -= start - break - } - line++ - linestr = str - if ss.offset >= offset { - offset -= start - break - } - } - offset = min(max(offset-1, 0), len(linestr)) - if offset > 48 { - skip := len(trimLastInvalidRune(linestr[:offset-48])) - linestr = linestr[skip:] - offset -= skip - } - linestr = trimLastInvalidRune(linestr[:min(64, len(linestr))]) - if offset < len(linestr) { - offset = len(trimLastInvalidRune(linestr[:offset])) - } else { - offset = len(linestr) - } - column = runewidth.StringWidth(linestr[:offset]) - return -} - -func getLineByLine(str string, line int) (linestr string) { - ss := &stringScanner{str, 0} - for { - str, _, ok := ss.next() - if !ok { - break - } - if line--; line == 0 { - linestr = str - break - } - } - if len(linestr) > 64 { - linestr = trimLastInvalidRune(linestr[:64]) - } - return -} - -func trimLastInvalidRune(s string) string { - for i := len(s) - 1; i >= 0 && i > len(s)-utf8.UTFMax; i-- { - if b := s[i]; b < utf8.RuneSelf { - return s[:i+1] - } else if utf8.RuneStart(b) { - if r, _ := utf8.DecodeRuneInString(s[i:]); r == utf8.RuneError { - return s[:i] - } - break - } - } - return s -} - -func formatLineInfo(linestr string, line, column int) string { - l := strconv.Itoa(line) - return fmt.Sprintf(" %s | %s\n %*c", l, linestr, column+len(l)+4, '^') -} - -type stringScanner struct { - str string - offset int -} - -func (ss *stringScanner) next() (line string, start int, ok bool) { - if ss.offset == len(ss.str) { - return - } - start, ok = ss.offset, true - line = ss.str[start:] - i := indexNewline(line) - if i < 0 { - ss.offset = len(ss.str) - return - } - line = line[:i] - if strings.HasPrefix(ss.str[start+i:], "\r\n") { - i++ - } - ss.offset += i + 1 - return -} - -// Faster than strings.ContainsAny(str, "\r\n"). -func containsNewline(str string) bool { - return strings.IndexByte(str, '\n') >= 0 || - strings.IndexByte(str, '\r') >= 0 -} - -// Faster than strings.IndexAny(str, "\r\n"). -func indexNewline(str string) (i int) { - if i = strings.IndexByte(str, '\n'); i >= 0 { - str = str[:i] - } - if j := strings.IndexByte(str, '\r'); j >= 0 { - i = j - } - return -} diff --git a/gojq-extended/flags.go b/gojq-extended/flags.go deleted file mode 100644 index 480d004..0000000 --- a/gojq-extended/flags.go +++ /dev/null @@ -1,211 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "strconv" - "strings" -) - -func parseFlags(args []string, opts any) ([]string, error) { - rest := make([]string, 0, len(args)) - val := reflect.ValueOf(opts).Elem() - typ := val.Type() - longToValue := map[string]reflect.Value{} - longToPositional := map[string]struct{}{} - shortToValue := map[string]reflect.Value{} - for i, l := 0, val.NumField(); i < l; i++ { - if flag, ok := typ.Field(i).Tag.Lookup("long"); ok { - longToValue[flag] = val.Field(i) - if _, ok := typ.Field(i).Tag.Lookup("positional"); ok { - longToPositional[flag] = struct{}{} - } - } - if flag, ok := typ.Field(i).Tag.Lookup("short"); ok { - shortToValue[flag] = val.Field(i) - } - } - mapKeys := map[string]struct{}{} - var positionalVal reflect.Value - for i := 0; i < len(args); i++ { - arg := args[i] - var ( - val reflect.Value - ok bool - shortopts string - ) - if arg == "--" { - if positionalVal.IsValid() { - for _, arg := range args[i+1:] { - positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg))) - } - } else { - rest = append(rest, args[i+1:]...) - } - break - } - if strings.HasPrefix(arg, "--") { - if val, ok = longToValue[arg[2:]]; !ok { - if j := strings.IndexByte(arg, '='); j >= 0 { - if val, ok = longToValue[arg[2:j]]; ok { - if val.Kind() == reflect.Bool { - return nil, fmt.Errorf("boolean flag `%s' cannot have an argument", arg[:j]) - } - args[i] = arg[j+1:] - arg = arg[:j] - i-- - } - } - if !ok { - return nil, fmt.Errorf("unknown flag `%s'", arg) - } - } - } else if len(arg) > 1 && arg[0] == '-' { - var skip bool - for i := 1; i < len(arg); i++ { - opt := arg[i : i+1] - if val, ok = shortToValue[opt]; ok { - if val.Kind() != reflect.Bool { - break - } - } else if !("A" <= opt && opt <= "Z" || "a" <= opt && opt <= "z") { - skip = true - break - } - } - if !skip && (len(arg) > 2 || !ok) { - shortopts = arg[1:] - goto L - } - } - if !ok { - if positionalVal.IsValid() && len(rest) > 0 { - positionalVal.Set(reflect.Append(positionalVal, reflect.ValueOf(arg))) - } else { - rest = append(rest, arg) - } - continue - } - S: - switch val.Kind() { - case reflect.Bool: - val.SetBool(true) - case reflect.String: - if i++; i >= len(args) { - return nil, fmt.Errorf("expected argument for flag `%s'", arg) - } - val.SetString(args[i]) - case reflect.Ptr: - if val.Type().Elem().Kind() == reflect.Int { - if i++; i >= len(args) { - return nil, fmt.Errorf("expected argument for flag `%s'", arg) - } - v, err := strconv.Atoi(args[i]) - if err != nil { - return nil, fmt.Errorf("invalid argument for flag `%s': %w", arg, err) - } - val.Set(reflect.New(val.Type().Elem())) - val.Elem().SetInt(int64(v)) - } - case reflect.Slice: - if _, ok := longToPositional[arg[2:]]; ok { - if positionalVal.IsValid() { - for positionalVal.Len() > val.Len() { - val.Set(reflect.Append(val, reflect.Zero(val.Type().Elem()))) - } - } - positionalVal = val - } else { - if i++; i >= len(args) { - return nil, fmt.Errorf("expected argument for flag `%s'", arg) - } - val.Set(reflect.Append(val, reflect.ValueOf(args[i]))) - } - case reflect.Map: - if i += 2; i >= len(args) { - return nil, fmt.Errorf("expected 2 arguments for flag `%s'", arg) - } - if val.IsNil() { - val.Set(reflect.MakeMap(val.Type())) - } - name := args[i-1] - if _, ok := mapKeys[name]; !ok { - mapKeys[name] = struct{}{} - val.SetMapIndex(reflect.ValueOf(name), reflect.ValueOf(args[i])) - } - } - L: - if shortopts != "" { - opt := shortopts[:1] - if val, ok = shortToValue[opt]; !ok { - return nil, fmt.Errorf("unknown flag `%s'", opt) - } - if val.Kind() != reflect.Bool && len(shortopts) > 1 { - if shortopts[1] == '=' { - args[i] = shortopts[2:] - } else { - args[i] = shortopts[1:] - } - i-- - shortopts = "" - } else { - shortopts = shortopts[1:] - } - arg = "-" + opt - goto S - } - } - return rest, nil -} - -func formatFlags(opts any) string { - val := reflect.ValueOf(opts).Elem() - typ := val.Type() - var sb strings.Builder - sb.WriteString("Command Options:\n") - for i, l := 0, typ.NumField(); i < l; i++ { - tag := typ.Field(i).Tag - if i == l-1 { - sb.WriteString("\nHelp Option:\n") - } - sb.WriteString(" ") - var short bool - if flag, ok := tag.Lookup("short"); ok { - sb.WriteString("-") - sb.WriteString(flag) - short = true - } else { - sb.WriteString(" ") - } - m := sb.Len() - if flag, ok := tag.Lookup("long"); ok { - if short { - sb.WriteString(", ") - } else { - sb.WriteString(" ") - } - sb.WriteString("--") - sb.WriteString(flag) - switch val.Field(i).Kind() { - case reflect.Bool: - sb.WriteString(" ") - case reflect.Map: - if strings.HasSuffix(flag, "file") { - sb.WriteString(" name file") - } else { - sb.WriteString(" name value") - } - default: - if _, ok = tag.Lookup("positional"); !ok { - sb.WriteString("=") - } - } - } else { - sb.WriteString("=") - } - sb.WriteString(" "[:24-sb.Len()+m]) - sb.WriteString(tag.Get("description")) - sb.WriteString("\n") - } - return sb.String() -} diff --git a/gojq-extended/go.mod b/gojq-extended/go.mod deleted file mode 100644 index 1f5819a..0000000 --- a/gojq-extended/go.mod +++ /dev/null @@ -1,15 +0,0 @@ -module forgejo.owo.monster/chaos/psychonaut_journal_cli/gojq-extended - -go 1.23.2 - -require ( - github.com/itchyny/gojq v0.12.16 - github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-runewidth v0.0.16 -) - -require ( - github.com/itchyny/timefmt-go v0.1.6 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - golang.org/x/sys v0.20.0 // indirect -) diff --git a/gojq-extended/go.sum b/gojq-extended/go.sum deleted file mode 100644 index c303e1c..0000000 --- a/gojq-extended/go.sum +++ /dev/null @@ -1,14 +0,0 @@ -github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= -github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= -github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= -github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/gojq-extended/inputs.go b/gojq-extended/inputs.go deleted file mode 100644 index 64b2f82..0000000 --- a/gojq-extended/inputs.go +++ /dev/null @@ -1,375 +0,0 @@ -package main - -import ( - "bufio" - "bytes" - "encoding/json" - "io" - "os" - "strings" - - "github.com/itchyny/gojq" -) - -type inputReader struct { - io.Reader - file *os.File - buf *bytes.Buffer -} - -func newInputReader(r io.Reader) *inputReader { - if r, ok := r.(*os.File); ok { - if _, err := r.Seek(0, io.SeekCurrent); err == nil { - return &inputReader{r, r, nil} - } - } - var buf bytes.Buffer // do not use strings.Builder because we need to Reset - return &inputReader{io.TeeReader(r, &buf), nil, &buf} -} - -func (ir *inputReader) getContents(offset *int64, line *int) string { - if buf := ir.buf; buf != nil { - return buf.String() - } - if current, err := ir.file.Seek(0, io.SeekCurrent); err == nil { - defer func() { ir.file.Seek(current, io.SeekStart) }() - } - ir.file.Seek(0, io.SeekStart) - const bufSize = 16 * 1024 - var buf bytes.Buffer // do not use strings.Builder because we need to Reset - if offset != nil && *offset > bufSize { - buf.Grow(bufSize) - for *offset > bufSize { - n, err := io.Copy(&buf, io.LimitReader(ir.file, bufSize)) - *offset -= int64(n) - *line += bytes.Count(buf.Bytes(), []byte{'\n'}) - buf.Reset() - if err != nil || n == 0 { - break - } - } - } - var r io.Reader - if offset == nil { - r = ir.file - } else { - r = io.LimitReader(ir.file, bufSize*2) - } - io.Copy(&buf, r) - return buf.String() -} - -type inputIter interface { - gojq.Iter - io.Closer - Name() string -} - -type jsonInputIter struct { - next func() (any, error) - ir *inputReader - fname string - offset int64 - line int - err error -} - -func newJSONInputIter(r io.Reader, fname string) inputIter { - ir := newInputReader(r) - dec := json.NewDecoder(ir) - dec.UseNumber() - next := func() (v any, err error) { err = dec.Decode(&v); return } - return &jsonInputIter{next: next, ir: ir, fname: fname} -} - -func (i *jsonInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - v, err := i.next() - if err != nil { - if err == io.EOF { - i.err = err - return nil, false - } - var offset *int64 - var line *int - if err, ok := err.(*json.SyntaxError); ok { - err.Offset -= i.offset - offset, line = &err.Offset, &i.line - } - i.err = &jsonParseError{i.fname, i.ir.getContents(offset, line), i.line, err} - return i.err, true - } - if buf := i.ir.buf; buf != nil && buf.Len() >= 16*1024 { - i.offset += int64(buf.Len()) - i.line += bytes.Count(buf.Bytes(), []byte{'\n'}) - buf.Reset() - } - return v, true -} - -func (i *jsonInputIter) Close() error { - i.err = io.EOF - return nil -} - -func (i *jsonInputIter) Name() string { - return i.fname -} - -func newStreamInputIter(r io.Reader, fname string) inputIter { - ir := newInputReader(r) - dec := json.NewDecoder(ir) - dec.UseNumber() - return &jsonInputIter{next: newJSONStream(dec).next, ir: ir, fname: fname} -} - -type nullInputIter struct { - err error -} - -func newNullInputIter() inputIter { - return &nullInputIter{} -} - -func (i *nullInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - i.err = io.EOF - return nil, true -} - -func (i *nullInputIter) Close() error { - i.err = io.EOF - return nil -} - -func (*nullInputIter) Name() string { - return "" -} - -type filesInputIter struct { - newIter func(io.Reader, string) inputIter - fnames []string - stdin io.Reader - iter inputIter - file io.Reader - err error -} - -func newFilesInputIter( - newIter func(io.Reader, string) inputIter, fnames []string, stdin io.Reader, -) inputIter { - return &filesInputIter{newIter: newIter, fnames: fnames, stdin: stdin} -} - -func (i *filesInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - for { - if i.file == nil { - if len(i.fnames) == 0 { - i.err = io.EOF - if i.iter != nil { - i.iter.Close() - i.iter = nil - } - return nil, false - } - fname := i.fnames[0] - i.fnames = i.fnames[1:] - if fname == "-" && i.stdin != nil { - i.file, fname = i.stdin, "" - } else { - file, err := os.Open(fname) - if err != nil { - return err, true - } - i.file = file - } - if i.iter != nil { - i.iter.Close() - } - i.iter = i.newIter(i.file, fname) - } - if v, ok := i.iter.Next(); ok { - return v, ok - } - if r, ok := i.file.(io.Closer); ok && i.file != i.stdin { - r.Close() - } - i.file = nil - } -} - -func (i *filesInputIter) Close() error { - if i.file != nil { - if r, ok := i.file.(io.Closer); ok && i.file != i.stdin { - r.Close() - } - i.file = nil - i.err = io.EOF - } - return nil -} - -func (i *filesInputIter) Name() string { - if i.iter != nil { - return i.iter.Name() - } - return "" -} - -type rawInputIter struct { - r *bufio.Reader - fname string - err error -} - -func newRawInputIter(r io.Reader, fname string) inputIter { - return &rawInputIter{r: bufio.NewReader(r), fname: fname} -} - -func (i *rawInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - line, err := i.r.ReadString('\n') - if err != nil { - i.err = err - if err != io.EOF { - return err, true - } - if line == "" { - return nil, false - } - } - return strings.TrimSuffix(line, "\n"), true -} - -func (i *rawInputIter) Close() error { - i.err = io.EOF - return nil -} - -func (i *rawInputIter) Name() string { - return i.fname -} - -type slurpInputIter struct { - iter inputIter - err error -} - -func newSlurpInputIter(iter inputIter) inputIter { - return &slurpInputIter{iter: iter} -} - -func (i *slurpInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - var vs []any - var v any - var ok bool - for { - v, ok = i.iter.Next() - if !ok { - i.err = io.EOF - return vs, true - } - if i.err, ok = v.(error); ok { - return i.err, true - } - vs = append(vs, v) - } -} - -func (i *slurpInputIter) Close() error { - if i.iter != nil { - i.iter.Close() - i.iter = nil - i.err = io.EOF - } - return nil -} - -func (i *slurpInputIter) Name() string { - return i.iter.Name() -} - -type readAllIter struct { - r io.Reader - fname string - err error -} - -func newReadAllIter(r io.Reader, fname string) inputIter { - return &readAllIter{r: r, fname: fname} -} - -func (i *readAllIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - i.err = io.EOF - cnt, err := io.ReadAll(i.r) - if err != nil { - return err, true - } - return string(cnt), true -} - -func (i *readAllIter) Close() error { - i.err = io.EOF - return nil -} - -func (i *readAllIter) Name() string { - return i.fname -} - -type slurpRawInputIter struct { - iter inputIter - err error -} - -func newSlurpRawInputIter(iter inputIter) inputIter { - return &slurpRawInputIter{iter: iter} -} - -func (i *slurpRawInputIter) Next() (any, bool) { - if i.err != nil { - return nil, false - } - var vs []string - var v any - var ok bool - for { - v, ok = i.iter.Next() - if !ok { - i.err = io.EOF - return strings.Join(vs, ""), true - } - if i.err, ok = v.(error); ok { - return i.err, true - } - vs = append(vs, v.(string)) - } -} - -func (i *slurpRawInputIter) Close() error { - if i.iter != nil { - i.iter.Close() - i.iter = nil - i.err = io.EOF - } - return nil -} - -func (i *slurpRawInputIter) Name() string { - return i.iter.Name() -} diff --git a/gojq-extended/marshaler.go b/gojq-extended/marshaler.go deleted file mode 100644 index 9a85bcf..0000000 --- a/gojq-extended/marshaler.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "fmt" - "io" - "strings" -) - -type marshaler interface { - marshal(any, io.Writer) error -} - -type rawMarshaler struct { - m marshaler - checkNul bool -} - -func (m *rawMarshaler) marshal(v any, w io.Writer) error { - if s, ok := v.(string); ok { - if m.checkNul && strings.ContainsRune(s, '\x00') { - return fmt.Errorf("cannot output a string containing NUL character: %q", s) - } - _, err := w.Write([]byte(s)) - return err - } - return m.m.marshal(v, w) -} diff --git a/gojq-extended/run.go b/gojq-extended/run.go deleted file mode 100644 index 0e09557..0000000 --- a/gojq-extended/run.go +++ /dev/null @@ -1,13 +0,0 @@ -package main - -import "os" - -// Run gojq. -func main() { - cli := cli{ - inStream: os.Stdin, - outStream: os.Stdout, - errStream: os.Stderr, - } - os.Exit(cli.run(os.Args[1:])) -} diff --git a/gojq-extended/stream.go b/gojq-extended/stream.go deleted file mode 100644 index 09e436b..0000000 --- a/gojq-extended/stream.go +++ /dev/null @@ -1,113 +0,0 @@ -package main - -import ( - "encoding/json" - "io" -) - -type jsonStream struct { - dec *json.Decoder - path []any - states []int -} - -func newJSONStream(dec *json.Decoder) *jsonStream { - return &jsonStream{dec: dec, states: []int{jsonStateTopValue}, path: []any{}} -} - -const ( - jsonStateTopValue = iota - jsonStateArrayStart - jsonStateArrayValue - jsonStateArrayEnd - jsonStateArrayEmptyEnd - jsonStateObjectStart - jsonStateObjectKey - jsonStateObjectValue - jsonStateObjectEnd - jsonStateObjectEmptyEnd -) - -func (s *jsonStream) next() (any, error) { - switch s.states[len(s.states)-1] { - case jsonStateArrayEnd, jsonStateObjectEnd: - s.path = s.path[:len(s.path)-1] - fallthrough - case jsonStateArrayEmptyEnd, jsonStateObjectEmptyEnd: - s.states = s.states[:len(s.states)-1] - } - if s.dec.More() { - switch s.states[len(s.states)-1] { - case jsonStateArrayValue: - s.path[len(s.path)-1] = s.path[len(s.path)-1].(int) + 1 - case jsonStateObjectValue: - s.path = s.path[:len(s.path)-1] - } - } - for { - token, err := s.dec.Token() - if err != nil { - if err == io.EOF && s.states[len(s.states)-1] != jsonStateTopValue { - err = io.ErrUnexpectedEOF - } - return nil, err - } - if d, ok := token.(json.Delim); ok { - switch d { - case '[', '{': - switch s.states[len(s.states)-1] { - case jsonStateArrayStart: - s.states[len(s.states)-1] = jsonStateArrayValue - case jsonStateObjectKey: - s.states[len(s.states)-1] = jsonStateObjectValue - } - if d == '[' { - s.states = append(s.states, jsonStateArrayStart) - s.path = append(s.path, 0) - } else { - s.states = append(s.states, jsonStateObjectStart) - } - case ']': - if s.states[len(s.states)-1] == jsonStateArrayStart { - s.states[len(s.states)-1] = jsonStateArrayEmptyEnd - s.path = s.path[:len(s.path)-1] - return []any{s.copyPath(), []any{}}, nil - } - s.states[len(s.states)-1] = jsonStateArrayEnd - return []any{s.copyPath()}, nil - case '}': - if s.states[len(s.states)-1] == jsonStateObjectStart { - s.states[len(s.states)-1] = jsonStateObjectEmptyEnd - return []any{s.copyPath(), map[string]any{}}, nil - } - s.states[len(s.states)-1] = jsonStateObjectEnd - return []any{s.copyPath()}, nil - default: - panic(d) - } - } else { - switch s.states[len(s.states)-1] { - case jsonStateArrayStart: - s.states[len(s.states)-1] = jsonStateArrayValue - fallthrough - case jsonStateArrayValue: - return []any{s.copyPath(), token}, nil - case jsonStateObjectStart, jsonStateObjectValue: - s.states[len(s.states)-1] = jsonStateObjectKey - s.path = append(s.path, token) - case jsonStateObjectKey: - s.states[len(s.states)-1] = jsonStateObjectValue - return []any{s.copyPath(), token}, nil - default: - s.states[len(s.states)-1] = jsonStateTopValue - return []any{s.copyPath(), token}, nil - } - } - } -} - -func (s *jsonStream) copyPath() []any { - path := make([]any, len(s.path)) - copy(path, s.path) - return path -} diff --git a/journal/src/helpers.rs b/journal/src/helpers.rs new file mode 100644 index 0000000..61c0078 --- /dev/null +++ b/journal/src/helpers.rs @@ -0,0 +1,111 @@ +use crate::types::{CustomUnits, CustomUnitsType, Ingestion}; + +pub fn ingestion_dose(ingestion: &Ingestion, custom_units: &CustomUnitsType) -> Option { + if let Some(custom_unit_id) = ingestion.custom_unit_id { + if let Some(ingestion_dose) = ingestion.dose { + let custom_unit = custom_units + .get_by_id(custom_unit_id) + .expect("Custom Unit could not be found"); + + Some(ingestion_dose * custom_unit.dose) + } else { + None + } + } else { + ingestion.dose + } +} + +fn add_standard_deviation( + expectation_x: f64, + standard_deviation_x: f64, + expectation_y: f64, + standard_deviation_y: f64, +) -> Option { + let sum_x = standard_deviation_x.powi(2) + expectation_x.powi(2); + let sum_y = standard_deviation_y.powi(2) + expectation_y.powi(2); + + let expectations = expectation_x.powi(2) + expectation_y.powi(2); + + let product_variance = (sum_x * sum_y) - expectations; + + if product_variance > 0.0000001 { + Some((product_variance.sqrt() * 100.0).round() / 100.0) + } else { + println!( + "{} {} {} {}", + expectation_x, standard_deviation_x, expectation_y, standard_deviation_y, + ); + + None + } +} + +pub fn ingestion_contains_estimate(ingestion: &Ingestion, custom_units: &CustomUnitsType) -> bool { + if ingestion.is_estimate { + return true; + } + + if let Some(custom_unit_id) = ingestion.custom_unit_id { + let custom_unit = custom_units + .get_by_id(custom_unit_id) + .expect("Custom Unit could not be found"); + + custom_unit.is_estimate + } else { + false + } +} + +pub fn ingestion_standard_deviation( + ingestion: &Ingestion, + custom_units: &CustomUnitsType, +) -> Option { + if ingestion.dose.is_none() { + return None; + } + + if !ingestion_contains_estimate(ingestion, custom_units) { + return None; + } + + if let Some(custom_unit_id) = ingestion.custom_unit_id { + let custom_unit = custom_units + .get_by_id(custom_unit_id) + .expect("Custom Unit could not be found"); + + if custom_unit.estimate_standard_deviation.is_none() { + return ingestion.estimate_standard_deviation; + }; + + if ingestion.estimate_standard_deviation.is_none() { + return custom_unit.estimate_standard_deviation; + } + + return add_standard_deviation( + ingestion.dose.unwrap(), + ingestion.estimate_standard_deviation.unwrap(), + custom_unit.dose, + custom_unit.estimate_standard_deviation.unwrap(), + ); + } else { + return ingestion.estimate_standard_deviation; + } +} + +// def ingestionStandardDeviation($customUnits): +// . as $ingestion | +// journalTypes::ensureIngestion | + +// if .customUnitId != null then +// ($customUnits | map(select(.id == $ingestion.customUnitId))[0]) as $customUnit | + +// ($ingestion.dose // 0) as $expectationX | +// ($ingestion.estimatedDoseStandardDeviation // 0) as $standardDeviationX | +// ($customUnit.dose // 0) as $expectationY | +// ($customUnit.estimatedDoseStandardDeviation // 0) as $standardDeviationY | + +// addStandardDeviations($expectationX; $standardDeviationX; $expectationY; $standardDeviationY) +// else +// .estimatedDoseStandardDeviation +// end; diff --git a/journal/src/lib.rs b/journal/src/lib.rs index dd198c6..5b5f982 100644 --- a/journal/src/lib.rs +++ b/journal/src/lib.rs @@ -1 +1,2 @@ -pub mod types; \ No newline at end of file +pub mod types; +pub mod helpers; \ No newline at end of file diff --git a/journal/src/types.rs b/journal/src/types.rs new file mode 100644 index 0000000..fe67227 --- /dev/null +++ b/journal/src/types.rs @@ -0,0 +1,150 @@ +use chrono::serde::ts_milliseconds; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::fmt::Display; + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "UPPERCASE")] +pub enum AdministrationRoute { + Oral, + Sublingual, + Buccal, + Insufflated, + Rectal, + Transdermal, + Subcutaneous, + Intramuscular, + Intravenous, + Smoked, + Inhaled, +} + +impl Display for AdministrationRoute { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Ingestion { + pub substance_name: String, + #[serde(with = "ts_milliseconds", rename = "time")] + pub ingestion_time: DateTime, + #[serde(with = "ts_milliseconds", rename = "creationDate")] + pub creation_time: DateTime, + pub dose: Option, + #[serde(rename = "isDoseAnEstimate")] + pub is_estimate: bool, + #[serde(rename = "estimatedDoseStandardDeviation")] + pub estimate_standard_deviation: Option, + pub units: String, + pub custom_unit_id: Option, + #[serde(rename = "administrationRoute")] + pub roa: AdministrationRoute, + pub notes: String, + pub stomach_fullness: Option, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CustomSubstance { + pub name: String, + pub description: String, + pub units: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct Experience { + pub title: String, + pub text: String, + #[serde(with = "ts_milliseconds", rename = "creationDate")] + pub creation_time: DateTime, + #[serde(with = "ts_milliseconds", rename = "sortDate")] + pub modified_time: DateTime, + pub ingestions: Vec, +} + +pub type ExperiencesType = Vec; + +pub trait Experiences { + fn filter_by_title(&self, title: String) -> Vec; + fn get_by_title(&self, title: String) -> Option; +} + +impl Experiences for ExperiencesType { + fn filter_by_title(&self, title: String) -> Vec { + self.iter() + .filter_map(|experience| { + if experience.title == title { + Some(experience.clone()) + } else { + None + } + }) + .collect() + } + fn get_by_title(&self, title: String) -> Option { + for experience in self.iter() { + if experience.title == title { + return Some(experience.clone()); + } + } + + None + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct SubstanceCompanion { + pub substance_name: String, + pub color: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct CustomUnit { + pub id: i64, + pub substance_name: String, + pub name: String, + #[serde(with = "ts_milliseconds", rename = "creationDate")] + pub creation_time: DateTime, + pub administration_route: AdministrationRoute, + pub dose: f64, + pub unit: String, + pub original_unit: String, + pub is_estimate: bool, + #[serde(rename = "estimatedDoseStandardDeviation")] + pub estimate_standard_deviation: Option, + pub is_archived: bool, +} + +pub type CustomUnitsType = Vec; + +pub trait CustomUnits { + fn get_by_id(&self, id: i64) -> Option; +} + +impl CustomUnits for CustomUnitsType { + fn get_by_id(&self, id: i64) -> Option { + for custom_unit in self.iter() { + if custom_unit.id == id { + return Some(custom_unit.clone()); + } + } + + None + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ExportData { + pub experiences: ExperiencesType, + pub substance_companions: Vec, + pub custom_substances: Vec, + pub custom_units: CustomUnitsType, +} diff --git a/journal/src/types/mod.rs b/journal/src/types/mod.rs deleted file mode 100644 index 36d23a0..0000000 --- a/journal/src/types/mod.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod types; - -pub use { - types::AdministrationRoute, - types::Ingestion, - types::CustomSubstance, - types::Experience, - types::ExportData -}; diff --git a/journal/src/types/types.rs b/journal/src/types/types.rs deleted file mode 100644 index 99be4f4..0000000 --- a/journal/src/types/types.rs +++ /dev/null @@ -1,74 +0,0 @@ -use chrono::serde::ts_milliseconds; -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::fmt::Display; - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "UPPERCASE")] -pub enum AdministrationRoute { - Oral, - Sublingual, - Buccal, - Insufflated, - Rectal, - Transdermal, - Subcutaneous, - Intramuscular, - Intravenous, - Smoked, - Inhaled, -} - -impl Display for AdministrationRoute { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Ingestion { - pub substance_name: String, - #[serde(with = "ts_milliseconds", rename = "time")] - pub ingestion_time: DateTime, - #[serde(with = "ts_milliseconds", rename = "creationDate")] - pub creation_time: DateTime, - pub dose: Option, - #[serde(rename = "isDoseAnEstimate")] - pub is_estimate: bool, - pub units: String, - pub custom_unit_id: Option, - #[serde(rename = "administrationRoute")] - pub roa: AdministrationRoute, - pub notes: String, - pub stomach_fullness: Option, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct CustomSubstance { - pub name: String, - pub description: String, - pub units: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct Experience { - pub title: String, - pub text: String, - #[serde(with = "ts_milliseconds", rename = "creationDate")] - pub creation_time: DateTime, - #[serde(with = "ts_milliseconds", rename = "sortDate")] - pub modified_time: DateTime, - pub ingestions: Vec, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(rename_all = "camelCase")] -pub struct ExportData { - //pub custom_substances: Vec, - //pub custom_units: Vec, - pub experiences: Vec, -} \ No newline at end of file diff --git a/journal_cli/src/args.rs b/journal_cli/src/args.rs new file mode 100644 index 0000000..ee55f9f --- /dev/null +++ b/journal_cli/src/args.rs @@ -0,0 +1,9 @@ +use clap::Parser; + +#[derive(Debug, Parser)] +#[clap()] +pub struct Args { + pub export_file: String, + // #[clap(subcommand)] + // pub command: Commands, +} diff --git a/journal_cli/src/main.rs b/journal_cli/src/main.rs new file mode 100644 index 0000000..76711f8 --- /dev/null +++ b/journal_cli/src/main.rs @@ -0,0 +1,49 @@ +use journal; +use journal::helpers::{ingestion_contains_estimate, ingestion_dose, ingestion_standard_deviation}; +use journal::types::Experiences; +use serde_json; +use std::fs::File; + +use clap::Parser; + +mod args; + +fn main() -> Result<(), Box> { + let args = args::Args::parse(); + + let file = File::open(args.export_file)?; + + let mut export_data: journal::types::ExportData = serde_json::from_reader(file)?; + + export_data + .experiences + .sort_by(|a, b| a.modified_time.cmp(&b.modified_time)); + + for experience in export_data.experiences.iter_mut() { + experience + .ingestions + .sort_by(|a, b| a.ingestion_time.cmp(&b.ingestion_time)); + } + + let experience = export_data + .experiences + .get_by_title("20 Apr 2024".to_string()) + .unwrap(); + + println!("{:#?}", experience); + + for ingestion in experience.ingestions.iter() { + println!( + "{}: {}{}", + ingestion.substance_name, + format!("{:.2}", ingestion_dose(ingestion, &export_data.custom_units).unwrap()).trim_end_matches(".00"), + if ingestion_contains_estimate(ingestion, &export_data.custom_units) { + format!("±{:.2}", ingestion_standard_deviation(ingestion, &export_data.custom_units).or(Some(0.0)).unwrap()) + } else { + "".to_string() + }.trim_end_matches(".00") + ) + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 4a2f370..0000000 --- a/src/main.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::fs::File; -use std::env; -use journal; -use serde_json; - -fn main() -> Result<(), Box> { - let args: Vec = env::args().collect(); - let file = File::open(args[1].clone())?; - let mut export_data: journal::types::ExportData = - serde_json::from_reader(file)?; - export_data.experiences.sort_by(|a, b| a.modified_time.cmp(&b.modified_time)); - for experience in export_data.experiences.iter_mut() { - experience.ingestions.sort_by(|a,b| a.ingestion_time.cmp(&b.ingestion_time)); - } - println!("Hello, world! {:?}", export_data); - Ok(()) -}