File: common.go

package info (click to toggle)
golang-github-dsnet-golib 0.0~git20171103.1ea1667-1.1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, forky, sid, trixie
  • size: 240 kB
  • sloc: makefile: 2
file content (169 lines) | stat: -rw-r--r-- 4,540 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
// Copyright 2017, Joe Tsai. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

// Package jsonfmt provides functionality for formatting JSON.
package jsonfmt

import "fmt"

var (
	newlineBytes = []byte{'\n'}
	spaceBytes   = []byte{' '}
)

type (
	jsonValue interface {
		isValue() // Satisfied by (*jsonObject | *jsonArray | jsonString | jsonNumber | jsonLiteral | jsonInvalid)
	}

	jsonObject struct {
		// '{'
		preRecords  jsonMeta // If non-empty, ends with jsonNewlines
		records     []jsonRecord
		postRecords jsonMeta // Never contains jsonNewlines
		// '}'
	}
	jsonRecord struct {
		preKey  jsonMeta // Never contains jsonNewlines
		key     jsonValue
		postKey jsonMeta
		// ':'
		preVal  jsonMeta
		val     jsonValue
		postVal jsonMeta
		// ','
		postComma jsonMeta // If non-empty, ends with jsonNewlines
	}

	jsonArray struct {
		// '['
		preElems  jsonMeta // If non-empty, ends with jsonNewlines
		elems     []jsonElement
		postElems jsonMeta // Never contains jsonNewlines
		// ']'
	}
	jsonElement struct {
		preVal  jsonMeta // Never contains jsonNewlines
		val     jsonValue
		postVal jsonMeta
		// ','
		postComma jsonMeta // If non-empty, ends with jsonNewlines
	}

	jsonString  []byte // Quoted string
	jsonNumber  []byte // Numeric value
	jsonLiteral []byte // "true" | "false" | "null"

	jsonMeta []interface {
		isMeta() // Implemented by (jsonComment | jsonNewlines | jsonInvalid)
	}
	jsonComment  []byte // Comment of either "//" or "/**/" form without trailing newlines
	jsonNewlines int    // Number of newlines

	// When a parsing error occurs, then the remainder of the input is stored
	// as jsonInvalid in the AST.
	jsonInvalid []byte // May possibly be an empty string
)

func (*jsonObject) isValue() {}
func (*jsonArray) isValue()  {}
func (jsonString) isValue()  {}
func (jsonNumber) isValue()  {}
func (jsonLiteral) isValue() {}
func (jsonInvalid) isValue() {}

func (jsonComment) isMeta()  {}
func (jsonNewlines) isMeta() {}
func (jsonInvalid) isMeta()  {}

// Option configures how to format JSON.
type Option interface {
	option()
}

type (
	minify      struct{ Option }
	standardize struct{ Option }
)

// TODO: Make these an user Option?
const defaultColumnLimit = 80
const defaultAlignLimit = 20

// Minify configures Format to produce the minimal representation of the input.
// If Format returns no error, then the output is guaranteed to be valid JSON,
func Minify() Option { return minify{} }

// Standardize configures Format to produce valid JSON according to ECMA-404.
// This strips any comments and trailing commas.
func Standardize() Option { return standardize{} }

// Format parses and formats the input JSON according to provided Options.
// If err is non-nil, then the output is a best effort at processing the input.
//
// This function accepts a superset of the JSON specification that allows
// comments and trailing commas after the last element in an object or array.
func Format(in []byte, opts ...Option) (out []byte, err error) {
	// Process the provided options.
	var st state
	for _, opt := range opts {
		switch opt.(type) {
		case minify:
			st.minify = true
			st.standardize = true
		case standardize:
			st.standardize = true
		default:
			panic(fmt.Sprintf("unknown option: %#v", opt))
		}
	}

	// Attempt to parse and format the JSON data.
	err = st.parse(in)
	if !st.minify {
		expandAST(st.val, defaultColumnLimit)
	}
	out = st.format()
	if !st.minify {
		out = alignJSON(out, defaultAlignLimit)
	}
	return out, err
}

type state struct {
	in      []byte
	preVal  jsonMeta
	val     jsonValue
	postVal jsonMeta
	last    interface{} // T where T = (*jsonValue | *jsonMeta)
	out     []byte

	// Parsing and formatting options.
	minify        bool // If set, implies standardize is set too
	standardize   bool // If set, output will be ECMA-404 compliant
	trailingComma bool // Set by parser if any trailing commas detected
	hasNewlines   bool // Set by formatter if any newlines are emitted

	newlines []byte // Pending newlines to output
	indents  []byte // Indents to output per line
}

func (s *state) pushIndent() {
	s.indents = append(s.indents, '\t')
}
func (s *state) popIndent() {
	s.indents = s.indents[:len(s.indents)-1]
}

type jsonError struct {
	line, column int
	message      string
}

func (e jsonError) Error() string {
	if e.line > 0 && e.column > 0 {
		return "jsonfmt: " + e.message
	}
	return fmt.Sprintf("jsonfmt: line %d, column %d: %v", e.line, e.column, e.message)
}