diff --git a/builtin.go b/builtin.go index 8ae4666c..b17b945d 100644 --- a/builtin.go +++ b/builtin.go @@ -60,11 +60,11 @@ func init() { "todateiso8601": {{Name: "todateiso8601", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "strftime", Args: []*Query{{Term: &Term{Type: TermTypeString, Str: &String{Str: "%Y-%m-%dT%H:%M:%SZ"}}}}}}}}}, "tostream": {{Name: "tostream", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{FuncDefs: []*FuncDef{{Name: "r", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}, {Optional: true}}}}, Op: OpPipe, Right: &Query{Func: "r"}}}}, Op: OpComma, Right: &Query{Func: "."}}}}, Func: "r"}}}, SuffixList: []*Suffix{{Bind: &Bind{Patterns: []*Pattern{{Name: "$p"}}, Body: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "getpath", Args: []*Query{{Func: "$p"}}}}}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeReduce, Reduce: &Reduce{Query: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "path", Args: []*Query{{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Iter: true}, {Optional: true}}}}}}}}, Pattern: &Pattern{Name: "$q"}, Start: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "$p"}, Op: OpComma, Right: &Query{Func: "."}}}}}, Update: &Query{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Left: &Query{Func: "$p"}, Op: OpAdd, Right: &Query{Func: "$q"}}}}}}}}}}}}}}}}, "truncate_stream": {{Name: "truncate_stream", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeIdentity, SuffixList: []*Suffix{{Bind: &Bind{Patterns: []*Pattern{{Name: "$n"}}, Body: &Query{Left: &Query{Func: "null"}, Op: OpPipe, Right: &Query{Left: &Query{Func: "f"}, Op: OpPipe, Right: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}, Op: OpPipe, Right: &Query{Left: &Query{Func: "length"}, Op: OpGt, Right: &Query{Func: "$n"}}}, Then: &Query{Left: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Term: &Term{Type: TermTypeNumber, Number: "0"}}}}}, Op: OpModify, Right: &Query{Term: &Term{Type: TermTypeIndex, Index: &Index{Start: &Query{Func: "$n"}, IsSlice: true}}}}, Else: &Query{Func: "empty"}}}}}}}}}}}}}, - "unique_by": {{Name: "unique_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_unique_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, - "until": {{Name: "until", Args: []string{"cond", "next"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_until", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Func: "."}, Else: &Query{Left: &Query{Func: "next"}, Op: OpPipe, Right: &Query{Func: "_until"}}}}}}}, Func: "_until"}}}, - "values": {{Name: "values", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Func: "null"}}}}}}}}, - "walk": {{Name: "walk", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_walk", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Func: "_walk"}}}}}, Elif: []*IfElif{{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map_values", Args: []*Query{{Func: "_walk"}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "f"}}}}, Func: "_walk"}}}, - "while": {{Name: "while", Args: []string{"cond", "update"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_while", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "update"}, Op: OpPipe, Right: &Query{Func: "_while"}}}}}, Else: &Query{Func: "empty"}}}}}}, Func: "_while"}}}, - "with_entries": {{Name: "with_entries", Args: []string{"f"}, Body: &Query{Left: &Query{Func: "to_entries"}, Op: OpPipe, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Func: "f"}}}}}, Op: OpPipe, Right: &Query{Func: "from_entries"}}}}}, + "unique_by": {{Name: "unique_by", Args: []string{"f"}, Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "_unique_by", Args: []*Query{{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Term: &Term{Type: TermTypeArray, Array: &Array{Query: &Query{Func: "f"}}}}}}}}}}}}}}, + "until": {{Name: "until", Args: []string{"cond", "next"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_until", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Func: "."}, Else: &Query{Left: &Query{Func: "next"}, Op: OpPipe, Right: &Query{Func: "_until"}}}}}}}, Func: "_until"}}}, + "values": {{Name: "values", Body: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "select", Args: []*Query{{Left: &Query{Func: "."}, Op: OpNe, Right: &Query{Func: "null"}}}}}}}}, + "walk": {{Name: "walk", Args: []string{"f"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_walk", Body: &Query{Left: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "array"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Func: "_walk"}}}}}, Elif: []*IfElif{{Cond: &Query{Left: &Query{Func: "type"}, Op: OpEq, Right: &Query{Term: &Term{Type: TermTypeString, Str: &String{Str: "object"}}}}, Then: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map_values", Args: []*Query{{Func: "_walk"}}}}}}}}}}, Op: OpPipe, Right: &Query{Func: "f"}}}}, Func: "_walk"}}}, + "while": {{Name: "while", Args: []string{"cond", "update"}, Body: &Query{FuncDefs: []*FuncDef{{Name: "_while", Body: &Query{Term: &Term{Type: TermTypeIf, If: &If{Cond: &Query{Func: "cond"}, Then: &Query{Left: &Query{Func: "."}, Op: OpComma, Right: &Query{Term: &Term{Type: TermTypeQuery, Query: &Query{Left: &Query{Func: "update"}, Op: OpPipe, Right: &Query{Func: "_while"}}}}}, Else: &Query{Func: "empty"}}}}}}, Func: "_while"}}}, + "with_entries": {{Name: "with_entries", Args: []string{"f"}, Body: &Query{Left: &Query{Func: "to_entries"}, Op: OpPipe, Right: &Query{Left: &Query{Term: &Term{Type: TermTypeFunc, Func: &Func{Name: "map", Args: []*Query{{Func: "f"}}}}}, Op: OpPipe, Right: &Query{Func: "from_entries"}}}}}, } } diff --git a/cli/cli.go b/cli/cli.go index 80e9c06b..fcf66307 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -4,14 +4,12 @@ package cli import ( "errors" "fmt" + "github.com/itchyny/gojq" + "github.com/mattn/go-isatty" "io" "os" "runtime" "strings" - - "github.com/mattn/go-isatty" - - "github.com/itchyny/gojq" ) const name = "gojq" @@ -40,11 +38,20 @@ type cli struct { outputCompact bool outputIndent *int outputTab bool + outputXML bool outputYAML bool inputRaw bool inputStream bool + inputJSON bool + inputXML bool inputYAML bool inputSlurp bool + stripSpaceXML bool + stripAttrsXML bool + forceListXML []string + htmlXML bool + rootXML string + elementXML string argnames []string argvalues []any @@ -60,13 +67,22 @@ type flagopts struct { 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"` - OutputYAML bool `long:"yaml-output" description:"output in YAML format"` + OutputYAML bool `short:"y" long:"yaml-output" description:"output in YAML format"` + OutputXML bool `short:"x" long:"xml-output" description:"output in XML format"` 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"` - InputYAML bool `long:"yaml-input" description:"read input as YAML format"` + InputJSON bool `short:"J" long:"json-input" description:"read input as JSON format"` + InputXML bool `short:"X" long:"xml-input" description:"read input as XML format"` + StripAttrsXML bool `long:"xml-no-attributes" description:"remove attributes from XML elements"` + StripSpaceXML bool `long:"xml-no-namespaces" description:"remove namespace from XML elements and attributes"` + ForceListXML []string `long:"xml-force-list" description:"force XML elements as array"` + RootXML string `long:"xml-root" description:"root XML element name"` + ElementXML string `long:"xml-element" description:"element XML element name"` + HtmlXML bool `short:"H" long:"xml-html" description:"read input as XML with HTML compatibility mode"` + InputYAML bool `short:"Y" long:"yaml-input" description:"read input as YAML format"` 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"` @@ -123,9 +139,9 @@ Usage: return nil } cli.outputRaw, cli.outputRaw0, cli.outputJoin, - cli.outputCompact, cli.outputIndent, cli.outputTab, cli.outputYAML = + cli.outputCompact, cli.outputIndent, cli.outputTab, cli.outputXML, cli.outputYAML = opts.OutputRaw, opts.OutputRaw0, opts.OutputJoin, - opts.OutputCompact, opts.OutputIndent, opts.OutputTab, opts.OutputYAML + opts.OutputCompact, opts.OutputIndent, opts.OutputTab, opts.OutputXML, opts.OutputYAML defer func(x bool) { noColor = x }(noColor) if opts.OutputColor || opts.OutputMono { noColor = opts.OutputMono @@ -154,6 +170,8 @@ Usage: } cli.inputRaw, cli.inputStream, cli.inputYAML, cli.inputSlurp = opts.InputRaw, opts.InputStream, opts.InputYAML, opts.InputSlurp + cli.inputJSON, cli.inputXML, cli.stripAttrsXML, cli.stripSpaceXML, cli.forceListXML, cli.rootXML, cli.elementXML, cli.htmlXML = + opts.InputJSON, opts.InputXML, opts.StripAttrsXML, opts.StripSpaceXML, opts.ForceListXML, opts.RootXML, opts.ElementXML, opts.HtmlXML for k, v := range opts.Arg { cli.argnames = append(cli.argnames, "$"+k) cli.argvalues = append(cli.argvalues, v) @@ -300,10 +318,28 @@ func (cli *cli) createInputIter(args []string) (iter inputIter) { } case cli.inputStream: newIter = newStreamInputIter + case cli.inputJSON: + newIter = newJSONInputIter + case cli.inputXML || cli.htmlXML: + newIter = func(r io.Reader, fname string) inputIter { + return newXMLInputIter(r, fname, !cli.stripAttrsXML, !cli.stripSpaceXML, cli.forceListXML, cli.htmlXML) + } case cli.inputYAML: newIter = newYAMLInputIter default: - newIter = newJSONInputIter + // automatically detect between JSON / YAML / XML format + newIter = func(r io.Reader, fname string) inputIter { + rd, f := detectInputType(r, 100) + switch f { + case JsonFormat: + return newJSONInputIter(rd, fname) + case YamlFormat: + return newYAMLInputIter(rd, fname) + case XmlFormat: + return newXMLInputIter(rd, fname, !cli.stripAttrsXML, !cli.stripSpaceXML, cli.forceListXML, cli.htmlXML) + } + return newJSONInputIter(rd, fname) + } } if cli.inputSlurp { defer func() { @@ -404,6 +440,9 @@ func (cli *cli) createMarshaler() marshaler { } else if i := cli.outputIndent; i != nil { indent = *i } + if cli.outputXML { + return xmlFormatter(&indent, cli.rootXML, cli.elementXML) + } f := newEncoder(cli.outputTab, indent) if cli.outputRaw || cli.outputRaw0 || cli.outputJoin { return &rawMarshaler{f, cli.outputRaw0} diff --git a/cli/detect.go b/cli/detect.go new file mode 100644 index 00000000..07fada76 --- /dev/null +++ b/cli/detect.go @@ -0,0 +1,184 @@ +package cli + +import ( + "bytes" + "io" +) + +type DetectedFormat int + +func (d DetectedFormat) String() string { + switch d { + case JsonFormat: + return "json" + case YamlFormat: + return "yaml" + case XmlFormat: + return "xml" + } + return "" +} + +const ( + JsonFormat DetectedFormat = iota + YamlFormat + XmlFormat +) + +func detectInputType(r io.Reader, bufSize int) (io.Reader, DetectedFormat) { + readers := make([]io.Reader, 0) + var buf []byte + index := 0 + length := 0 + var err error + result := func(t DetectedFormat) (io.Reader, DetectedFormat) { + readers = append(readers, r) + return io.MultiReader(readers...), t + } + readByte := func() (byte, error) { + if index == length { + if err != nil { + return 0, err + } + buf = make([]byte, bufSize) + length, err = r.Read(buf) + if length == 0 && err != nil { + return 0, err + } + readers = append(readers, bytes.NewReader(buf[0:length])) + index = 0 + } + i := index + index = index + 1 + return buf[i], nil + } + + // state machine + state := "loop" + var b, c byte +loop: + for { + switch state { + // main loop + case "loop": + for { + b, err = readByte() + if err != nil { + return result(JsonFormat) + } + switch b { + case ' ', '\t', '\r', '\n': + case '{', '[', '/': + return result(JsonFormat) + case '#': + return result(YamlFormat) + case '<': + return result(XmlFormat) + case '-': + // yaml if "- " or "---" + c, err = readByte() + if err != nil { + return result(JsonFormat) + } + if c == ' ' { + return result(YamlFormat) + } + if c != '-' { + return result(JsonFormat) + } + c, err = readByte() + if err != nil || c != '-' { + return result(JsonFormat) + } + return result(YamlFormat) + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.': + return result(JsonFormat) + case '"': + // string can be either a json/yaml text + state = "string" + c = b + continue loop + case 't': + // json if true + for _, c = range []byte("rue") { + b, err = readByte() + if err != nil || b != c { + return result(YamlFormat) + } + } + state = "after" + continue loop + case 'f': + // json if false + for _, c = range []byte("alse") { + b, err = readByte() + if err != nil || b != c { + return result(YamlFormat) + } + } + state = "after" + continue loop + case 'n': + // json if false + for _, c = range []byte("ull") { + b, err = readByte() + if err != nil || b != c { + return result(YamlFormat) + } + } + state = "after" + continue loop + default: + // neither a number or string with " + return result(YamlFormat) + } + } + // string, started by " + case "string": + escape := false + for { + b, err = readByte() + if err != nil { + return result(JsonFormat) + } + if escape { + continue + } + switch b { + case ' ', '\t': + case '\r', '\n': + // new line not allowed in yaml tags + result(JsonFormat) + case '\\': + // escape next character + escape = true + case c: + // close string, look for next char to identify if it is yaml tag + state = "after" + continue loop + } + } + // close string, look for next char to identify if it is yaml tag + case "after": + for { + b, err = readByte() + if err != nil { + return result(JsonFormat) + } + switch b { + case ' ', '\t': + case '\r', '\n': + // new line not allowed in yaml tags + return result(JsonFormat) + case ':': + // it is a yaml tag + return result(YamlFormat) + default: + // it is not a yaml tag + return result(JsonFormat) + } + } + } + + } +} diff --git a/cli/detect_test.go b/cli/detect_test.go new file mode 100644 index 00000000..e7c13b69 --- /dev/null +++ b/cli/detect_test.go @@ -0,0 +1,56 @@ +package cli + +import ( + "bytes" + "io" + "strings" + "testing" +) + +func TestDetectInputType(t *testing.T) { + for _, s := range []string{"", "\t", "\r", "\n", " ", " \t", " \r", " \n", " \t ", " \r ", " \n "} { + testDetectInputType(t, s+"", JsonFormat) + testDetectInputType(t, s+"{", JsonFormat) + testDetectInputType(t, s+"#", YamlFormat) + testDetectInputType(t, s+"<", XmlFormat) + testDetectInputType(t, s+"a", YamlFormat) + testDetectInputType(t, s+"a:", YamlFormat) + testDetectInputType(t, s+"a: 1", YamlFormat) + testDetectInputType(t, s+"true", JsonFormat) + testDetectInputType(t, s+"true true", JsonFormat) + testDetectInputType(t, s+"true:", YamlFormat) + testDetectInputType(t, s+"null", JsonFormat) + testDetectInputType(t, s+"null null", JsonFormat) + testDetectInputType(t, s+"null:", YamlFormat) + testDetectInputType(t, s+"false", JsonFormat) + testDetectInputType(t, s+"false false", JsonFormat) + testDetectInputType(t, s+"false:", YamlFormat) + testDetectInputType(t, s+"1", JsonFormat) + testDetectInputType(t, s+"-1", JsonFormat) + testDetectInputType(t, s+"-1e3", JsonFormat) + testDetectInputType(t, s+"- ", YamlFormat) + testDetectInputType(t, s+"--", JsonFormat) + testDetectInputType(t, s+"---", YamlFormat) + testDetectInputType(t, s+`"hello"`, JsonFormat) + testDetectInputType(t, s+`"hello":1`, YamlFormat) + testDetectInputType(t, s+`"hello": 1`, YamlFormat) + testDetectInputType(t, s+`'hello'`, YamlFormat) + testDetectInputType(t, s+`'hello':1`, YamlFormat) + testDetectInputType(t, s+`'hello': 1`, YamlFormat) + } +} + +func testDetectInputType(t *testing.T, s string, format DetectedFormat) { + r, f := detectInputType(strings.NewReader(s), 1) + if f != format { + t.Fatalf("failed: invalid format '%s' expected '%s' for string '%s'", f, format, s) + } + buf := new(bytes.Buffer) + _, err := io.Copy(buf, r) + if err != nil { + t.Fatalf("failed: copy error for string '%s'", s) + } + if buf.String() != s { + t.Fatalf("failed: invalid reader content '%s'' for string '%s'", buf.String(), s) + } +} diff --git a/cli/inputs.go b/cli/inputs.go index 7a35dcdf..af02abfd 100644 --- a/cli/inputs.go +++ b/cli/inputs.go @@ -11,6 +11,7 @@ import ( "gopkg.in/yaml.v3" "github.com/itchyny/gojq" + "github.com/momiji/xqml" ) type inputReader struct { @@ -305,6 +306,51 @@ func (i *streamInputIter) Name() string { return i.fname } +type xmlInputIter struct { + dec *xqml.Decoder + ir *inputReader + fname string + err error +} + +func newXMLInputIter(r io.Reader, fname string, attributes bool, namespaces bool, forceList []string, html bool) inputIter { + ir := newInputReader(r) + dec := xqml.NewDecoder(ir) + dec.Attributes = attributes + dec.Namespaces = namespaces + dec.ForceList = forceList + dec.Html = html + dec.Cast = true + dec.Partials = true + return &xmlInputIter{dec: dec, ir: ir, fname: fname} +} + +func (i *xmlInputIter) Next() (any, bool) { + if i.err != nil { + return nil, false + } + var v any + err := i.dec.Decode(&v) + if err != nil { + if err == io.EOF { + i.err = err + return nil, false + } + i.err = err + return i.err, true + } + return v, true +} + +func (i *xmlInputIter) Close() error { + i.err = io.EOF + return nil +} + +func (i *xmlInputIter) Name() string { + return i.fname +} + type yamlInputIter struct { dec *yaml.Decoder ir *inputReader diff --git a/cli/marshaler.go b/cli/marshaler.go index db09afbb..08bab601 100644 --- a/cli/marshaler.go +++ b/cli/marshaler.go @@ -2,6 +2,7 @@ package cli import ( "fmt" + "github.com/momiji/xqml" "io" "strings" @@ -28,6 +29,31 @@ func (m *rawMarshaler) marshal(v any, w io.Writer) error { return m.m.marshal(v, w) } +func xmlFormatter(indent *int, root string, element string) *xmlMarshaller { + return &xmlMarshaller{indent, root, element} +} + +type xmlMarshaller struct { + indent *int + root string + element string +} + +func (m *xmlMarshaller) marshal(v any, w io.Writer) error { + enc := xqml.NewEncoder(w) + if i := m.indent; i != nil { + indent := strings.Repeat(" ", *m.indent) + enc.Indent = indent + } + if m.root != "" { + enc.Root = m.root + } + if m.element != "" { + enc.Element = m.element + } + return enc.Encode(v) +} + func yamlFormatter(indent *int) *yamlMarshaler { return &yamlMarshaler{indent} } diff --git a/cli/test.yaml b/cli/test.yaml index 72d4bf34..1dbe31f8 100644 --- a/cli/test.yaml +++ b/cli/test.yaml @@ -7806,7 +7806,7 @@ "abcde": :★★★★★★★★★★★★★★★ error: | invalid json: :3 - 3 | "abcde": :★★★★★★★★★★★★ + 3 | "abcde": :★★★★★★★★★★★★★★★ ^ invalid character ':' looking for beginning of value - name: json file not found error diff --git a/cli/testdata/1.xml b/cli/testdata/1.xml new file mode 100644 index 00000000..78cc0c7b --- /dev/null +++ b/cli/testdata/1.xml @@ -0,0 +1,6 @@ + + 42 + + + 100 + diff --git a/go.mod b/go.mod index 1f8fb609..808b4733 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/itchyny/timefmt-go v0.1.6 github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-runewidth v0.0.15 + github.com/momiji/xqml v0.0.9 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 011a7fd5..c413d4fa 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/momiji/xqml v0.0.9 h1:jEqjozVHqb4JlievVOHhXBjceF8nBmo8+VpyXc2n+/w= +github.com/momiji/xqml v0.0.9/go.mod h1:IrROwWeq3LeqxsOtamTWx6L39irg7HJrH7GuDNolSv0= 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=