Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+10
@@ -1,5 +1,7 @@
|
||||
# HuJSON - "Human JSON" ([JWCC](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html))
|
||||
|
||||
[](https://pkg.go.dev/github.com/tailscale/hujson)
|
||||
|
||||
The `github.com/tailscale/hujson` package implements
|
||||
the [JWCC](https://nigeltao.github.io/blog/2021/json-with-commas-comments.html) extension
|
||||
of [standard JSON](https://datatracker.ietf.org/doc/html/rfc8259).
|
||||
@@ -15,6 +17,14 @@ For details, see the JWCC docs at:
|
||||
|
||||
https://nigeltao.github.io/blog/2021/json-with-commas-comments.html
|
||||
|
||||
## Formatting with hujsonfmt
|
||||
|
||||
`hujsonfmt` is a program that formats HuJSON files. Install it by running:
|
||||
|
||||
```
|
||||
go install github.com/tailscale/hujson/cmd/hujsonfmt@latest
|
||||
```
|
||||
|
||||
## Visual Studio Code association
|
||||
|
||||
Visual Studio Code supports a similar `jsonc` (JSON with comments) format. To
|
||||
|
||||
+15
-25
@@ -88,21 +88,6 @@ func (v *Value) Format() {
|
||||
v.UpdateOffsets()
|
||||
}
|
||||
|
||||
// Range iterates through a Value in depth-first order and
|
||||
// calls f for each value (including the root value).
|
||||
// It stops iteration when f returns false.
|
||||
func (v *Value) Range(f func(v *Value) bool) bool {
|
||||
if !f(v) {
|
||||
return false
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
return comp.rangeValues(func(v2 *Value) bool {
|
||||
return v2.Range(f)
|
||||
})
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// normalize performs simple normalization changes. In particular, it:
|
||||
// - normalizes strings,
|
||||
// - normalizes empty objects and arrays as simply {} or [],
|
||||
@@ -129,15 +114,16 @@ func (v *Value) normalize() bool {
|
||||
|
||||
// If there is only whitespace between the name and colon,
|
||||
// or between the value and comma, then remove the whitespace.
|
||||
v2.rangeValues(func(v *Value) bool {
|
||||
if !v.AfterExtra.hasComment() {
|
||||
v.AfterExtra = nil
|
||||
for v3 := range v2.allValues() {
|
||||
if !v3.AfterExtra.hasComment() {
|
||||
v3.AfterExtra = nil
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Normalize all sub-values.
|
||||
v2.rangeValues((*Value).normalize)
|
||||
for v3 := range v2.allValues() {
|
||||
v3.normalize()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -583,7 +569,9 @@ func (v *Value) alignObjectValues() bool {
|
||||
|
||||
// Recursively align all sub-objects.
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
comp.rangeValues((*Value).alignObjectValues)
|
||||
for v2 := range comp.allValues() {
|
||||
v2.alignObjectValues()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -593,9 +581,11 @@ func (v Value) hasNewline(checkTopLevelExtra bool) bool {
|
||||
return true
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
return !comp.rangeValues(func(v *Value) bool {
|
||||
return !v.hasNewline(true)
|
||||
})
|
||||
for v := range comp.allValues() {
|
||||
if v.hasNewline(true) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
go 1.18
|
||||
go 1.26
|
||||
|
||||
use (
|
||||
.
|
||||
|
||||
+20
@@ -221,13 +221,24 @@ func consumeExtra(n int, b []byte) (int, error) {
|
||||
n += consumeWhitespace(b[n:])
|
||||
// Skip past comments.
|
||||
case '/':
|
||||
isLineComment := bytes.HasPrefix(b[n:], lineCommentStart)
|
||||
switch nc := consumeComment(b[n:]); {
|
||||
case nc == 0:
|
||||
return n, nil
|
||||
case nc < 0:
|
||||
if isLineComment {
|
||||
if i := indexLineSeparator(b[n:]); i >= 0 {
|
||||
return n + i, newInvalidCharacterError(b[n+i:], "in line comment")
|
||||
}
|
||||
}
|
||||
return n, fmt.Errorf("parsing comment: %w", io.ErrUnexpectedEOF)
|
||||
case !utf8.Valid(b[n : n+nc]):
|
||||
return n, fmt.Errorf("invalid UTF-8 in comment")
|
||||
case isLineComment:
|
||||
if i := indexLineSeparator(b[n : n+nc]); i >= 0 {
|
||||
return n + i, newInvalidCharacterError(b[n+i:], "in line comment")
|
||||
}
|
||||
n += nc
|
||||
default:
|
||||
n += nc
|
||||
}
|
||||
@@ -245,6 +256,15 @@ func consumeWhitespace(b []byte) (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func indexLineSeparator(b []byte) int {
|
||||
i := bytes.IndexRune(b, '\u2028')
|
||||
j := bytes.IndexRune(b, '\u2029')
|
||||
if i < 0 || (j >= 0 && j < i) {
|
||||
return j
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
// consumeComment consumes a line or block comment start in b.
|
||||
// It returns the length of the comment if valid, otherwise
|
||||
// it returns 0 if it is not a comment and -1 if it is invalid.
|
||||
|
||||
+2
-2
@@ -417,8 +417,8 @@ func (b *Extra) extractTrailingcomments(readonly bool) (trailing Extra) {
|
||||
|
||||
// classifyComments classifies comments as belonging to the previous element
|
||||
// or belonging to the current element such that:
|
||||
// * b[:prevEnd] belongs to the previous element, and
|
||||
// * b[currStart:] belongs to the current element.
|
||||
// - b[:prevEnd] belongs to the previous element, and
|
||||
// - b[currStart:] belongs to the current element.
|
||||
//
|
||||
// Invariant: prevEnd <= currStart
|
||||
func (b Extra) classifyComments() (prevEnd, currStart int) {
|
||||
|
||||
+12
-8
@@ -14,8 +14,10 @@ func (v *Value) isStandard() bool {
|
||||
return false
|
||||
}
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
if !comp.rangeValues((*Value).isStandard) {
|
||||
return false
|
||||
for v2 := range comp.allValues() {
|
||||
if !v2.isStandard() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if hasTrailingComma(comp) || !comp.afterExtra().IsStandard() {
|
||||
return false
|
||||
@@ -41,15 +43,16 @@ func (v *Value) Minimize() {
|
||||
v.minimize()
|
||||
v.UpdateOffsets()
|
||||
}
|
||||
func (v *Value) minimize() bool {
|
||||
func (v *Value) minimize() {
|
||||
v.BeforeExtra = nil
|
||||
if v2, ok := v.Value.(composite); ok {
|
||||
v2.rangeValues((*Value).minimize)
|
||||
for v3 := range v2.allValues() {
|
||||
v3.minimize()
|
||||
}
|
||||
setTrailingComma(v2, false)
|
||||
*v2.afterExtra() = nil
|
||||
}
|
||||
v.AfterExtra = nil
|
||||
return true
|
||||
}
|
||||
|
||||
// Standardize strips any features specific to HuJSON from v,
|
||||
@@ -60,10 +63,12 @@ func (v *Value) Standardize() {
|
||||
v.standardize()
|
||||
v.UpdateOffsets() // should be noop if offsets are already correct
|
||||
}
|
||||
func (v *Value) standardize() bool {
|
||||
func (v *Value) standardize() {
|
||||
v.BeforeExtra.standardize()
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
comp.rangeValues((*Value).standardize)
|
||||
for v2 := range comp.allValues() {
|
||||
v2.standardize()
|
||||
}
|
||||
if last := comp.lastValue(); last != nil && last.AfterExtra != nil {
|
||||
*comp.afterExtra() = append(append(last.AfterExtra, ' '), *comp.afterExtra()...)
|
||||
last.AfterExtra = nil
|
||||
@@ -71,7 +76,6 @@ func (v *Value) standardize() bool {
|
||||
comp.afterExtra().standardize()
|
||||
}
|
||||
v.AfterExtra.standardize()
|
||||
return true
|
||||
}
|
||||
func (b *Extra) standardize() {
|
||||
for i, c := range *b {
|
||||
|
||||
+74
-20
@@ -12,8 +12,7 @@
|
||||
//
|
||||
// See https://nigeltao.github.io/blog/2021/json-with-commas-comments.html
|
||||
//
|
||||
//
|
||||
// Functionality
|
||||
// # Functionality
|
||||
//
|
||||
// The Parse function parses HuJSON input as a Value,
|
||||
// which is a syntax tree exactly representing the input.
|
||||
@@ -32,8 +31,7 @@
|
||||
// but instead for the HuJSON and standard JSON format.
|
||||
// The Patch method applies a JSON Patch (RFC 6902) to the receiving value.
|
||||
//
|
||||
//
|
||||
// Grammar
|
||||
// # Grammar
|
||||
//
|
||||
// The changes to the JSON grammar are:
|
||||
//
|
||||
@@ -72,8 +70,7 @@
|
||||
// '000A' ws
|
||||
// '000D' ws
|
||||
//
|
||||
//
|
||||
// Use with the Standard Library
|
||||
// # Use with the Standard Library
|
||||
//
|
||||
// This package operates with HuJSON as an AST. In order to parse HuJSON
|
||||
// into arbitrary Go types, use this package to parse HuJSON input as an AST,
|
||||
@@ -95,6 +92,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"iter"
|
||||
"math"
|
||||
"strconv"
|
||||
"unicode/utf8"
|
||||
@@ -147,6 +145,45 @@ func (v Value) Clone() Value {
|
||||
return v
|
||||
}
|
||||
|
||||
// Range iterates through a Value in depth-first order and
|
||||
// calls f for each value (including the root value).
|
||||
// It stops iteration when f returns false.
|
||||
//
|
||||
// Deprecated: Use [All] instead.
|
||||
func (v *Value) Range(f func(v *Value) bool) bool {
|
||||
for v2 := range v.All() {
|
||||
if !f(v2) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// All returns an iterator over all values in depth-first order,
|
||||
// starting with v itself.
|
||||
func (v *Value) All() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
v.walkAll(yield)
|
||||
}
|
||||
}
|
||||
|
||||
// walkAll recursively yields v and its descendants in depth-first order.
|
||||
func (v *Value) walkAll(yield func(*Value) bool) bool {
|
||||
if !yield(v) {
|
||||
return false
|
||||
}
|
||||
|
||||
if comp, ok := v.Value.(composite); ok {
|
||||
for v2 := range comp.allValues() {
|
||||
if !v2.walkAll(yield) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ValueTrimmed is a JSON value without surrounding whitespace or comments.
|
||||
// This is a sum type consisting of Literal, *Object, or *Array.
|
||||
type ValueTrimmed interface {
|
||||
@@ -335,6 +372,7 @@ type Object struct {
|
||||
// after the preceding open brace or comma and before the closing brace.
|
||||
AfterExtra Extra
|
||||
}
|
||||
|
||||
type ObjectMember struct {
|
||||
Name, Value Value
|
||||
}
|
||||
@@ -342,35 +380,43 @@ type ObjectMember struct {
|
||||
func (obj Object) length() int {
|
||||
return len(obj.Members)
|
||||
}
|
||||
|
||||
func (obj Object) firstValue() *Value {
|
||||
if len(obj.Members) > 0 {
|
||||
return &obj.Members[0].Name
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (obj Object) rangeValues(f func(*Value) bool) bool {
|
||||
for i := range obj.Members {
|
||||
if !f(&obj.Members[i].Name) {
|
||||
return false
|
||||
}
|
||||
if !f(&obj.Members[i].Value) {
|
||||
return false
|
||||
|
||||
// allValues iterates all members of the object,
|
||||
// interleaved between the member name and the member value.
|
||||
func (obj Object) allValues() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
for i := range obj.Members {
|
||||
if !yield(&obj.Members[i].Name) {
|
||||
return
|
||||
}
|
||||
if !yield(&obj.Members[i].Value) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (obj Object) lastValue() *Value {
|
||||
if len(obj.Members) > 0 {
|
||||
return &obj.Members[len(obj.Members)-1].Value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (obj *Object) beforeExtraAt(i int) *Extra {
|
||||
if i < len(obj.Members) {
|
||||
return &obj.Members[i].Name.BeforeExtra
|
||||
}
|
||||
return &obj.AfterExtra
|
||||
}
|
||||
|
||||
func (obj *Object) afterExtra() *Extra {
|
||||
return &obj.AfterExtra
|
||||
}
|
||||
@@ -401,37 +447,45 @@ type Array struct {
|
||||
// after the preceding open bracket or comma and before the closing bracket.
|
||||
AfterExtra Extra
|
||||
}
|
||||
|
||||
type ArrayElement = Value
|
||||
|
||||
func (arr Array) length() int {
|
||||
return len(arr.Elements)
|
||||
}
|
||||
|
||||
func (arr Array) firstValue() *Value {
|
||||
if len(arr.Elements) > 0 {
|
||||
return &arr.Elements[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (arr Array) rangeValues(f func(*Value) bool) bool {
|
||||
for i := range arr.Elements {
|
||||
if !f(&arr.Elements[i]) {
|
||||
return false
|
||||
|
||||
// allValues iterates all elements of the array.
|
||||
func (arr Array) allValues() iter.Seq[*Value] {
|
||||
return func(yield func(*Value) bool) {
|
||||
for i := range arr.Elements {
|
||||
if !yield(&arr.Elements[i]) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (arr Array) lastValue() *Value {
|
||||
if len(arr.Elements) > 0 {
|
||||
return &arr.Elements[len(arr.Elements)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (arr *Array) beforeExtraAt(i int) *Extra {
|
||||
if i < len(arr.Elements) {
|
||||
return &arr.Elements[i].BeforeExtra
|
||||
}
|
||||
return &arr.AfterExtra
|
||||
}
|
||||
|
||||
func (arr *Array) afterExtra() *Extra {
|
||||
return &arr.AfterExtra
|
||||
}
|
||||
@@ -457,7 +511,7 @@ type composite interface {
|
||||
length() int
|
||||
|
||||
firstValue() *Value
|
||||
rangeValues(func(*Value) bool) bool
|
||||
allValues() iter.Seq[*Value]
|
||||
lastValue() *Value
|
||||
|
||||
getAt(int) ValueTrimmed
|
||||
|
||||
Reference in new issue
Block a user