1
1
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:
Mattias Wadman 2021-09-23 18:35:04 +02:00
parent 3044fefb5b
commit e86b45bd1a
15 changed files with 440 additions and 124 deletions

View File

@ -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"

View File

@ -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

View 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
}

View 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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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 ""

View File

@ -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)

View File

@ -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
View 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; "");

View File

@ -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);

View File

@ -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))

View File

@ -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
View 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

View File

@ -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