mirror of
https://github.com/wader/fq.git
synced 2024-12-24 13:52:02 +03:00
cli: Prepare completion for better variables support
This commit is contained in:
parent
e666380c4f
commit
2684ed2561
@ -15,11 +15,12 @@ const (
|
||||
CompletionTypeFunc CompletionType = "function"
|
||||
CompletionTypeVar CompletionType = "variable"
|
||||
CompletionTypeNone CompletionType = "none"
|
||||
CompletionTypeError CompletionType = "error"
|
||||
)
|
||||
|
||||
func BuildCompletionQuery(src string) (*gojq.Query, CompletionType, string) {
|
||||
if src == "" {
|
||||
return nil, CompletionTypeNone, ""
|
||||
return nil, CompletionTypeError, ""
|
||||
}
|
||||
|
||||
// HACK: if ending with "." or "$" append a test index that we remove later
|
||||
@ -32,7 +33,7 @@ func BuildCompletionQuery(src string) (*gojq.Query, CompletionType, string) {
|
||||
|
||||
q, err := gojq.Parse(src + probePrefix)
|
||||
if err != nil {
|
||||
return nil, CompletionTypeNone, ""
|
||||
return nil, CompletionTypeError, ""
|
||||
}
|
||||
|
||||
cq, ct, prefix := transformToCompletionQuery(q)
|
||||
@ -40,7 +41,37 @@ func BuildCompletionQuery(src string) (*gojq.Query, CompletionType, string) {
|
||||
prefix = strings.TrimSuffix(prefix, probePrefix)
|
||||
}
|
||||
|
||||
return cq, ct, prefix
|
||||
if ct == CompletionTypeNone {
|
||||
return cq, ct, ""
|
||||
}
|
||||
|
||||
// [.[] | cq | add]
|
||||
return &gojq.Query{
|
||||
Left: &gojq.Query{
|
||||
Term: &gojq.Term{
|
||||
Type: gojq.TermTypeArray,
|
||||
Array: &gojq.Array{
|
||||
Query: &gojq.Query{
|
||||
Left: &gojq.Query{
|
||||
Term: &gojq.Term{
|
||||
Type: gojq.TermTypeIdentity,
|
||||
SuffixList: []*gojq.Suffix{{Iter: true}},
|
||||
},
|
||||
},
|
||||
Op: gojq.OpPipe,
|
||||
Right: cq,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Op: gojq.OpPipe,
|
||||
Right: &gojq.Query{
|
||||
Term: &gojq.Term{
|
||||
Type: gojq.TermTypeFunc,
|
||||
Func: &gojq.Func{Name: "add"},
|
||||
},
|
||||
},
|
||||
}, ct, prefix
|
||||
}
|
||||
|
||||
// find the right most term that is completeable
|
||||
@ -56,6 +87,23 @@ func transformToCompletionQuery(q *gojq.Query) (*gojq.Query, CompletionType, str
|
||||
return q, ct, prefix
|
||||
}
|
||||
|
||||
keysFuncName := func(name string) string {
|
||||
if strings.HasPrefix(name, "_") {
|
||||
return "_extkeys"
|
||||
}
|
||||
return "keys"
|
||||
}
|
||||
|
||||
optFunc := func(name string) *gojq.Query {
|
||||
return &gojq.Query{
|
||||
Term: &gojq.Term{
|
||||
Type: gojq.TermTypeFunc,
|
||||
Func: &gojq.Func{Name: name},
|
||||
SuffixList: []*gojq.Suffix{{Optional: true}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ... as ...
|
||||
if q.Term.SuffixList != nil {
|
||||
last := q.Term.SuffixList[len(q.Term.SuffixList)-1]
|
||||
@ -70,40 +118,56 @@ func transformToCompletionQuery(q *gojq.Query) (*gojq.Query, CompletionType, str
|
||||
if last.Index != nil && last.Index.Name != "" {
|
||||
prefix := last.Index.Name
|
||||
last.Index = nil
|
||||
return q, CompletionTypeIndex, prefix
|
||||
return &gojq.Query{
|
||||
Left: q,
|
||||
Op: gojq.OpPipe,
|
||||
Right: optFunc(keysFuncName(prefix)),
|
||||
}, CompletionTypeIndex, prefix
|
||||
}
|
||||
}
|
||||
|
||||
switch q.Term.Type { //nolint:exhaustive
|
||||
case gojq.TermTypeIdentity:
|
||||
return q, CompletionTypeIndex, ""
|
||||
return &gojq.Query{
|
||||
Left: q,
|
||||
Op: gojq.OpPipe,
|
||||
Right: optFunc(keysFuncName("")),
|
||||
}, CompletionTypeIndex, ""
|
||||
case gojq.TermTypeIndex:
|
||||
if len(q.Term.SuffixList) == 0 {
|
||||
if q.Term.Index.Start == nil {
|
||||
return &gojq.Query{Term: &gojq.Term{Type: gojq.TermTypeIdentity}}, CompletionTypeIndex, q.Term.Index.Name
|
||||
return &gojq.Query{
|
||||
Left: &gojq.Query{Term: &gojq.Term{Type: gojq.TermTypeIdentity}},
|
||||
Op: gojq.OpPipe,
|
||||
Right: optFunc(keysFuncName(q.Term.Index.Name)),
|
||||
}, CompletionTypeIndex, q.Term.Index.Name
|
||||
}
|
||||
return nil, CompletionTypeNone, ""
|
||||
return q, CompletionTypeNone, ""
|
||||
}
|
||||
|
||||
last := q.Term.SuffixList[len(q.Term.SuffixList)-1]
|
||||
if last.Index != nil && last.Index.Start == nil {
|
||||
q.Term.SuffixList = q.Term.SuffixList[0 : len(q.Term.SuffixList)-1]
|
||||
return q, CompletionTypeIndex, last.Index.Name
|
||||
return &gojq.Query{
|
||||
Left: q,
|
||||
Op: gojq.OpPipe,
|
||||
Right: optFunc(keysFuncName(last.Index.Name)),
|
||||
}, CompletionTypeIndex, last.Index.Name
|
||||
}
|
||||
|
||||
return nil, CompletionTypeNone, ""
|
||||
return q, CompletionTypeNone, ""
|
||||
case gojq.TermTypeFunc:
|
||||
if len(q.Term.SuffixList) == 0 {
|
||||
if strings.HasPrefix(q.Term.Func.Name, "$") {
|
||||
return &gojq.Query{Term: &gojq.Term{Type: gojq.TermTypeIdentity}}, CompletionTypeVar, q.Term.Func.Name
|
||||
return optFunc("scope"), CompletionTypeVar, q.Term.Func.Name
|
||||
} else {
|
||||
return &gojq.Query{Term: &gojq.Term{Type: gojq.TermTypeIdentity}}, CompletionTypeFunc, q.Term.Func.Name
|
||||
return optFunc("scope"), CompletionTypeFunc, q.Term.Func.Name
|
||||
}
|
||||
}
|
||||
|
||||
return nil, CompletionTypeNone, ""
|
||||
return q, CompletionTypeNone, ""
|
||||
default:
|
||||
return nil, CompletionTypeNone, ""
|
||||
return q, CompletionTypeNone, ""
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -13,25 +13,25 @@ func TestBuildCompletionQuery(t *testing.T) {
|
||||
expectedType interp.CompletionType
|
||||
expectedPrefix string
|
||||
}{
|
||||
{"", "", interp.CompletionTypeNone, ""},
|
||||
{`.`, `.`, interp.CompletionTypeIndex, ``},
|
||||
{`.`, `.`, interp.CompletionTypeIndex, ``},
|
||||
{`.a`, `.`, interp.CompletionTypeIndex, `a`},
|
||||
{`.a.`, `.a`, interp.CompletionTypeIndex, ``},
|
||||
{`.a.b`, `.a`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a.b.`, `.a.b`, interp.CompletionTypeIndex, ``},
|
||||
{` .a.b`, `.a`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a | .b`, `.a | .`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a | .b.c`, `.a | .b`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a[]`, ``, interp.CompletionTypeNone, ``},
|
||||
{`.a[].b`, `.a[]`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a[].b.c`, `.a[].b`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a["b"]`, ``, interp.CompletionTypeNone, ``},
|
||||
{`.a["b"].c`, `.a["b"]`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a[1:2]`, ``, interp.CompletionTypeNone, ``},
|
||||
{`.a[1:2].c`, `.a[1:2]`, interp.CompletionTypeIndex, `c`},
|
||||
{`a`, `.`, interp.CompletionTypeFunc, `a`},
|
||||
{`a | b`, `a | .`, interp.CompletionTypeFunc, `b`},
|
||||
{"", "", interp.CompletionTypeError, ""},
|
||||
{`.`, `[.[] | . | keys?] | add`, interp.CompletionTypeIndex, ``},
|
||||
{`.`, `[.[] | . | keys?] | add`, interp.CompletionTypeIndex, ``},
|
||||
{`.a`, `[.[] | . | keys?] | add`, interp.CompletionTypeIndex, `a`},
|
||||
{`.a.`, `[.[] | .a | keys?] | add`, interp.CompletionTypeIndex, ``},
|
||||
{`.a.b`, `[.[] | .a | keys?] | add`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a.b.`, `[.[] | .a.b | keys?] | add`, interp.CompletionTypeIndex, ``},
|
||||
{`.a.b`, `[.[] | .a | keys?] | add`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a | .b`, `[.[] | .a | . | keys?] | add`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a | .b.c`, `[.[] | .a | .b | keys?] | add`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a[]`, `.a[]`, interp.CompletionTypeNone, ``},
|
||||
{`.a[].b`, `[.[] | .a[] | keys?] | add`, interp.CompletionTypeIndex, `b`},
|
||||
{`.a[].b.c`, `[.[] | .a[].b | keys?] | add`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a["b"]`, `.a["b"]`, interp.CompletionTypeNone, ``},
|
||||
{`.a["b"].c`, `[.[] | .a["b"] | keys?] | add`, interp.CompletionTypeIndex, `c`},
|
||||
{`.a[1:2]`, `.a[1:2]`, interp.CompletionTypeNone, ``},
|
||||
{`.a[1:2].c`, `[.[] | .a[1:2] | keys?] | add`, interp.CompletionTypeIndex, `c`},
|
||||
{`a`, `[.[] | scope?] | add`, interp.CompletionTypeFunc, `a`},
|
||||
{`a | b`, `[.[] | a | scope?] | add`, interp.CompletionTypeFunc, `b`},
|
||||
}
|
||||
for _, tC := range testCases {
|
||||
t.Run(tC.input, func(t *testing.T) {
|
||||
|
@ -35,43 +35,59 @@ def _exit_code_compile_error: 3;
|
||||
def _exit_code_input_decode_error: 4;
|
||||
def _exit_code_expr_error: 5;
|
||||
|
||||
# TODO: refactor this
|
||||
# TODO: completionMode
|
||||
# TODO: return escaped identifier, not sure current readline implementation supports
|
||||
# completions that needs to change previous input, ex: .a\t -> ."a \" b" etc
|
||||
def _complete($e; $pos):
|
||||
def _complete($e; $cursor_pos):
|
||||
def _is_internal: startswith("_") or startswith("$_");
|
||||
def _query_index_or_key($q):
|
||||
( ([.[] | eval($q) | type]) as $n
|
||||
| if ($n | all(. == "object")) then "."
|
||||
elif ($n | all(. == "array")) then "[]"
|
||||
else null
|
||||
end
|
||||
);
|
||||
# only complete if at end of there is a whitespace for now
|
||||
if ($e[$pos] | . == "" or . == " ") then
|
||||
( ( $e[0:$pos] | _complete_query) as {$type, $query, $prefix}
|
||||
if ($e[$cursor_pos] | . == "" or . == " ") then
|
||||
( . as $c
|
||||
| ( $e[0:$cursor_pos] | _complete_query) as {$type, $query, $prefix}
|
||||
| {
|
||||
prefix: $prefix,
|
||||
names: (
|
||||
( if $type == "function" or $type == "variable" then
|
||||
[.[] | eval($query) | scope] | add
|
||||
elif $type == "index" then
|
||||
[.[] | eval($query) | keys?, _extkeys?] | add
|
||||
else
|
||||
[]
|
||||
end
|
||||
| ($prefix | _is_internal) as $prefix_is_internal
|
||||
| map(
|
||||
select(
|
||||
strings and
|
||||
(_is_ident or $type == "variable") and
|
||||
((_is_internal | not) or $prefix_is_internal or $type == "index") and
|
||||
startswith($prefix)
|
||||
)
|
||||
if $type == "none" then
|
||||
( $c
|
||||
| _query_index_or_key($query)
|
||||
| if . then [.] else [] end
|
||||
)
|
||||
| unique
|
||||
| sort
|
||||
)
|
||||
else
|
||||
( $c
|
||||
| eval($query)
|
||||
| ($prefix | _is_internal) as $prefix_is_internal
|
||||
| map(
|
||||
select(
|
||||
strings and
|
||||
(_is_ident or $type == "variable") and
|
||||
((_is_internal | not) or $prefix_is_internal or $type == "index") and
|
||||
startswith($prefix)
|
||||
)
|
||||
)
|
||||
| unique
|
||||
| sort
|
||||
| if length == 1 and .[0] == $prefix then
|
||||
( $c
|
||||
| _query_index_or_key($e)
|
||||
| if . then [$prefix+.] else [$prefix] end
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
}
|
||||
)
|
||||
else
|
||||
{prefix: "", names: []}
|
||||
end;
|
||||
# for convenience when testing
|
||||
def _complete($e): _complete($e; $e | length);
|
||||
|
||||
def _obj_to_csv_kv:
|
||||
|
Loading…
Reference in New Issue
Block a user