mirror of
https://github.com/wader/fq.git
synced 2024-11-23 00:57:15 +03:00
Add *grep/1/2 and find/1/2
This commit is contained in:
parent
3044fefb5b
commit
e86b45bd1a
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -8,6 +8,7 @@
|
||||
"APIC",
|
||||
"Arity",
|
||||
"BCDU",
|
||||
"bgrep",
|
||||
"bzip",
|
||||
"CCIT",
|
||||
"chzyer",
|
||||
@ -15,6 +16,7 @@
|
||||
"CLLID",
|
||||
"coef",
|
||||
"colorjson",
|
||||
"cond",
|
||||
"cpus",
|
||||
"ctxreadseeker",
|
||||
"ctxstack",
|
||||
@ -30,6 +32,7 @@
|
||||
"Exif",
|
||||
"Exiter",
|
||||
"FALLID",
|
||||
"fgrep",
|
||||
"fpbits",
|
||||
"fqtest",
|
||||
"ftyp",
|
||||
@ -60,6 +63,7 @@
|
||||
"ipco",
|
||||
"iprint",
|
||||
"iprp",
|
||||
"isempty",
|
||||
"itchyny",
|
||||
"ldflags",
|
||||
"libavformat",
|
||||
@ -131,6 +135,7 @@
|
||||
"unparam",
|
||||
"Unsychronized",
|
||||
"UTCID",
|
||||
"vgrep",
|
||||
"WEBP",
|
||||
"Xiph",
|
||||
"xrange"
|
||||
|
@ -142,6 +142,10 @@ notable is support for arbitrary-precision integers.
|
||||
- `format_root/0` return root value of format for value
|
||||
- `parent/0` return parent value
|
||||
- `parents/0` output parents of value
|
||||
- `grep/1`, `grep/2` recursively match value and buffer
|
||||
- `vgrep/1`, `vgrep/2` recursively match value
|
||||
- `bgrep/1`, `bgrep/2` recursively match buffer
|
||||
- `fgrep/1`, `fgrep/2` recursively match field name
|
||||
- `open` open file for reading
|
||||
- `probe` or `decode` probe format and decode
|
||||
- `mp3`, `matroska`, ..., `<name>`, `decode([name])` force decode as format
|
||||
|
28
internal/gojqextra/regexp.go
Normal file
28
internal/gojqextra/regexp.go
Normal file
@ -0,0 +1,28 @@
|
||||
package gojqextra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// from gojq, see https://github.com/itchyny/gojq/blob/main/LICENSE
|
||||
func CompileRegexp(re, allowedFlags, flags string) (*regexp.Regexp, error) {
|
||||
if strings.IndexFunc(flags, func(r rune) bool {
|
||||
return !strings.ContainsAny(string([]rune{r}), allowedFlags)
|
||||
}) >= 0 {
|
||||
return nil, fmt.Errorf("unsupported regular expression flag: %q", flags)
|
||||
}
|
||||
re = strings.ReplaceAll(re, "(?<", "(?P<")
|
||||
if strings.ContainsRune(flags, 'i') {
|
||||
re = "(?i)" + re
|
||||
}
|
||||
if strings.ContainsRune(flags, 'm') {
|
||||
re = "(?s)" + re
|
||||
}
|
||||
r, err := regexp.Compile(re)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid regular expression %q: %w", re, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
88
internal/ioextra/runereader.go
Normal file
88
internal/ioextra/runereader.go
Normal file
@ -0,0 +1,88 @@
|
||||
package ioextra
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/bits"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// ByteRuneReader reads each by as a runes from a io.ReadSeeker
|
||||
// ex: when used with regexp \u00ff code point will match byte 0xff and not the utf-8 encoded version of 0xff
|
||||
type ByteRuneReader struct {
|
||||
RS io.ReadSeeker
|
||||
}
|
||||
|
||||
func (brr ByteRuneReader) ReadRune() (r rune, size int, err error) {
|
||||
var b [1]byte
|
||||
_, err = io.ReadFull(brr.RS, b[:])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
r = rune(b[0])
|
||||
return r, 1, nil
|
||||
}
|
||||
|
||||
func (brr ByteRuneReader) Seek(offset int64, whence int) (int64, error) {
|
||||
return brr.RS.Seek(offset, whence)
|
||||
}
|
||||
|
||||
type RuneReadSeeker struct {
|
||||
RS io.ReadSeeker
|
||||
}
|
||||
|
||||
func utf8Bytes(b byte) int {
|
||||
c := bits.LeadingZeros8(^b)
|
||||
// 0b0xxxxxxx 1 byte
|
||||
// 0b110xxxxx 2 byte
|
||||
// 0b1110xxxx 3 byte
|
||||
// 0b11110xxx 4 byte
|
||||
switch c {
|
||||
case 0:
|
||||
return 1
|
||||
case 2, 3, 4:
|
||||
return c
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
// RuneReadSeeker reads runs from a io.ReadSeeker
|
||||
func (brr RuneReadSeeker) ReadRune() (r rune, size int, err error) {
|
||||
var b [utf8.UTFMax]byte
|
||||
|
||||
_, err = io.ReadFull(brr.RS, b[0:1])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
c := b[0]
|
||||
if c < utf8.RuneSelf {
|
||||
return rune(c), 1, nil
|
||||
}
|
||||
|
||||
ss := utf8Bytes(b[0])
|
||||
if ss < 0 {
|
||||
return utf8.RuneError, 1, nil
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(brr.RS, b[1:ss])
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
r, s := utf8.DecodeRune(b[0:ss])
|
||||
// possibly rewind if DecodeRune fails as there was a invalid multi byte code point
|
||||
// TODO: better way that don't require seek back? buffer? one at a time?
|
||||
d := ss - s
|
||||
if d > 0 {
|
||||
if _, err := brr.Seek(int64(-d), io.SeekCurrent); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return r, s, nil
|
||||
}
|
||||
|
||||
func (brr RuneReadSeeker) Seek(offset int64, whence int) (int64, error) {
|
||||
return brr.RS.Seek(offset, whence)
|
||||
}
|
@ -1,8 +1,5 @@
|
||||
package bitio
|
||||
|
||||
// TODO: should return int64?
|
||||
// TODO: document len(p)/nBits, should be +1 for when not aligned
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
@ -346,8 +343,8 @@ func (r *SectionBitReader) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func (r *SectionBitReader) Seek(offset int64, whence int) (int64, error) {
|
||||
seekBytePos, err := r.SeekBits(offset*8, whence)
|
||||
return seekBytePos * 8, err
|
||||
seekBitsPos, err := r.SeekBits(offset*8, whence)
|
||||
return seekBitsPos / 8, err
|
||||
}
|
||||
|
||||
// TODO: smart, track index?
|
||||
@ -443,3 +440,8 @@ func (m *MultiBitReader) Read(p []byte) (n int, err error) {
|
||||
|
||||
return int(BitsByteCount(int64(n))), nil
|
||||
}
|
||||
|
||||
func (m *MultiBitReader) Seek(offset int64, whence int) (int64, error) {
|
||||
seekBitsPos, err := m.SeekBits(offset*8, whence)
|
||||
return seekBitsPos / 8, err
|
||||
}
|
||||
|
@ -1,13 +1,5 @@
|
||||
package bitio
|
||||
|
||||
// not concurrency safe as bitsBuf is reused
|
||||
|
||||
// TODO:
|
||||
// cache pos, len
|
||||
// inline for speed?
|
||||
// F -> FLT?
|
||||
// UTF16/UTF32
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
@ -20,6 +12,7 @@ import (
|
||||
type Buffer struct {
|
||||
br interface {
|
||||
io.Reader // both Reader and SectionBitReader implement io.Reader
|
||||
io.Seeker
|
||||
BitReadSeeker
|
||||
BitReader
|
||||
BitReaderAt
|
||||
@ -51,6 +44,7 @@ func NewBufferFromReadSeeker(rs io.ReadSeeker) (*Buffer, error) {
|
||||
|
||||
func NewBufferFromBitReadSeeker(br interface {
|
||||
io.Reader
|
||||
io.Seeker
|
||||
BitReadSeeker
|
||||
BitReaderAt
|
||||
}) (*Buffer, error) {
|
||||
@ -175,6 +169,10 @@ func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
return b.br.Read(p)
|
||||
}
|
||||
|
||||
func (b *Buffer) Seek(offset int64, whence int) (int64, error) {
|
||||
return b.br.Seek(offset, whence)
|
||||
}
|
||||
|
||||
// BytesRange reads nBytes bytes starting bit position start
|
||||
// Does not update current position.
|
||||
// TODO: swap args
|
||||
|
@ -61,6 +61,7 @@ func (bv BufferView) JQValueLength() interface{} {
|
||||
func (bv BufferView) JQValueSliceLen() interface{} {
|
||||
return bv.JQValueLength()
|
||||
}
|
||||
|
||||
func (bv BufferView) JQValueIndex(index int) interface{} {
|
||||
if index < 0 {
|
||||
return ""
|
||||
|
@ -15,9 +15,9 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
@ -28,6 +28,7 @@ import (
|
||||
"github.com/wader/fq/internal/progressreadseeker"
|
||||
"github.com/wader/fq/pkg/bitio"
|
||||
"github.com/wader/fq/pkg/decode"
|
||||
"github.com/wader/fq/pkg/ranges"
|
||||
|
||||
"github.com/wader/gojq"
|
||||
)
|
||||
@ -53,6 +54,7 @@ func (i *Interp) makeFunctions() []Function {
|
||||
|
||||
{[]string{"open"}, 0, 0, i._open, nil},
|
||||
{[]string{"_decode"}, 2, 2, i._decode, nil},
|
||||
{[]string{"_is_decode_value"}, 0, 0, i._isDecodeValue, nil},
|
||||
|
||||
{[]string{"format"}, 0, 0, i.format, nil},
|
||||
{[]string{"_display"}, 1, 1, nil, i._display},
|
||||
@ -93,7 +95,7 @@ func (i *Interp) makeFunctions() []Function {
|
||||
{[]string{"path_unescape"}, 0, 0, i.pathUnescape, nil},
|
||||
{[]string{"aes_ctr"}, 1, 2, i.aesCtr, nil},
|
||||
|
||||
{[]string{"find"}, 1, 1, nil, i.find},
|
||||
{[]string{"find"}, 1, 2, nil, i.find},
|
||||
}
|
||||
|
||||
return fs
|
||||
@ -650,6 +652,11 @@ func (i *Interp) _decode(c interface{}, a []interface{}) interface{} {
|
||||
return makeDecodeValue(dv)
|
||||
}
|
||||
|
||||
func (i *Interp) _isDecodeValue(c interface{}, a []interface{}) interface{} {
|
||||
_, ok := c.(DecodeValue)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (i *Interp) format(c interface{}, a []interface{}) interface{} {
|
||||
cj, ok := c.(gojq.JQValue)
|
||||
if !ok {
|
||||
@ -663,10 +670,7 @@ func (i *Interp) format(c interface{}, a []interface{}) interface{} {
|
||||
}
|
||||
|
||||
func (i *Interp) _display(c interface{}, a []interface{}) gojq.Iter {
|
||||
opts, err := i.Options(a...)
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
}
|
||||
opts := i.OptionsEx(a...)
|
||||
|
||||
switch v := c.(type) {
|
||||
case Display:
|
||||
@ -713,11 +717,10 @@ func (i *Interp) toBits(c interface{}, a []interface{}) interface{} {
|
||||
}
|
||||
|
||||
func (i *Interp) toValue(c interface{}, a []interface{}) interface{} {
|
||||
opts, err := i.Options(append([]interface{}{}, a...)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v, _ := toValue(opts, c)
|
||||
v, _ := toValue(
|
||||
func() Options { return i.OptionsEx(append([]interface{}{}, a...)...) },
|
||||
c,
|
||||
)
|
||||
return v
|
||||
}
|
||||
|
||||
@ -806,45 +809,81 @@ func (i *Interp) aesCtr(c interface{}, a []interface{}) interface{} {
|
||||
}
|
||||
|
||||
func (i *Interp) find(c interface{}, a []interface{}) gojq.Iter {
|
||||
bb, err := toBuffer(c)
|
||||
var ok bool
|
||||
|
||||
bv, err := toBufferView(c)
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
}
|
||||
|
||||
sbb, err := toBuffer(a[0])
|
||||
var re string
|
||||
re, ok = a[0].(string)
|
||||
if !ok {
|
||||
return gojq.NewIter(gojqextra.FuncTypeError{Name: "find", Typ: "string"})
|
||||
}
|
||||
|
||||
var flags string
|
||||
if len(a) > 1 {
|
||||
flags, ok = a[1].(string)
|
||||
if !ok {
|
||||
return gojq.NewIter(gojqextra.FuncTypeError{Name: "find", Typ: "string"})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: err to string
|
||||
// TODO: extract to regexpextra? "all" FindReaderSubmatchIndex that can iter?
|
||||
sre, err := gojqextra.CompileRegexp(re, "gimb", flags)
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
}
|
||||
|
||||
log.Printf("sbb: %#+v\n", sbb)
|
||||
|
||||
// TODO: error, bitio.Copy?
|
||||
|
||||
bbBytes := &bytes.Buffer{}
|
||||
_, _ = io.Copy(bbBytes, bb)
|
||||
|
||||
sbbBytes := &bytes.Buffer{}
|
||||
_, _ = io.Copy(sbbBytes, sbb)
|
||||
|
||||
// log.Printf("bbBytes.Bytes(): %#+v\n", bbBytes.Bytes())
|
||||
// log.Printf("sbbBytes.Bytes(): %#+v\n", sbbBytes.Bytes())
|
||||
|
||||
idx := bytes.Index(bbBytes.Bytes(), sbbBytes.Bytes())
|
||||
if idx == -1 {
|
||||
return gojq.NewIter()
|
||||
bb, err := bv.toBuffer()
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
}
|
||||
|
||||
bbo := bufferViewFromBuffer(bb, 8)
|
||||
// log.Printf("bbo: %#+v\n", bbo)
|
||||
var rr interface {
|
||||
io.RuneReader
|
||||
io.Seeker
|
||||
}
|
||||
if strings.Contains(flags, "b") {
|
||||
// byte mode, read each byte as a rune
|
||||
rr = ioextra.ByteRuneReader{RS: bb}
|
||||
} else {
|
||||
rr = ioextra.RuneReadSeeker{RS: bb}
|
||||
}
|
||||
|
||||
return gojq.NewIter(bbo)
|
||||
var off int64
|
||||
return iterFn(func() (interface{}, bool) {
|
||||
_, err = rr.Seek(off, io.SeekStart)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
// TODO: groups
|
||||
l := sre.FindReaderSubmatchIndex(rr)
|
||||
if l == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
matchBitOff := (off + int64(l[0])) * 8
|
||||
bbo := BufferView{
|
||||
bb: bv.bb,
|
||||
r: ranges.Range{
|
||||
Start: bv.r.Start + matchBitOff,
|
||||
Len: bb.Len() - matchBitOff,
|
||||
},
|
||||
unit: 8,
|
||||
}
|
||||
|
||||
off = off + int64(l[1])
|
||||
|
||||
return bbo, true
|
||||
})
|
||||
}
|
||||
|
||||
func (i *Interp) _hexdump(c interface{}, a []interface{}) gojq.Iter {
|
||||
opts, err := i.Options(a...)
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
}
|
||||
opts := i.OptionsEx(a...)
|
||||
bv, err := toBufferView(c)
|
||||
if err != nil {
|
||||
return gojq.NewIter(err)
|
||||
|
@ -21,6 +21,7 @@ def display: _display({});
|
||||
def d($opts): _display($opts);
|
||||
def d: _display({});
|
||||
def full($opts): _display({array_truncate: 0} + $opts);
|
||||
# TODO: rename, gets mixed up with f args often
|
||||
def full: full({});
|
||||
def f($opts): full($opts);
|
||||
def f: full;
|
||||
|
60
pkg/interp/grep.jq
Normal file
60
pkg/interp/grep.jq
Normal file
@ -0,0 +1,60 @@
|
||||
def _grep($v; filter_cond; string_cond; other_cond):
|
||||
if $v | type == "string" then
|
||||
( ..
|
||||
| select(filter_cond and string_cond)
|
||||
)
|
||||
else
|
||||
( ..
|
||||
| select(filter_cond and other_cond)
|
||||
)
|
||||
end;
|
||||
|
||||
def _value_grep_string_cond($v; $flags):
|
||||
( _tovalue
|
||||
| if type == "string" then test($v; $flags)
|
||||
else false
|
||||
end
|
||||
)? // false;
|
||||
def _value_grep_other_cond($v; $flags):
|
||||
( _tovalue
|
||||
| . == $v
|
||||
)? // false;
|
||||
def vgrep($v; $flags):
|
||||
_grep(
|
||||
$v;
|
||||
_is_scalar;
|
||||
_value_grep_string_cond($v; $flags);
|
||||
_value_grep_other_cond($v; $flags)
|
||||
);
|
||||
def vgrep($v): vgrep($v; "");
|
||||
|
||||
def _buf_grep_string_cond($v; $flags):
|
||||
(isempty(find($v; $flags)) | not)? // false;
|
||||
def bgrep($v; $flags):
|
||||
_grep(
|
||||
$v;
|
||||
_is_scalar;
|
||||
_buf_grep_string_cond($v; $flags);
|
||||
empty
|
||||
);
|
||||
def bgrep($v): bgrep($v; "");
|
||||
|
||||
def grep($v; $flags):
|
||||
_grep(
|
||||
$v;
|
||||
_is_scalar;
|
||||
_buf_grep_string_cond($v; $flags) or _value_grep_string_cond($v; $flags);
|
||||
_value_grep_other_cond($v; $flags)
|
||||
);
|
||||
def grep($v): grep($v; "");
|
||||
|
||||
def _field_grep_string_cond($v; $flags):
|
||||
(has("_name") and (._name | test($v; $flags)))? // false;
|
||||
def fgrep($v; $flags):
|
||||
_grep(
|
||||
$v;
|
||||
true;
|
||||
_field_grep_string_cond($v; $flags);
|
||||
empty
|
||||
);
|
||||
def fgrep($v): fgrep($v; "");
|
@ -1,3 +1,39 @@
|
||||
def _global_var($k): _global_state[$k];
|
||||
def _global_var($k; f): _global_state(_global_state | .[$k] |= f) | .[$k];
|
||||
|
||||
def _include_paths: _global_var("include_paths");
|
||||
def _include_paths(f): _global_var("include_paths"; f);
|
||||
|
||||
def _options_stack: _global_var("options_stack");
|
||||
def _options_stack(f): _global_var("options_stack"; f);
|
||||
|
||||
def _options_cache: _global_var("options_cache");
|
||||
def _options_cache(f): _global_var("options_cache"; f);
|
||||
|
||||
def _cli_last_expr_error: _global_var("cli_last_expr_error");
|
||||
def _cli_last_expr_error(f): _global_var("cli_last_expr_error"; f);
|
||||
|
||||
def _input_filename: _global_var("input_filename");
|
||||
def _input_filename(f): _global_var("input_filename"; f);
|
||||
|
||||
def _input_filenames: _global_var("input_filenames");
|
||||
def _input_filenames(f): _global_var("input_filenames"; f);
|
||||
|
||||
def _input_strings: _global_var("input_strings");
|
||||
def _input_strings(f): _global_var("input_strings"; f);
|
||||
|
||||
def _input_strings_lines: _global_var("input_strings_lines");
|
||||
def _input_strings_lines(f): _global_var("input_strings_lines"; f);
|
||||
|
||||
def _input_io_errors: _global_var("input_io_errors");
|
||||
def _input_io_errors(f): _global_var("input_io_errors"; f);
|
||||
|
||||
def _input_decode_errors: _global_var("input_decode_errors");
|
||||
def _input_decode_errors(f): _global_var("input_decode_errors"; f);
|
||||
|
||||
def _variables: _global_var("variables");
|
||||
def _variables(f): _global_var("variables"; f);
|
||||
|
||||
# eval f and finally eval fin even on empty or error
|
||||
def _finally(f; fin):
|
||||
( try f // (fin | empty)
|
||||
@ -23,21 +59,29 @@ def _recurse_break(f):
|
||||
else error
|
||||
end;
|
||||
|
||||
|
||||
# TODO: better way? what about nested eval errors?
|
||||
def _eval_is_compile_error: type == "object" and .error != null and .what != null;
|
||||
def _eval_compile_error_tostring:
|
||||
"\(.filename // "src"):\(.line):\(.column): \(.error)";
|
||||
def _eval($expr; $filename; f; on_error; on_compile_error):
|
||||
( try eval($expr; $filename) | f
|
||||
catch
|
||||
if _eval_is_compile_error then on_compile_error
|
||||
else on_error
|
||||
end
|
||||
try
|
||||
eval($expr; $filename) | f
|
||||
catch
|
||||
if _eval_is_compile_error then on_compile_error
|
||||
else on_error
|
||||
end;
|
||||
|
||||
# TODO: remove one symbolic value is done properly?
|
||||
def _tovalue:
|
||||
( if _is_decode_value then
|
||||
( ._symbol as $s
|
||||
| if $s != "" then $s end
|
||||
)
|
||||
end
|
||||
);
|
||||
|
||||
def _is_decode_value:
|
||||
try has("_root") catch false;
|
||||
def _is_scalar:
|
||||
type |. != "array" and . != "object";
|
||||
|
||||
# TODO: error value preview
|
||||
def _expected_decode_value:
|
||||
@ -50,36 +94,3 @@ def _decode_value(f):
|
||||
|
||||
def _error_str: "error: \(.)";
|
||||
def _errorln: ., "\n" | stderr;
|
||||
|
||||
def _global_var($k): _global_state[$k];
|
||||
def _global_var($k; f): _global_state(_global_state | .[$k] |= f) | .[$k];
|
||||
|
||||
def _include_paths: _global_var("include_paths");
|
||||
def _include_paths(f): _global_var("include_paths"; f);
|
||||
|
||||
def _options_stack: _global_var("options_stack");
|
||||
def _options_stack(f): _global_var("options_stack"; f);
|
||||
|
||||
def _cli_last_expr_error: _global_var("cli_last_expr_error");
|
||||
def _cli_last_expr_error(f): _global_var("cli_last_expr_error"; f);
|
||||
|
||||
def _input_filename: _global_var("input_filename");
|
||||
def _input_filename(f): _global_var("input_filename"; f);
|
||||
|
||||
def _input_filenames: _global_var("input_filenames");
|
||||
def _input_filenames(f): _global_var("input_filenames"; f);
|
||||
|
||||
def _input_strings: _global_var("input_strings");
|
||||
def _input_strings(f): _global_var("input_strings"; f);
|
||||
|
||||
def _input_strings_lines: _global_var("input_strings_lines");
|
||||
def _input_strings_lines(f): _global_var("input_strings_lines"; f);
|
||||
|
||||
def _input_io_errors: _global_var("input_io_errors");
|
||||
def _input_io_errors(f): _global_var("input_io_errors"; f);
|
||||
|
||||
def _input_decode_errors: _global_var("input_decode_errors");
|
||||
def _input_decode_errors(f): _global_var("input_decode_errors"; f);
|
||||
|
||||
def _variables: _global_var("variables");
|
||||
def _variables(f): _global_var("variables"; f);
|
||||
|
@ -32,8 +32,9 @@ import (
|
||||
|
||||
//go:embed interp.jq
|
||||
//go:embed internal.jq
|
||||
//go:embed options.jq
|
||||
//go:embed funcs.jq
|
||||
//go:embed grep.jq
|
||||
//go:embed options.jq
|
||||
//go:embed args.jq
|
||||
//go:embed query.jq
|
||||
//go:embed repl.jq
|
||||
@ -184,7 +185,7 @@ type ToBufferView interface {
|
||||
}
|
||||
|
||||
type JQValueEx interface {
|
||||
JQValueToGoJQEx(opts Options) interface{}
|
||||
JQValueToGoJQEx(optsFn func() Options) interface{}
|
||||
}
|
||||
|
||||
func valuePath(v *decode.Value) []interface{} {
|
||||
@ -356,10 +357,11 @@ func toBufferView(v interface{}) (BufferView, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func toValue(opts Options, v interface{}) (interface{}, bool) {
|
||||
// optsFn is a function as toValue is used by tovalue/0 so needs to be fast
|
||||
func toValue(optsFn func() Options, v interface{}) (interface{}, bool) {
|
||||
switch v := v.(type) {
|
||||
case JQValueEx:
|
||||
return v.JQValueToGoJQEx(opts), true
|
||||
return v.JQValueToGoJQEx(optsFn), true
|
||||
case gojq.JQValue:
|
||||
return v.JQValueToGoJQ(), true
|
||||
case nil, bool, float64, int, string, *big.Int, map[string]interface{}, []interface{}:
|
||||
@ -859,26 +861,30 @@ func (i *Interp) variables() map[string]interface{} {
|
||||
return variablesAny
|
||||
}
|
||||
|
||||
func (i *Interp) Options(fnOptsV ...interface{}) (Options, error) {
|
||||
vs, err := i.EvalFuncValues(i.evalContext.ctx, nil, "options", []interface{}{fnOptsV}, DiscardCtxWriter{Ctx: i.evalContext.ctx})
|
||||
if err != nil {
|
||||
return Options{}, err
|
||||
}
|
||||
if len(vs) < 1 {
|
||||
return Options{}, fmt.Errorf("no options value")
|
||||
}
|
||||
v := vs[0]
|
||||
if vErr, ok := v.(error); ok {
|
||||
return Options{}, vErr
|
||||
}
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return Options{}, fmt.Errorf("options value not a map: %v", m)
|
||||
}
|
||||
func (i *Interp) OptionsEx(fnOptsV ...interface{}) Options {
|
||||
opts := func() Options {
|
||||
vs, err := i.EvalFuncValues(i.evalContext.ctx, nil, "options", []interface{}{fnOptsV}, DiscardCtxWriter{Ctx: i.evalContext.ctx})
|
||||
if err != nil {
|
||||
return Options{}
|
||||
}
|
||||
if len(vs) < 1 {
|
||||
return Options{}
|
||||
}
|
||||
v := vs[0]
|
||||
if _, ok := v.(error); ok {
|
||||
return Options{}
|
||||
}
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return Options{}
|
||||
}
|
||||
var opts Options
|
||||
_ = mapstructure.Decode(m, &opts)
|
||||
opts.Depth = num.MaxInt(0, opts.Depth)
|
||||
|
||||
return opts
|
||||
}()
|
||||
|
||||
var opts Options
|
||||
_ = mapstructure.Decode(m, &opts)
|
||||
opts.Depth = num.MaxInt(0, opts.Depth)
|
||||
opts.ArrayTruncate = num.MaxInt(0, opts.ArrayTruncate)
|
||||
opts.AddrBase = num.ClampInt(2, 36, opts.AddrBase)
|
||||
opts.SizeBase = num.ClampInt(2, 36, opts.SizeBase)
|
||||
@ -887,7 +893,11 @@ func (i *Interp) Options(fnOptsV ...interface{}) (Options, error) {
|
||||
opts.Decorator = decoratorFromOptions(opts)
|
||||
opts.BitsFormatFn = bitsFormatFnFromOptions(opts)
|
||||
|
||||
return opts, nil
|
||||
return opts
|
||||
}
|
||||
|
||||
func (i *Interp) Options() Options {
|
||||
return i.OptionsEx(nil)
|
||||
}
|
||||
|
||||
func (i *Interp) NewColorJSON(opts Options) (*colorjson.Encoder, error) {
|
||||
@ -901,7 +911,7 @@ func (i *Interp) NewColorJSON(opts Options) (*colorjson.Encoder, error) {
|
||||
false,
|
||||
indent,
|
||||
func(v interface{}) interface{} {
|
||||
if v, ok := toValue(opts, v); ok {
|
||||
if v, ok := toValue(func() Options { return opts }, v); ok {
|
||||
return v
|
||||
}
|
||||
panic(fmt.Sprintf("toValue not a JQValue value: %#v", v))
|
||||
|
@ -1,5 +1,6 @@
|
||||
include "internal";
|
||||
include "funcs";
|
||||
include "grep";
|
||||
include "options";
|
||||
include "args";
|
||||
include "repl";
|
||||
|
62
pkg/interp/testdata/grep.fqtest
vendored
Normal file
62
pkg/interp/testdata/grep.fqtest
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
$ fq -i -d mp3 . /test.mp3
|
||||
mp3> grep(44100, "ID", "^ID3$", "^ID.?$", "Info", "magic", "\u00ff")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x20| 40| @|.frames[0].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0xe0| 50 | P |.frames[1].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x1b0| 52 | R |.frames[2].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x40| 49 6e 66 6f | Info |.frames[0].xing.header: "Info"
|
||||
mp3> vgrep(44100, "ID", "^ID3$", "^ID.?$", "Info", "magic", "\u00ff")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x20| 40| @|.frames[0].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0xe0| 50 | P |.frames[1].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x1b0| 52 | R |.frames[2].header.sample_rate: 44100
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x40| 49 6e 66 6f | Info |.frames[0].xing.header: "Info"
|
||||
mp3> fgrep(44100, "ID", "^ID3$", "^ID.?$", "Info", "magic", "\u00ff")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
mp3> bgrep(44100, "ID", "^ID3$", "^ID.?$", "Info", "magic", "\u00ff")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|49 44 33 |ID3 |.headers[0].magic: "ID3" (Correct)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x40| 49 6e 66 6f | Info |.frames[0].xing.header: "Info"
|
||||
mp3> "64ff65ff66" | hex | bgrep("\u00ff"; "b")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0|64 ff 65 ff 66| |d.e.f| |.: none 0x0-0x4.7 (5)
|
||||
mp3> "64ff65ff66" | hex | find("\u00ff"; "b")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| ff 65 ff 66| | .e.f| |.: none 0x1-0x4.7 (4)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| ff 66| | .f| |.: none 0x3-0x4.7 (2)
|
||||
mp3> "aöaöa" | find("ö")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| c3 b6 61 c3 b6 61| | ..a..a| |.: none 0x1-0x6.7 (6)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| c3 b6 61| | ..a| |.: none 0x4-0x6.7 (3)
|
||||
mp3> "aöaöa" | find("\u00c3"; "b")
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| c3 b6 61 c3 b6 61| | ..a..a| |.: none 0x1-0x6.7 (6)
|
||||
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|
|
||||
0x0| c3 b6 61| | ..a| |.: none 0x4-0x6.7 (3)
|
||||
mp3> ^D
|
@ -33,9 +33,11 @@ func (err notUpdateableError) Error() string {
|
||||
}
|
||||
|
||||
// TODO: rename
|
||||
type valueIf interface {
|
||||
type DecodeValue interface {
|
||||
Value
|
||||
ToBufferView
|
||||
|
||||
DecodeValue() *decode.Value
|
||||
}
|
||||
|
||||
func valueKey(name string, a, b func(name string) interface{}) interface{} {
|
||||
@ -55,7 +57,7 @@ func valueHas(key interface{}, a func(name string) interface{}, b func(key inter
|
||||
return b(key)
|
||||
}
|
||||
|
||||
func makeDecodeValue(dv *decode.Value) valueIf {
|
||||
func makeDecodeValue(dv *decode.Value) DecodeValue {
|
||||
switch vv := dv.V.(type) {
|
||||
case decode.Array:
|
||||
return NewArrayDecodeValue(dv, vv)
|
||||
@ -113,7 +115,6 @@ func makeDecodeValue(dv *decode.Value) valueIf {
|
||||
JQValue: gojqextra.Null{},
|
||||
decodeValueBase: decodeValueBase{dv},
|
||||
}
|
||||
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
@ -123,6 +124,10 @@ type decodeValueBase struct {
|
||||
dv *decode.Value
|
||||
}
|
||||
|
||||
func (dvb decodeValueBase) DecodeValue() *decode.Value {
|
||||
return dvb.dv
|
||||
}
|
||||
|
||||
func (dvb decodeValueBase) DisplayName() string {
|
||||
if dvb.dv.Format != nil {
|
||||
return dvb.dv.Format.Name
|
||||
@ -226,7 +231,7 @@ func (dvb decodeValueBase) JQValueKey(name string) interface{} {
|
||||
return expectedExtkeyError{Key: name}
|
||||
}
|
||||
|
||||
var _ valueIf = decodeValue{}
|
||||
var _ DecodeValue = decodeValue{}
|
||||
|
||||
type decodeValue struct {
|
||||
gojq.JQValue
|
||||
@ -242,7 +247,8 @@ func (v decodeValue) JQValueHas(key interface{}) interface{} {
|
||||
|
||||
// string (*bitio.Buffer)
|
||||
|
||||
var _ valueIf = BufferDecodeValue{}
|
||||
var _ DecodeValue = BufferDecodeValue{}
|
||||
var _ JQValueEx = BufferDecodeValue{}
|
||||
|
||||
type BufferDecodeValue struct {
|
||||
gojqextra.Base
|
||||
@ -300,8 +306,8 @@ func (v BufferDecodeValue) JQValueToString() interface{} {
|
||||
func (v BufferDecodeValue) JQValueToGoJQ() interface{} {
|
||||
return v.JQValueToString()
|
||||
}
|
||||
func (v BufferDecodeValue) JQValueToGoJQEx(opts Options) interface{} {
|
||||
s, err := opts.BitsFormatFn(v.Buffer.Copy())
|
||||
func (v BufferDecodeValue) JQValueToGoJQEx(optsFn func() Options) interface{} {
|
||||
s, err := optsFn().BitsFormatFn(v.Buffer.Copy())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -310,7 +316,7 @@ func (v BufferDecodeValue) JQValueToGoJQEx(opts Options) interface{} {
|
||||
|
||||
// decode value array
|
||||
|
||||
var _ valueIf = ArrayDecodeValue{}
|
||||
var _ DecodeValue = ArrayDecodeValue{}
|
||||
|
||||
type ArrayDecodeValue struct {
|
||||
gojqextra.Base
|
||||
@ -384,7 +390,7 @@ func (v ArrayDecodeValue) JQValueToGoJQ() interface{} {
|
||||
|
||||
// decode value struct
|
||||
|
||||
var _ valueIf = StructDecodeValue{}
|
||||
var _ DecodeValue = StructDecodeValue{}
|
||||
|
||||
type StructDecodeValue struct {
|
||||
gojqextra.Base
|
||||
|
Loading…
Reference in New Issue
Block a user