mirror of https://github.com/stashapp/stash.git
Upgrade gjson module (#1610)
This commit is contained in:
parent
eaa23240f7
commit
518be8ee70
4
go.mod
4
go.mod
|
@ -21,7 +21,6 @@ require (
|
|||
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
|
||||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/json-iterator/go v1.1.9
|
||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
|
||||
github.com/remeh/sizedwaitgroup v1.0.0
|
||||
|
@ -32,7 +31,8 @@ require (
|
|||
github.com/spf13/pflag v1.0.3
|
||||
github.com/spf13/viper v1.7.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tidwall/gjson v1.6.0
|
||||
github.com/tidwall/gjson v1.8.1
|
||||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.0.1
|
||||
github.com/vektra/mockery/v2 v2.2.1
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
|
|
31
go.sum
31
go.sum
|
@ -75,14 +75,9 @@ github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBT
|
|||
github.com/bradfitz/iter v0.0.0-20140124041915-454541ec3da2/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/bradfitz/iter v0.0.0-20190303215204-33e6a9893b0c/go.mod h1:PyRFw1Lt2wKX4ZVSQ2mk+PeDa1rxyObEDlApuIsUKuo=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chromedp/cdproto v0.0.0-20200116234248-4da64dd111ac/go.mod h1:PfAWWKJqjlGFYJEidUM6aVIWPr0EpobeyVWEEmplX7g=
|
||||
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c h1:qM1xzKK8kc93zKPkxK4iqtjksqDDrU6g9wGnr30jyLo=
|
||||
github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c/go.mod h1:E6LPWRdIJc11h/di5p0rwvRmUYbhGpBEH7ZbPfzDIOE=
|
||||
github.com/chromedp/cdproto v0.0.0-20210526005521-9e51b9051fd0/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
||||
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84 h1:Xxl4imt7LA3SbkrlIH5mm+mbzsv0tpHomLASTPINlvQ=
|
||||
github.com/chromedp/cdproto v0.0.0-20210622022015-fe1827b46b84/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U=
|
||||
github.com/chromedp/chromedp v0.5.3 h1:F9LafxmYpsQhWQBdCs+6Sret1zzeeFyHS5LkRF//Ffg=
|
||||
github.com/chromedp/chromedp v0.5.3/go.mod h1:YLdPtndaHQ4rCpSpBG+IPpy9JvX0VD+7aaLxYgYj28w=
|
||||
github.com/chromedp/chromedp v0.7.3 h1:FvgJICfjvXtDX+miuMUY0NHuY8zQvjS/TcEQEG6Ldzs=
|
||||
github.com/chromedp/chromedp v0.7.3/go.mod h1:9gC521Yzgrk078Ulv6KIgG7hJ2x9aWrxMBBobTFk30A=
|
||||
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
|
||||
|
@ -373,16 +368,10 @@ github.com/gobuffalo/uuid v2.0.5+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw
|
|||
github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM=
|
||||
github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc=
|
||||
github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||
github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/gobwas/ws v1.1.0-rc.5 h1:QOAag7FoBaBYYHRqzqkhhd8fq5RTubvI4v3Ft/gDVVQ=
|
||||
github.com/gobwas/ws v1.1.0-rc.5/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
|
||||
github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0=
|
||||
|
@ -431,6 +420,7 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
|
|||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
|
@ -516,8 +506,6 @@ github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46s
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08 h1:V0an7KRw92wmJysvFvtqtKMAPmvS5O0jtB0nYo6t+gs=
|
||||
github.com/knq/sysutil v0.0.0-20191005231841-15668db23d08/go.mod h1:dFWs1zEqDjFtnBXsd1vPOZaLsESovai349994nHx3e0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
@ -538,9 +526,6 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
|
|||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8=
|
||||
github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM=
|
||||
|
@ -770,13 +755,15 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
|
||||
github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc=
|
||||
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
|
||||
github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
language: go
|
|
@ -3,13 +3,10 @@
|
|||
src="logo.png"
|
||||
width="240" height="78" border="0" alt="GJSON">
|
||||
<br>
|
||||
<a href="https://travis-ci.org/tidwall/gjson"><img src="https://img.shields.io/travis/tidwall/gjson.svg?style=flat-square" alt="Build Status"></a>
|
||||
<a href="https://godoc.org/github.com/tidwall/gjson"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||
<a href="http://tidwall.com/gjson-play"><img src="https://img.shields.io/badge/%F0%9F%8F%90-playground-9900cc.svg?style=flat-square" alt="GJSON Playground"></a>
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
<p align="center">get json values quickly</a></p>
|
||||
|
||||
GJSON is a Go package that provides a [fast](#performance) and [simple](#get-a-value) way to get values from a json document.
|
||||
|
@ -153,10 +150,6 @@ result.Less(token Result, caseSensitive bool) bool
|
|||
|
||||
The `result.Value()` function returns an `interface{}` which requires type assertion and is one of the following Go types:
|
||||
|
||||
The `result.Array()` function returns back an array of values.
|
||||
If the result represents a non-existent value, then an empty array will be returned.
|
||||
If the result is not a JSON array, the return value will be an array containing one result.
|
||||
|
||||
```go
|
||||
boolean >> bool
|
||||
number >> float64
|
||||
|
@ -166,6 +159,10 @@ array >> []interface{}
|
|||
object >> map[string]interface{}
|
||||
```
|
||||
|
||||
The `result.Array()` function returns back an array of values.
|
||||
If the result represents a non-existent value, then an empty array will be returned.
|
||||
If the result is not a JSON array, the return value will be an array containing one result.
|
||||
|
||||
### 64-bit integers
|
||||
|
||||
The `result.Int()` and `result.Uint()` calls are capable of reading all 64 bits, allowing for large JSON integers.
|
||||
|
@ -476,7 +473,7 @@ JSON document used:
|
|||
}
|
||||
```
|
||||
|
||||
Each operation was rotated though one of the following search paths:
|
||||
Each operation was rotated through one of the following search paths:
|
||||
|
||||
```
|
||||
widget.window.name
|
||||
|
@ -484,12 +481,4 @@ widget.image.hOffset
|
|||
widget.text.onMouseUp
|
||||
```
|
||||
|
||||
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be be found [here](https://github.com/tidwall/gjson-benchmarks).*
|
||||
|
||||
|
||||
## Contact
|
||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||
|
||||
## License
|
||||
|
||||
GJSON source code is available under the MIT [License](/LICENSE).
|
||||
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.8 and can be found [here](https://github.com/tidwall/gjson-benchmarks).*
|
||||
|
|
|
@ -77,14 +77,21 @@ Special purpose characters, such as `.`, `*`, and `?` can be escaped with `\`.
|
|||
fav\.movie "Deer Hunter"
|
||||
```
|
||||
|
||||
You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in source code.
|
||||
You'll also need to make sure that the `\` character is correctly escaped when hardcoding a path in you source code.
|
||||
|
||||
```go
|
||||
res := gjson.Get(json, "fav\\.movie") // must escape the slash
|
||||
res := gjson.Get(json, `fav\.movie`) // no need to escape the slash
|
||||
|
||||
// Go
|
||||
val := gjson.Get(json, "fav\\.movie") // must escape the slash
|
||||
val := gjson.Get(json, `fav\.movie`) // no need to escape the slash
|
||||
```
|
||||
|
||||
```rust
|
||||
// Rust
|
||||
let val = gjson::get(json, "fav\\.movie") // must escape the slash
|
||||
let val = gjson::get(json, r#"fav\.movie"#) // no need to escape the slash
|
||||
```
|
||||
|
||||
|
||||
### Arrays
|
||||
|
||||
The `#` character allows for digging into JSON Arrays.
|
||||
|
@ -128,6 +135,37 @@ changed in v1.3.0 as to avoid confusion with the new [multipath](#multipaths)
|
|||
syntax. For backwards compatibility, `#[...]` will continue to work until the
|
||||
next major release.*
|
||||
|
||||
The `~` (tilde) operator will convert a value to a boolean before comparison.
|
||||
|
||||
For example, using the following JSON:
|
||||
|
||||
```json
|
||||
{
|
||||
"vals": [
|
||||
{ "a": 1, "b": true },
|
||||
{ "a": 2, "b": true },
|
||||
{ "a": 3, "b": false },
|
||||
{ "a": 4, "b": "0" },
|
||||
{ "a": 5, "b": 0 },
|
||||
{ "a": 6, "b": "1" },
|
||||
{ "a": 7, "b": 1 },
|
||||
{ "a": 8, "b": "true" },
|
||||
{ "a": 9, "b": false },
|
||||
{ "a": 10, "b": null },
|
||||
{ "a": 11 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
You can now query for all true(ish) or false(ish) values:
|
||||
|
||||
```
|
||||
vals.#(b==~true)#.a >> [1,2,6,7,8]
|
||||
vals.#(b==~false)#.a >> [3,4,5,9,10,11]
|
||||
```
|
||||
|
||||
The last value which was non-existent is treated as `false`
|
||||
|
||||
### Dot vs Pipe
|
||||
|
||||
The `.` is standard separator, but it's also possible to use a `|`.
|
||||
|
@ -248,6 +286,8 @@ gjson.AddModifier("case", func(json, arg string) string {
|
|||
"children.@case:lower.@reverse" ["jack","alex","sara"]
|
||||
```
|
||||
|
||||
*Note: Custom modifiers are not yet available in the Rust version*
|
||||
|
||||
### Multipaths
|
||||
|
||||
Starting with v1.3.0, GJSON added the ability to join multiple paths together
|
||||
|
|
|
@ -3,7 +3,6 @@ package gjson
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -106,7 +105,8 @@ func (t Result) Bool() bool {
|
|||
case True:
|
||||
return true
|
||||
case String:
|
||||
return t.Str != "" && t.Str != "0" && t.Str != "false"
|
||||
b, _ := strconv.ParseBool(strings.ToLower(t.Str))
|
||||
return b
|
||||
case Number:
|
||||
return t.Num != 0
|
||||
}
|
||||
|
@ -124,16 +124,17 @@ func (t Result) Int() int64 {
|
|||
return n
|
||||
case Number:
|
||||
// try to directly convert the float64 to int64
|
||||
n, ok := floatToInt(t.Num)
|
||||
if !ok {
|
||||
// now try to parse the raw string
|
||||
n, ok = parseInt(t.Raw)
|
||||
if !ok {
|
||||
// fallback to a standard conversion
|
||||
return int64(t.Num)
|
||||
}
|
||||
i, ok := safeInt(t.Num)
|
||||
if ok {
|
||||
return i
|
||||
}
|
||||
return n
|
||||
// now try to parse the raw string
|
||||
i, ok = parseInt(t.Raw)
|
||||
if ok {
|
||||
return i
|
||||
}
|
||||
// fallback to a standard conversion
|
||||
return int64(t.Num)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,16 +150,17 @@ func (t Result) Uint() uint64 {
|
|||
return n
|
||||
case Number:
|
||||
// try to directly convert the float64 to uint64
|
||||
n, ok := floatToUint(t.Num)
|
||||
if !ok {
|
||||
// now try to parse the raw string
|
||||
n, ok = parseUint(t.Raw)
|
||||
if !ok {
|
||||
// fallback to a standard conversion
|
||||
return uint64(t.Num)
|
||||
}
|
||||
i, ok := safeInt(t.Num)
|
||||
if ok && i >= 0 {
|
||||
return uint64(i)
|
||||
}
|
||||
return n
|
||||
// now try to parse the raw string
|
||||
u, ok := parseUint(t.Raw)
|
||||
if ok {
|
||||
return u
|
||||
}
|
||||
// fallback to a standard conversion
|
||||
return uint64(t.Num)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -495,6 +497,9 @@ func squash(json string) string {
|
|||
}
|
||||
}
|
||||
if depth == 0 {
|
||||
if i >= len(json) {
|
||||
return json
|
||||
}
|
||||
return json[:i+1]
|
||||
}
|
||||
case '{', '[', '(':
|
||||
|
@ -579,7 +584,7 @@ func tostr(json string) (raw string, str string) {
|
|||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
return json[:i+1], unescape(json[1:i])
|
||||
}
|
||||
}
|
||||
var ret string
|
||||
|
@ -709,10 +714,10 @@ type arrayPathResult struct {
|
|||
alogkey string
|
||||
query struct {
|
||||
on bool
|
||||
all bool
|
||||
path string
|
||||
op string
|
||||
value string
|
||||
all bool
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -726,8 +731,13 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
|||
}
|
||||
if path[i] == '.' {
|
||||
r.part = path[:i]
|
||||
r.path = path[i+1:]
|
||||
r.more = true
|
||||
if !r.arrch && i < len(path)-1 && isDotPiperChar(path[i+1]) {
|
||||
r.pipe = path[i+1:]
|
||||
r.piped = true
|
||||
} else {
|
||||
r.path = path[i+1:]
|
||||
r.more = true
|
||||
}
|
||||
return
|
||||
}
|
||||
if path[i] == '#' {
|
||||
|
@ -740,120 +750,27 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
|||
} else if path[1] == '[' || path[1] == '(' {
|
||||
// query
|
||||
r.query.on = true
|
||||
if true {
|
||||
qpath, op, value, _, fi, ok := parseQuery(path[i:])
|
||||
if !ok {
|
||||
// bad query, end now
|
||||
break
|
||||
}
|
||||
r.query.path = qpath
|
||||
r.query.op = op
|
||||
r.query.value = value
|
||||
i = fi - 1
|
||||
if i+1 < len(path) && path[i+1] == '#' {
|
||||
r.query.all = true
|
||||
}
|
||||
} else {
|
||||
var end byte
|
||||
if path[1] == '[' {
|
||||
end = ']'
|
||||
} else {
|
||||
end = ')'
|
||||
}
|
||||
i += 2
|
||||
// whitespace
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] > ' ' {
|
||||
break
|
||||
}
|
||||
}
|
||||
s := i
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] <= ' ' ||
|
||||
path[i] == '!' ||
|
||||
path[i] == '=' ||
|
||||
path[i] == '<' ||
|
||||
path[i] == '>' ||
|
||||
path[i] == '%' ||
|
||||
path[i] == end {
|
||||
break
|
||||
}
|
||||
}
|
||||
r.query.path = path[s:i]
|
||||
// whitespace
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] > ' ' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i < len(path) {
|
||||
s = i
|
||||
if path[i] == '!' {
|
||||
if i < len(path)-1 && (path[i+1] == '=' ||
|
||||
path[i+1] == '%') {
|
||||
i++
|
||||
}
|
||||
} else if path[i] == '<' || path[i] == '>' {
|
||||
if i < len(path)-1 && path[i+1] == '=' {
|
||||
i++
|
||||
}
|
||||
} else if path[i] == '=' {
|
||||
if i < len(path)-1 && path[i+1] == '=' {
|
||||
s++
|
||||
i++
|
||||
}
|
||||
}
|
||||
i++
|
||||
r.query.op = path[s:i]
|
||||
// whitespace
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] > ' ' {
|
||||
break
|
||||
}
|
||||
}
|
||||
s = i
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] == '"' {
|
||||
i++
|
||||
s2 := i
|
||||
for ; i < len(path); i++ {
|
||||
if path[i] > '\\' {
|
||||
continue
|
||||
}
|
||||
if path[i] == '"' {
|
||||
// look for an escaped slash
|
||||
if path[i-1] == '\\' {
|
||||
n := 0
|
||||
for j := i - 2; j > s2-1; j-- {
|
||||
if path[j] != '\\' {
|
||||
break
|
||||
}
|
||||
n++
|
||||
}
|
||||
if n%2 == 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if path[i] == end {
|
||||
if i+1 < len(path) && path[i+1] == '#' {
|
||||
r.query.all = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if i > len(path) {
|
||||
i = len(path)
|
||||
}
|
||||
v := path[s:i]
|
||||
for len(v) > 0 && v[len(v)-1] <= ' ' {
|
||||
v = v[:len(v)-1]
|
||||
}
|
||||
r.query.value = v
|
||||
qpath, op, value, _, fi, vesc, ok :=
|
||||
parseQuery(path[i:])
|
||||
if !ok {
|
||||
// bad query, end now
|
||||
break
|
||||
}
|
||||
if len(value) > 2 && value[0] == '"' &&
|
||||
value[len(value)-1] == '"' {
|
||||
value = value[1 : len(value)-1]
|
||||
if vesc {
|
||||
value = unescape(value)
|
||||
}
|
||||
}
|
||||
r.query.path = qpath
|
||||
r.query.op = op
|
||||
r.query.value = value
|
||||
|
||||
i = fi - 1
|
||||
if i+1 < len(path) && path[i+1] == '#' {
|
||||
r.query.all = true
|
||||
}
|
||||
}
|
||||
}
|
||||
continue
|
||||
|
@ -879,11 +796,11 @@ func parseArrayPath(path string) (r arrayPathResult) {
|
|||
// # middle
|
||||
// .cap # right
|
||||
func parseQuery(query string) (
|
||||
path, op, value, remain string, i int, ok bool,
|
||||
path, op, value, remain string, i int, vesc, ok bool,
|
||||
) {
|
||||
if len(query) < 2 || query[0] != '#' ||
|
||||
(query[1] != '(' && query[1] != '[') {
|
||||
return "", "", "", "", i, false
|
||||
return "", "", "", "", i, false, false
|
||||
}
|
||||
i = 2
|
||||
j := 0 // start of value part
|
||||
|
@ -911,6 +828,7 @@ func parseQuery(query string) (
|
|||
i++
|
||||
for ; i < len(query); i++ {
|
||||
if query[i] == '\\' {
|
||||
vesc = true
|
||||
i++
|
||||
} else if query[i] == '"' {
|
||||
break
|
||||
|
@ -919,7 +837,7 @@ func parseQuery(query string) (
|
|||
}
|
||||
}
|
||||
if depth > 0 {
|
||||
return "", "", "", "", i, false
|
||||
return "", "", "", "", i, false, false
|
||||
}
|
||||
if j > 0 {
|
||||
path = trim(query[2:j])
|
||||
|
@ -956,7 +874,7 @@ func parseQuery(query string) (
|
|||
path = trim(query[2:i])
|
||||
remain = query[i+1:]
|
||||
}
|
||||
return path, op, value, remain, i + 1, true
|
||||
return path, op, value, remain, i + 1, vesc, true
|
||||
}
|
||||
|
||||
func trim(s string) string {
|
||||
|
@ -973,6 +891,11 @@ right:
|
|||
return s
|
||||
}
|
||||
|
||||
// peek at the next byte and see if it's a '@', '[', or '{'.
|
||||
func isDotPiperChar(c byte) bool {
|
||||
return !DisableModifiers && (c == '@' || c == '[' || c == '{')
|
||||
}
|
||||
|
||||
type objectPathResult struct {
|
||||
part string
|
||||
path string
|
||||
|
@ -991,12 +914,8 @@ func parseObjectPath(path string) (r objectPathResult) {
|
|||
return
|
||||
}
|
||||
if path[i] == '.' {
|
||||
// peek at the next byte and see if it's a '@', '[', or '{'.
|
||||
r.part = path[:i]
|
||||
if !DisableModifiers &&
|
||||
i < len(path)-1 &&
|
||||
(path[i+1] == '@' ||
|
||||
path[i+1] == '[' || path[i+1] == '{') {
|
||||
if i < len(path)-1 && isDotPiperChar(path[i+1]) {
|
||||
r.pipe = path[i+1:]
|
||||
r.piped = true
|
||||
} else {
|
||||
|
@ -1026,14 +945,11 @@ func parseObjectPath(path string) (r objectPathResult) {
|
|||
continue
|
||||
} else if path[i] == '.' {
|
||||
r.part = string(epart)
|
||||
// peek at the next byte and see if it's a '@' modifier
|
||||
if !DisableModifiers &&
|
||||
i < len(path)-1 && path[i+1] == '@' {
|
||||
if i < len(path)-1 && isDotPiperChar(path[i+1]) {
|
||||
r.pipe = path[i+1:]
|
||||
r.piped = true
|
||||
} else {
|
||||
r.path = path[i+1:]
|
||||
r.more = true
|
||||
}
|
||||
r.more = true
|
||||
return
|
||||
|
@ -1258,8 +1174,14 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
|
|||
}
|
||||
func queryMatches(rp *arrayPathResult, value Result) bool {
|
||||
rpv := rp.query.value
|
||||
if len(rpv) > 2 && rpv[0] == '"' && rpv[len(rpv)-1] == '"' {
|
||||
rpv = rpv[1 : len(rpv)-1]
|
||||
if len(rpv) > 0 && rpv[0] == '~' {
|
||||
// convert to bool
|
||||
rpv = rpv[1:]
|
||||
if value.Bool() {
|
||||
value = Result{Type: True}
|
||||
} else {
|
||||
value = Result{Type: False}
|
||||
}
|
||||
}
|
||||
if !value.Exists() {
|
||||
return false
|
||||
|
@ -1398,7 +1320,6 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for i < len(c.json)+1 {
|
||||
if !rp.arrch {
|
||||
pmatch = partidx == h
|
||||
|
@ -1600,10 +1521,17 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||
c.calcd = true
|
||||
return i + 1, true
|
||||
}
|
||||
if len(multires) > 0 && !c.value.Exists() {
|
||||
c.value = Result{
|
||||
Raw: string(append(multires, ']')),
|
||||
Type: JSON,
|
||||
if !c.value.Exists() {
|
||||
if len(multires) > 0 {
|
||||
c.value = Result{
|
||||
Raw: string(append(multires, ']')),
|
||||
Type: JSON,
|
||||
}
|
||||
} else if rp.query.all {
|
||||
c.value = Result{
|
||||
Raw: "[]",
|
||||
Type: JSON,
|
||||
}
|
||||
}
|
||||
}
|
||||
return i + 1, false
|
||||
|
@ -1835,7 +1763,7 @@ type parseContext struct {
|
|||
// A path is in dot syntax, such as "name.last" or "age".
|
||||
// When the value is found it's returned immediately.
|
||||
//
|
||||
// A path is a series of keys searated by a dot.
|
||||
// A path is a series of keys separated by a dot.
|
||||
// A key may contain special wildcard characters '*' and '?'.
|
||||
// To access an array value use the index as the key.
|
||||
// To get the number of elements in an array or to access a child path, use
|
||||
|
@ -1944,7 +1872,6 @@ func Get(json, path string) Result {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
var i int
|
||||
var c = &parseContext{json: json}
|
||||
if len(path) >= 2 && path[0] == '.' && path[1] == '.' {
|
||||
|
@ -2169,11 +2096,6 @@ func parseAny(json string, i int, hit bool) (int, Result, bool) {
|
|||
return i, res, false
|
||||
}
|
||||
|
||||
var ( // used for testing
|
||||
testWatchForFallback bool
|
||||
testLastWasFallback bool
|
||||
)
|
||||
|
||||
// GetMany searches json for the multiple paths.
|
||||
// The return value is a Result array where the number of items
|
||||
// will be equal to the number of input paths.
|
||||
|
@ -2374,6 +2296,12 @@ func validnumber(data []byte, i int) (outi int, ok bool) {
|
|||
// sign
|
||||
if data[i] == '-' {
|
||||
i++
|
||||
if i == len(data) {
|
||||
return i, false
|
||||
}
|
||||
if data[i] < '0' || data[i] > '9' {
|
||||
return i, false
|
||||
}
|
||||
}
|
||||
// int
|
||||
if i == len(data) {
|
||||
|
@ -2524,25 +2452,14 @@ func parseInt(s string) (n int64, ok bool) {
|
|||
return n, true
|
||||
}
|
||||
|
||||
const minUint53 = 0
|
||||
const maxUint53 = 4503599627370495
|
||||
const minInt53 = -2251799813685248
|
||||
const maxInt53 = 2251799813685247
|
||||
|
||||
func floatToUint(f float64) (n uint64, ok bool) {
|
||||
n = uint64(f)
|
||||
if float64(n) == f && n >= minUint53 && n <= maxUint53 {
|
||||
return n, true
|
||||
// safeInt validates a given JSON number
|
||||
// ensures it lies within the minimum and maximum representable JSON numbers
|
||||
func safeInt(f float64) (n int64, ok bool) {
|
||||
// https://tc39.es/ecma262/#sec-number.min_safe_integer || https://tc39.es/ecma262/#sec-number.max_safe_integer
|
||||
if f < -9007199254740991 || f > 9007199254740991 {
|
||||
return 0, false
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func floatToInt(f float64) (n int64, ok bool) {
|
||||
n = int64(f)
|
||||
if float64(n) == f && n >= minInt53 && n <= maxInt53 {
|
||||
return n, true
|
||||
}
|
||||
return 0, false
|
||||
return int64(f), true
|
||||
}
|
||||
|
||||
// execModifier parses the path to find a matching modifier function.
|
||||
|
@ -2600,7 +2517,7 @@ func execModifier(json, path string) (pathOut, res string, ok bool) {
|
|||
// unwrap removes the '[]' or '{}' characters around json
|
||||
func unwrap(json string) string {
|
||||
json = trim(json)
|
||||
if len(json) >= 2 && json[0] == '[' || json[0] == '{' {
|
||||
if len(json) >= 2 && (json[0] == '[' || json[0] == '{') {
|
||||
json = json[1 : len(json)-1]
|
||||
}
|
||||
return json
|
||||
|
@ -2632,6 +2549,26 @@ func ModifierExists(name string, fn func(json, arg string) string) bool {
|
|||
return ok
|
||||
}
|
||||
|
||||
// cleanWS remove any non-whitespace from string
|
||||
func cleanWS(s string) string {
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
continue
|
||||
default:
|
||||
var s2 []byte
|
||||
for i := 0; i < len(s); i++ {
|
||||
switch s[i] {
|
||||
case ' ', '\t', '\n', '\r':
|
||||
s2 = append(s2, s[i])
|
||||
}
|
||||
}
|
||||
return string(s2)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// @pretty modifier makes the json look nice.
|
||||
func modPretty(json, arg string) string {
|
||||
if len(arg) > 0 {
|
||||
|
@ -2641,9 +2578,9 @@ func modPretty(json, arg string) string {
|
|||
case "sortKeys":
|
||||
opts.SortKeys = value.Bool()
|
||||
case "indent":
|
||||
opts.Indent = value.String()
|
||||
opts.Indent = cleanWS(value.String())
|
||||
case "prefix":
|
||||
opts.Prefix = value.String()
|
||||
opts.Prefix = cleanWS(value.String())
|
||||
case "width":
|
||||
opts.Width = int(value.Int())
|
||||
}
|
||||
|
@ -2729,19 +2666,24 @@ func modFlatten(json, arg string) string {
|
|||
out = append(out, '[')
|
||||
var idx int
|
||||
res.ForEach(func(_, value Result) bool {
|
||||
if idx > 0 {
|
||||
out = append(out, ',')
|
||||
}
|
||||
var raw string
|
||||
if value.IsArray() {
|
||||
if deep {
|
||||
out = append(out, unwrap(modFlatten(value.Raw, arg))...)
|
||||
raw = unwrap(modFlatten(value.Raw, arg))
|
||||
} else {
|
||||
out = append(out, unwrap(value.Raw)...)
|
||||
raw = unwrap(value.Raw)
|
||||
}
|
||||
} else {
|
||||
out = append(out, value.Raw...)
|
||||
raw = value.Raw
|
||||
}
|
||||
raw = strings.TrimSpace(raw)
|
||||
if len(raw) > 0 {
|
||||
if idx > 0 {
|
||||
out = append(out, ',')
|
||||
}
|
||||
out = append(out, raw...)
|
||||
idx++
|
||||
}
|
||||
idx++
|
||||
return true
|
||||
})
|
||||
out = append(out, ']')
|
||||
|
@ -2825,6 +2767,19 @@ func modValid(json, arg string) string {
|
|||
return json
|
||||
}
|
||||
|
||||
// stringHeader instead of reflect.StringHeader
|
||||
type stringHeader struct {
|
||||
data unsafe.Pointer
|
||||
len int
|
||||
}
|
||||
|
||||
// sliceHeader instead of reflect.SliceHeader
|
||||
type sliceHeader struct {
|
||||
data unsafe.Pointer
|
||||
len int
|
||||
cap int
|
||||
}
|
||||
|
||||
// getBytes casts the input json bytes to a string and safely returns the
|
||||
// results as uniquely allocated data. This operation is intended to minimize
|
||||
// copies and allocations for the large json string->[]byte.
|
||||
|
@ -2834,14 +2789,14 @@ func getBytes(json []byte, path string) Result {
|
|||
// unsafe cast to string
|
||||
result = Get(*(*string)(unsafe.Pointer(&json)), path)
|
||||
// safely get the string headers
|
||||
rawhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Raw))
|
||||
strhi := *(*reflect.StringHeader)(unsafe.Pointer(&result.Str))
|
||||
rawhi := *(*stringHeader)(unsafe.Pointer(&result.Raw))
|
||||
strhi := *(*stringHeader)(unsafe.Pointer(&result.Str))
|
||||
// create byte slice headers
|
||||
rawh := reflect.SliceHeader{Data: rawhi.Data, Len: rawhi.Len}
|
||||
strh := reflect.SliceHeader{Data: strhi.Data, Len: strhi.Len}
|
||||
if strh.Data == 0 {
|
||||
rawh := sliceHeader{data: rawhi.data, len: rawhi.len, cap: rawhi.len}
|
||||
strh := sliceHeader{data: strhi.data, len: strhi.len, cap: rawhi.len}
|
||||
if strh.data == nil {
|
||||
// str is nil
|
||||
if rawh.Data == 0 {
|
||||
if rawh.data == nil {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
} else {
|
||||
|
@ -2849,19 +2804,20 @@ func getBytes(json []byte, path string) Result {
|
|||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
}
|
||||
result.Str = ""
|
||||
} else if rawh.Data == 0 {
|
||||
} else if rawh.data == nil {
|
||||
// raw is nil
|
||||
result.Raw = ""
|
||||
// str has data, safely copy the slice header to a string
|
||||
result.Str = string(*(*[]byte)(unsafe.Pointer(&strh)))
|
||||
} else if strh.Data >= rawh.Data &&
|
||||
int(strh.Data)+strh.Len <= int(rawh.Data)+rawh.Len {
|
||||
} else if uintptr(strh.data) >= uintptr(rawh.data) &&
|
||||
uintptr(strh.data)+uintptr(strh.len) <=
|
||||
uintptr(rawh.data)+uintptr(rawh.len) {
|
||||
// Str is a substring of Raw.
|
||||
start := int(strh.Data - rawh.Data)
|
||||
start := uintptr(strh.data) - uintptr(rawh.data)
|
||||
// safely copy the raw slice header
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
// substring the raw
|
||||
result.Str = result.Raw[start : start+strh.Len]
|
||||
result.Str = result.Raw[start : start+uintptr(strh.len)]
|
||||
} else {
|
||||
// safely copy both the raw and str slice headers to strings
|
||||
result.Raw = string(*(*[]byte)(unsafe.Pointer(&rawh)))
|
||||
|
@ -2876,9 +2832,9 @@ func getBytes(json []byte, path string) Result {
|
|||
// used instead.
|
||||
func fillIndex(json string, c *parseContext) {
|
||||
if len(c.value.Raw) > 0 && !c.calcd {
|
||||
jhdr := *(*reflect.StringHeader)(unsafe.Pointer(&json))
|
||||
rhdr := *(*reflect.StringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||
c.value.Index = int(rhdr.Data - jhdr.Data)
|
||||
jhdr := *(*stringHeader)(unsafe.Pointer(&json))
|
||||
rhdr := *(*stringHeader)(unsafe.Pointer(&(c.value.Raw)))
|
||||
c.value.Index = int(uintptr(rhdr.data) - uintptr(jhdr.data))
|
||||
if c.value.Index < 0 || c.value.Index >= len(json) {
|
||||
c.value.Index = 0
|
||||
}
|
||||
|
@ -2886,10 +2842,10 @@ func fillIndex(json string, c *parseContext) {
|
|||
}
|
||||
|
||||
func stringBytes(s string) []byte {
|
||||
return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
|
||||
Data: (*reflect.StringHeader)(unsafe.Pointer(&s)).Data,
|
||||
Len: len(s),
|
||||
Cap: len(s),
|
||||
return *(*[]byte)(unsafe.Pointer(&sliceHeader{
|
||||
data: (*stringHeader)(unsafe.Pointer(&s)).data,
|
||||
len: len(s),
|
||||
cap: len(s),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,6 @@ module github.com/tidwall/gjson
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/tidwall/match v1.0.1
|
||||
github.com/tidwall/pretty v1.0.0
|
||||
github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/pretty v1.1.0
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
language: go
|
|
@ -1,20 +1,17 @@
|
|||
Match
|
||||
=====
|
||||
<a href="https://travis-ci.org/tidwall/match"><img src="https://img.shields.io/travis/tidwall/match.svg?style=flat-square" alt="Build Status"></a>
|
||||
<a href="https://godoc.org/github.com/tidwall/match"><img src="https://img.shields.io/badge/api-reference-blue.svg?style=flat-square" alt="GoDoc"></a>
|
||||
# Match
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/tidwall/match?status.svg)](https://godoc.org/github.com/tidwall/match)
|
||||
|
||||
Match is a very simple pattern matcher where '*' matches on any
|
||||
number characters and '?' matches on any one character.
|
||||
|
||||
Installing
|
||||
----------
|
||||
## Installing
|
||||
|
||||
```
|
||||
go get -u github.com/tidwall/match
|
||||
```
|
||||
|
||||
Example
|
||||
-------
|
||||
## Example
|
||||
|
||||
```go
|
||||
match.Match("hello", "*llo")
|
||||
|
@ -23,10 +20,10 @@ match.Match("hello", "h*o")
|
|||
```
|
||||
|
||||
|
||||
Contact
|
||||
-------
|
||||
## Contact
|
||||
|
||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||
|
||||
License
|
||||
-------
|
||||
## License
|
||||
|
||||
Redcon source code is available under the MIT [License](/LICENSE).
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/tidwall/match
|
||||
|
||||
go 1.15
|
|
@ -1,4 +1,4 @@
|
|||
// Match provides a simple pattern matcher with unicode support.
|
||||
// Package match provides a simple pattern matcher with unicode support.
|
||||
package match
|
||||
|
||||
import "unicode/utf8"
|
||||
|
@ -6,7 +6,7 @@ import "unicode/utf8"
|
|||
// Match returns true if str matches pattern. This is a very
|
||||
// simple wildcard match where '*' matches on any number characters
|
||||
// and '?' matches on any one character.
|
||||
|
||||
//
|
||||
// pattern:
|
||||
// { term }
|
||||
// term:
|
||||
|
@ -16,12 +16,16 @@ import "unicode/utf8"
|
|||
// '\\' c matches character c
|
||||
//
|
||||
func Match(str, pattern string) bool {
|
||||
return deepMatch(str, pattern)
|
||||
}
|
||||
|
||||
func deepMatch(str, pattern string) bool {
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
return deepMatch(str, pattern)
|
||||
}
|
||||
func deepMatch(str, pattern string) bool {
|
||||
for len(pattern) > 1 && pattern[0] == '*' && pattern[1] == '*' {
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
for len(pattern) > 0 {
|
||||
if pattern[0] > 0x7f {
|
||||
return deepMatchRune(str, pattern)
|
||||
|
@ -52,6 +56,13 @@ func deepMatch(str, pattern string) bool {
|
|||
}
|
||||
|
||||
func deepMatchRune(str, pattern string) bool {
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
for len(pattern) > 1 && pattern[0] == '*' && pattern[1] == '*' {
|
||||
pattern = pattern[1:]
|
||||
}
|
||||
|
||||
var sr, pr rune
|
||||
var srsz, prsz int
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
language: go
|
|
@ -1,8 +1,6 @@
|
|||
# Pretty
|
||||
[![Build Status](https://img.shields.io/travis/tidwall/pretty.svg?style=flat-square)](https://travis-ci.org/tidwall/prettty)
|
||||
[![Coverage Status](https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square)](http://gocover.io/github.com/tidwall/pretty)
|
||||
[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://godoc.org/github.com/tidwall/pretty)
|
||||
|
||||
[![GoDoc](https://img.shields.io/badge/api-reference-blue.svg?style=flat-square)](https://pkg.go.dev/github.com/tidwall/pretty)
|
||||
|
||||
Pretty is a Go package that provides [fast](#performance) methods for formatting JSON for human readability, or to compact JSON for smaller payloads.
|
||||
|
||||
|
@ -81,7 +79,6 @@ Will format the json to:
|
|||
{"name":{"first":"Tom","last":"Anderson"},"age":37,"children":["Sara","Alex","Jack"],"fav.movie":"Deer Hunter","friends":[{"first":"Janet","last":"Murphy","age":44}]}```
|
||||
```
|
||||
|
||||
|
||||
## Customized output
|
||||
|
||||
There's a `PrettyOptions(json, opts)` function which allows for customizing the output with the following options:
|
||||
|
@ -106,14 +103,15 @@ type Options struct {
|
|||
|
||||
Benchmarks of Pretty alongside the builtin `encoding/json` Indent/Compact methods.
|
||||
```
|
||||
BenchmarkPretty-8 1000000 1283 ns/op 720 B/op 2 allocs/op
|
||||
BenchmarkUgly-8 3000000 426 ns/op 240 B/op 1 allocs/op
|
||||
BenchmarkUglyInPlace-8 5000000 340 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkJSONIndent-8 300000 4628 ns/op 1069 B/op 4 allocs/op
|
||||
BenchmarkJSONCompact-8 1000000 2469 ns/op 758 B/op 4 allocs/op
|
||||
BenchmarkPretty-16 1000000 1034 ns/op 720 B/op 2 allocs/op
|
||||
BenchmarkPrettySortKeys-16 586797 1983 ns/op 2848 B/op 14 allocs/op
|
||||
BenchmarkUgly-16 4652365 254 ns/op 240 B/op 1 allocs/op
|
||||
BenchmarkUglyInPlace-16 6481233 183 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkJSONIndent-16 450654 2687 ns/op 1221 B/op 0 allocs/op
|
||||
BenchmarkJSONCompact-16 685111 1699 ns/op 442 B/op 0 allocs/op
|
||||
```
|
||||
|
||||
*These benchmarks were run on a MacBook Pro 15" 2.8 GHz Intel Core i7 using Go 1.7.*
|
||||
*These benchmarks were run on a MacBook Pro 2.4 GHz 8-Core Intel Core i9.*
|
||||
|
||||
## Contact
|
||||
Josh Baker [@tidwall](http://twitter.com/tidwall)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
module github.com/tidwall/pretty
|
||||
|
||||
go 1.16
|
|
@ -1,7 +1,10 @@
|
|||
package pretty
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Options is Pretty options
|
||||
|
@ -84,6 +87,14 @@ func ugly(dst, src []byte) []byte {
|
|||
return dst
|
||||
}
|
||||
|
||||
func isNaNOrInf(src []byte) bool {
|
||||
return src[0] == 'i' || //Inf
|
||||
src[0] == 'I' || // inf
|
||||
src[0] == '+' || // +Inf
|
||||
src[0] == 'N' || // Nan
|
||||
(src[0] == 'n' && len(src) > 1 && src[1] != 'u') // nan
|
||||
}
|
||||
|
||||
func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
|
||||
for ; i < len(json); i++ {
|
||||
if json[i] <= ' ' {
|
||||
|
@ -92,7 +103,8 @@ func appendPrettyAny(buf, json []byte, i int, pretty bool, width int, prefix, in
|
|||
if json[i] == '"' {
|
||||
return appendPrettyString(buf, json, i, nl)
|
||||
}
|
||||
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' {
|
||||
|
||||
if (json[i] >= '0' && json[i] <= '9') || json[i] == '-' || isNaNOrInf(json[i:]) {
|
||||
return appendPrettyNumber(buf, json, i, nl)
|
||||
}
|
||||
if json[i] == '{' {
|
||||
|
@ -118,25 +130,121 @@ type pair struct {
|
|||
vstart, vend int
|
||||
}
|
||||
|
||||
type byKey struct {
|
||||
type byKeyVal struct {
|
||||
sorted bool
|
||||
json []byte
|
||||
buf []byte
|
||||
pairs []pair
|
||||
}
|
||||
|
||||
func (arr *byKey) Len() int {
|
||||
func (arr *byKeyVal) Len() int {
|
||||
return len(arr.pairs)
|
||||
}
|
||||
func (arr *byKey) Less(i, j int) bool {
|
||||
key1 := arr.json[arr.pairs[i].kstart+1 : arr.pairs[i].kend-1]
|
||||
key2 := arr.json[arr.pairs[j].kstart+1 : arr.pairs[j].kend-1]
|
||||
return string(key1) < string(key2)
|
||||
func (arr *byKeyVal) Less(i, j int) bool {
|
||||
if arr.isLess(i, j, byKey) {
|
||||
return true
|
||||
}
|
||||
if arr.isLess(j, i, byKey) {
|
||||
return false
|
||||
}
|
||||
return arr.isLess(i, j, byVal)
|
||||
}
|
||||
func (arr *byKey) Swap(i, j int) {
|
||||
func (arr *byKeyVal) Swap(i, j int) {
|
||||
arr.pairs[i], arr.pairs[j] = arr.pairs[j], arr.pairs[i]
|
||||
arr.sorted = true
|
||||
}
|
||||
|
||||
type byKind int
|
||||
|
||||
const (
|
||||
byKey byKind = 0
|
||||
byVal byKind = 1
|
||||
)
|
||||
|
||||
type jtype int
|
||||
|
||||
const (
|
||||
jnull jtype = iota
|
||||
jfalse
|
||||
jnumber
|
||||
jstring
|
||||
jtrue
|
||||
jjson
|
||||
)
|
||||
|
||||
func getjtype(v []byte) jtype {
|
||||
if len(v) == 0 {
|
||||
return jnull
|
||||
}
|
||||
switch v[0] {
|
||||
case '"':
|
||||
return jstring
|
||||
case 'f':
|
||||
return jfalse
|
||||
case 't':
|
||||
return jtrue
|
||||
case 'n':
|
||||
return jnull
|
||||
case '[', '{':
|
||||
return jjson
|
||||
default:
|
||||
return jnumber
|
||||
}
|
||||
}
|
||||
|
||||
func (arr *byKeyVal) isLess(i, j int, kind byKind) bool {
|
||||
k1 := arr.json[arr.pairs[i].kstart:arr.pairs[i].kend]
|
||||
k2 := arr.json[arr.pairs[j].kstart:arr.pairs[j].kend]
|
||||
var v1, v2 []byte
|
||||
if kind == byKey {
|
||||
v1 = k1
|
||||
v2 = k2
|
||||
} else {
|
||||
v1 = bytes.TrimSpace(arr.buf[arr.pairs[i].vstart:arr.pairs[i].vend])
|
||||
v2 = bytes.TrimSpace(arr.buf[arr.pairs[j].vstart:arr.pairs[j].vend])
|
||||
if len(v1) >= len(k1)+1 {
|
||||
v1 = bytes.TrimSpace(v1[len(k1)+1:])
|
||||
}
|
||||
if len(v2) >= len(k2)+1 {
|
||||
v2 = bytes.TrimSpace(v2[len(k2)+1:])
|
||||
}
|
||||
}
|
||||
t1 := getjtype(v1)
|
||||
t2 := getjtype(v2)
|
||||
if t1 < t2 {
|
||||
return true
|
||||
}
|
||||
if t1 > t2 {
|
||||
return false
|
||||
}
|
||||
if t1 == jstring {
|
||||
s1 := parsestr(v1)
|
||||
s2 := parsestr(v2)
|
||||
return string(s1) < string(s2)
|
||||
}
|
||||
if t1 == jnumber {
|
||||
n1, _ := strconv.ParseFloat(string(v1), 64)
|
||||
n2, _ := strconv.ParseFloat(string(v2), 64)
|
||||
return n1 < n2
|
||||
}
|
||||
return string(v1) < string(v2)
|
||||
|
||||
}
|
||||
|
||||
func parsestr(s []byte) []byte {
|
||||
for i := 1; i < len(s); i++ {
|
||||
if s[i] == '\\' {
|
||||
var str string
|
||||
json.Unmarshal(s, &str)
|
||||
return []byte(str)
|
||||
}
|
||||
if s[i] == '"' {
|
||||
return s[1:i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool, width int, prefix, indent string, sortkeys bool, tabs, nl, max int) ([]byte, int, int, bool) {
|
||||
var ok bool
|
||||
if width > 0 {
|
||||
|
@ -174,7 +282,11 @@ func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool,
|
|||
}
|
||||
if n > 0 {
|
||||
nl = len(buf)
|
||||
buf = append(buf, '\n')
|
||||
if buf[nl-1] == ' ' {
|
||||
buf[nl-1] = '\n'
|
||||
} else {
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
}
|
||||
if buf[len(buf)-1] != open {
|
||||
buf = appendTabs(buf, prefix, indent, tabs)
|
||||
|
@ -193,7 +305,11 @@ func appendPrettyObject(buf, json []byte, i int, open, close byte, pretty bool,
|
|||
var p pair
|
||||
if pretty {
|
||||
nl = len(buf)
|
||||
buf = append(buf, '\n')
|
||||
if buf[nl-1] == ' ' {
|
||||
buf[nl-1] = '\n'
|
||||
} else {
|
||||
buf = append(buf, '\n')
|
||||
}
|
||||
if open == '{' && sortkeys {
|
||||
p.kstart = i
|
||||
p.vstart = len(buf)
|
||||
|
@ -235,8 +351,8 @@ func sortPairs(json, buf []byte, pairs []pair) []byte {
|
|||
}
|
||||
vstart := pairs[0].vstart
|
||||
vend := pairs[len(pairs)-1].vend
|
||||
arr := byKey{false, json, pairs}
|
||||
sort.Sort(&arr)
|
||||
arr := byKeyVal{false, json, buf, pairs}
|
||||
sort.Stable(&arr)
|
||||
if !arr.sorted {
|
||||
return buf
|
||||
}
|
||||
|
@ -305,6 +421,7 @@ func appendTabs(buf []byte, prefix, indent string, tabs int) []byte {
|
|||
type Style struct {
|
||||
Key, String, Number [2]string
|
||||
True, False, Null [2]string
|
||||
Escape [2]string
|
||||
Append func(dst []byte, c byte) []byte
|
||||
}
|
||||
|
||||
|
@ -318,21 +435,26 @@ func hexp(p byte) byte {
|
|||
}
|
||||
|
||||
// TerminalStyle is for terminals
|
||||
var TerminalStyle = &Style{
|
||||
Key: [2]string{"\x1B[94m", "\x1B[0m"},
|
||||
String: [2]string{"\x1B[92m", "\x1B[0m"},
|
||||
Number: [2]string{"\x1B[93m", "\x1B[0m"},
|
||||
True: [2]string{"\x1B[96m", "\x1B[0m"},
|
||||
False: [2]string{"\x1B[96m", "\x1B[0m"},
|
||||
Null: [2]string{"\x1B[91m", "\x1B[0m"},
|
||||
Append: func(dst []byte, c byte) []byte {
|
||||
if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
|
||||
dst = append(dst, "\\u00"...)
|
||||
dst = append(dst, hexp((c>>4)&0xF))
|
||||
return append(dst, hexp((c)&0xF))
|
||||
}
|
||||
return append(dst, c)
|
||||
},
|
||||
var TerminalStyle *Style
|
||||
|
||||
func init() {
|
||||
TerminalStyle = &Style{
|
||||
Key: [2]string{"\x1B[94m", "\x1B[0m"},
|
||||
String: [2]string{"\x1B[92m", "\x1B[0m"},
|
||||
Number: [2]string{"\x1B[93m", "\x1B[0m"},
|
||||
True: [2]string{"\x1B[96m", "\x1B[0m"},
|
||||
False: [2]string{"\x1B[96m", "\x1B[0m"},
|
||||
Null: [2]string{"\x1B[91m", "\x1B[0m"},
|
||||
Escape: [2]string{"\x1B[35m", "\x1B[0m"},
|
||||
Append: func(dst []byte, c byte) []byte {
|
||||
if c < ' ' && (c != '\r' && c != '\n' && c != '\t' && c != '\v') {
|
||||
dst = append(dst, "\\u00"...)
|
||||
dst = append(dst, hexp((c>>4)&0xF))
|
||||
return append(dst, hexp((c)&0xF))
|
||||
}
|
||||
return append(dst, c)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Color will colorize the json. The style parma is used for customizing
|
||||
|
@ -363,8 +485,39 @@ func Color(src []byte, style *Style) []byte {
|
|||
dst = append(dst, style.String[0]...)
|
||||
}
|
||||
dst = apnd(dst, '"')
|
||||
esc := false
|
||||
uesc := 0
|
||||
for i = i + 1; i < len(src); i++ {
|
||||
dst = apnd(dst, src[i])
|
||||
if src[i] == '\\' {
|
||||
if key {
|
||||
dst = append(dst, style.Key[1]...)
|
||||
} else {
|
||||
dst = append(dst, style.String[1]...)
|
||||
}
|
||||
dst = append(dst, style.Escape[0]...)
|
||||
dst = apnd(dst, src[i])
|
||||
esc = true
|
||||
if i+1 < len(src) && src[i+1] == 'u' {
|
||||
uesc = 5
|
||||
} else {
|
||||
uesc = 1
|
||||
}
|
||||
} else if esc {
|
||||
dst = apnd(dst, src[i])
|
||||
if uesc == 1 {
|
||||
esc = false
|
||||
dst = append(dst, style.Escape[1]...)
|
||||
if key {
|
||||
dst = append(dst, style.Key[0]...)
|
||||
} else {
|
||||
dst = append(dst, style.String[0]...)
|
||||
}
|
||||
} else {
|
||||
uesc--
|
||||
}
|
||||
} else {
|
||||
dst = apnd(dst, src[i])
|
||||
}
|
||||
if src[i] == '"' {
|
||||
j := i - 1
|
||||
for ; ; j-- {
|
||||
|
@ -377,7 +530,9 @@ func Color(src []byte, style *Style) []byte {
|
|||
}
|
||||
}
|
||||
}
|
||||
if key {
|
||||
if esc {
|
||||
dst = append(dst, style.Escape[1]...)
|
||||
} else if key {
|
||||
dst = append(dst, style.Key[1]...)
|
||||
} else {
|
||||
dst = append(dst, style.String[1]...)
|
||||
|
@ -393,7 +548,7 @@ func Color(src []byte, style *Style) []byte {
|
|||
dst = apnd(dst, src[i])
|
||||
} else {
|
||||
var kind byte
|
||||
if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' {
|
||||
if (src[i] >= '0' && src[i] <= '9') || src[i] == '-' || isNaNOrInf(src[i:]) {
|
||||
kind = '0'
|
||||
dst = append(dst, style.Number[0]...)
|
||||
} else if src[i] == 't' {
|
||||
|
@ -430,3 +585,90 @@ func Color(src []byte, style *Style) []byte {
|
|||
}
|
||||
return dst
|
||||
}
|
||||
|
||||
// Spec strips out comments and trailing commas and convert the input to a
|
||||
// valid JSON per the official spec: https://tools.ietf.org/html/rfc8259
|
||||
//
|
||||
// The resulting JSON will always be the same length as the input and it will
|
||||
// include all of the same line breaks at matching offsets. This is to ensure
|
||||
// the result can be later processed by a external parser and that that
|
||||
// parser will report messages or errors with the correct offsets.
|
||||
func Spec(src []byte) []byte {
|
||||
return spec(src, nil)
|
||||
}
|
||||
|
||||
// SpecInPlace is the same as Spec, but this method reuses the input json
|
||||
// buffer to avoid allocations. Do not use the original bytes slice upon return.
|
||||
func SpecInPlace(src []byte) []byte {
|
||||
return spec(src, src)
|
||||
}
|
||||
|
||||
func spec(src, dst []byte) []byte {
|
||||
dst = dst[:0]
|
||||
for i := 0; i < len(src); i++ {
|
||||
if src[i] == '/' {
|
||||
if i < len(src)-1 {
|
||||
if src[i+1] == '/' {
|
||||
dst = append(dst, ' ', ' ')
|
||||
i += 2
|
||||
for ; i < len(src); i++ {
|
||||
if src[i] == '\n' {
|
||||
dst = append(dst, '\n')
|
||||
break
|
||||
} else if src[i] == '\t' || src[i] == '\r' {
|
||||
dst = append(dst, src[i])
|
||||
} else {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if src[i+1] == '*' {
|
||||
dst = append(dst, ' ', ' ')
|
||||
i += 2
|
||||
for ; i < len(src)-1; i++ {
|
||||
if src[i] == '*' && src[i+1] == '/' {
|
||||
dst = append(dst, ' ', ' ')
|
||||
i++
|
||||
break
|
||||
} else if src[i] == '\n' || src[i] == '\t' ||
|
||||
src[i] == '\r' {
|
||||
dst = append(dst, src[i])
|
||||
} else {
|
||||
dst = append(dst, ' ')
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
dst = append(dst, src[i])
|
||||
if src[i] == '"' {
|
||||
for i = i + 1; i < len(src); i++ {
|
||||
dst = append(dst, src[i])
|
||||
if src[i] == '"' {
|
||||
j := i - 1
|
||||
for ; ; j-- {
|
||||
if src[j] != '\\' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (j-i)%2 != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if src[i] == '}' || src[i] == ']' {
|
||||
for j := len(dst) - 2; j >= 0; j-- {
|
||||
if dst[j] <= ' ' {
|
||||
continue
|
||||
}
|
||||
if dst[j] == ',' {
|
||||
dst[j] = ' '
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
|
|
@ -300,11 +300,11 @@ github.com/stretchr/testify/assert
|
|||
github.com/stretchr/testify/mock
|
||||
# github.com/subosito/gotenv v1.2.0
|
||||
github.com/subosito/gotenv
|
||||
# github.com/tidwall/gjson v1.6.0
|
||||
# github.com/tidwall/gjson v1.8.1
|
||||
github.com/tidwall/gjson
|
||||
# github.com/tidwall/match v1.0.1
|
||||
# github.com/tidwall/match v1.0.3
|
||||
github.com/tidwall/match
|
||||
# github.com/tidwall/pretty v1.0.0
|
||||
# github.com/tidwall/pretty v1.2.0
|
||||
github.com/tidwall/pretty
|
||||
# github.com/urfave/cli/v2 v2.1.1
|
||||
github.com/urfave/cli/v2
|
||||
|
|
Loading…
Reference in New Issue