1// Copyright (c) 2020, Peter Ohler, All rights reserved.
2
3package ojg
4
5import (
6	"fmt"
7	"strconv"
8	"time"
9)
10
11const (
12	// Normal is the Normal ANSI encoding sequence.
13	Normal = "\x1b[m"
14	// Black is the Black ANSI encoding sequence.
15	Black = "\x1b[30m"
16	// Red is the Red ANSI encoding sequence.
17	Red = "\x1b[31m"
18	// Green is the Green ANSI encoding sequence.
19	Green = "\x1b[32m"
20	// Yellow is the Yellow ANSI encoding sequence.
21	Yellow = "\x1b[33m"
22	// Blue is the Blue ANSI encoding sequence.
23	Blue = "\x1b[34m"
24	// Magenta is the Magenta ANSI encoding sequence.
25	Magenta = "\x1b[35m"
26	// Cyan is the Cyan ANSI encoding sequence.
27	Cyan = "\x1b[36m"
28	// White is the White ANSI encoding sequence.
29	White = "\x1b[37m"
30	// Gray is the Gray ANSI encoding sequence.
31	Gray = "\x1b[90m"
32	// BrightRed is the BrightRed ANSI encoding sequence.
33	BrightRed = "\x1b[91m"
34	// BrightGreen is the BrightGreen ANSI encoding sequence.
35	BrightGreen = "\x1b[92m"
36	// BrightYellow is the BrightYellow ANSI encoding sequence.
37	BrightYellow = "\x1b[93m"
38	// BrightBlue is the BrightBlue ANSI encoding sequence.
39	BrightBlue = "\x1b[94m"
40	// BrightMagenta is the BrightMagenta ANSI encoding sequence.
41	BrightMagenta = "\x1b[95m"
42	// BrightCyan is the BrightCyan ANSI encoding sequence.
43	BrightCyan = "\x1b[96m"
44	// BrightWhite is the BrightWhite ANSI encoding sequence.
45	BrightWhite = "\x1b[97m"
46
47	// BytesAsString indicates []byte should be encoded as a string.
48	BytesAsString = iota
49	// BytesAsBase64 indicates []byte should be encoded as base64.
50	BytesAsBase64
51	// BytesAsArray indicates []byte should be encoded as an array if integers.
52	BytesAsArray
53
54	// MaskByTag is the mask for byTag fields.
55	MaskByTag = byte(0x10)
56	// MaskExact is the mask for Exact fields.
57	MaskExact = byte(0x08) // exact key vs lowwer case first letter
58	// MaskPretty is the mask for Pretty fields.
59	MaskPretty = byte(0x04)
60	// MaskNested is the mask for Nested fields.
61	MaskNested = byte(0x02)
62	// MaskSen is the mask for Sen fields.
63	MaskSen = byte(0x01)
64	// MaskSet is the mask for Set fields.
65	MaskSet = byte(0x20)
66	// MaskIndex is the mask for an index that has been set up.
67	MaskIndex = byte(0x1f)
68)
69
70var (
71	// DefaultOptions default options that can be set as desired.
72	DefaultOptions = Options{
73		InitSize:    256,
74		SyntaxColor: Normal,
75		KeyColor:    Blue,
76		NullColor:   Red,
77		BoolColor:   Yellow,
78		NumberColor: Cyan,
79		StringColor: Green,
80		TimeColor:   Magenta,
81		HTMLUnsafe:  true,
82		WriteLimit:  1024,
83	}
84
85	// BrightOptions encoding options for color encoding.
86	BrightOptions = Options{
87		InitSize:    256,
88		SyntaxColor: Normal,
89		KeyColor:    BrightBlue,
90		NullColor:   BrightRed,
91		BoolColor:   BrightYellow,
92		NumberColor: BrightCyan,
93		StringColor: BrightGreen,
94		TimeColor:   BrightMagenta,
95		WriteLimit:  1024,
96	}
97
98	// GoOptions are the options closest to the go json package.
99	GoOptions = Options{
100		InitSize:     256,
101		SyntaxColor:  Normal,
102		KeyColor:     Blue,
103		NullColor:    Red,
104		BoolColor:    Yellow,
105		NumberColor:  Cyan,
106		StringColor:  Green,
107		TimeColor:    Magenta,
108		CreateKey:    "",
109		FullTypePath: false,
110		OmitNil:      false,
111		UseTags:      true,
112		KeyExact:     true,
113		NestEmbed:    false,
114		BytesAs:      BytesAsBase64,
115		WriteLimit:   1024,
116	}
117
118	// HTMLOptions defines color options for generating colored HTML. The
119	// encoding is suitable for use in a <pre> element.
120	HTMLOptions = Options{
121		InitSize:    256,
122		SyntaxColor: "<span>",
123		KeyColor:    `<span style="color:#44f">`,
124		NullColor:   `<span style="color:red">`,
125		BoolColor:   `<span style="color:#a40">`,
126		NumberColor: `<span style="color:#04a">`,
127		StringColor: `<span style="color:green">`,
128		TimeColor:   `<span style="color:#f0f">`,
129		NoColor:     "</span>",
130		HTMLUnsafe:  false,
131		WriteLimit:  1024,
132	}
133)
134
135// Options for writing data to JSON.
136type Options struct {
137
138	// Indent for the output.
139	Indent int
140
141	// Tab if true will indent using tabs and ignore the Indent member.
142	Tab bool
143
144	// Sort object members if true.
145	Sort bool
146
147	// OmitNil skips the writing of nil values in an object.
148	OmitNil bool
149
150	// InitSize is the initial buffer size.
151	InitSize int
152
153	// WriteLimit is the size of the buffer that will trigger a write when
154	// using a writer.
155	WriteLimit int
156
157	// TimeFormat defines how time is encoded. Options are to use a
158	// time. layout string format such as time.RFC3339Nano, "second" for a
159	// decimal representation, "nano" for a an integer. For decompose setting
160	// to "time" will leave it unchanged.
161	TimeFormat string
162
163	// TimeWrap if not empty encoded time as an object with a single member. For
164	// example if set to "@" then and TimeFormat is RFC3339Nano then the encoded
165	// time will look like '{"@":"2020-04-12T16:34:04.123456789Z"}'
166	TimeWrap string
167
168	// TimeMap if true will encode time as a map with a create key and a
169	// 'value' member formatted according to the TimeFormat options.
170	TimeMap bool
171
172	// CreateKey if set is the key to use when encoding objects that can later
173	// be reconstituted with an Unmarshall call. This is only use when writing
174	// simple types where one of the object in an array or map is not a
175	// Simplifier. Reflection is used to encode all public members of the
176	// object if possible. For example, is CreateKey is set to "type" this
177	// might be the encoding.
178	//
179	//   { "type": "MyType", "a": 3, "b": true }
180	//
181	CreateKey string
182
183	// NoReflect if true does not use reflection to encode an object. This is
184	// only considered if the CreateKey is empty.
185	NoReflect bool
186
187	// FullTypePath if true includes the full type name and path when used
188	// with the CreateKey.
189	FullTypePath bool
190
191	// Color if true will colorize the output.
192	Color bool
193
194	// SyntaxColor is the color for syntax in the JSON output.
195	SyntaxColor string
196
197	// KeyColor is the color for a key in the JSON output.
198	KeyColor string
199
200	// NullColor is the color for a null in the JSON output.
201	NullColor string
202
203	// BoolColor is the color for a bool in the JSON output.
204	BoolColor string
205
206	// NumberColor is the color for a number in the JSON output.
207	NumberColor string
208
209	// StringColor is the color for a string in the JSON output.
210	StringColor string
211
212	// TimeColor is the color for a time.Time in the JSON output.
213	TimeColor string
214
215	// NoColor turns the color off.
216	NoColor string
217
218	// UseTags if true will use the json annotation tags when marhsalling,
219	// writing, or decomposing an struct. If no tag is present then the
220	// KeyExact flag is referenced to determine the key.
221	UseTags bool
222
223	// KeyExact if true will use the exact field name for an encoded struct
224	// field. If false the key style most often seen in JSON files where the
225	// first character of the object keys is lowercase.
226	KeyExact bool
227
228	// HTMLUnsafe if true turns off escaping of &, <, and >.
229	HTMLUnsafe bool
230
231	// NestEmbed if true will generate an element for each anonymous embedded
232	// field.
233	NestEmbed bool
234
235	// BytesAs indicates how []byte fields should be encoded. Choices are
236	// BytesAsString, BytesAsBase64 (the go json package default), or
237	// BytesAsArray.
238	BytesAs int
239
240	// Converter to use when decomposing or altering if non nil.
241	Converter *Converter
242}
243
244// AppendTime appends a time string to the buffer.
245func (o *Options) AppendTime(buf []byte, t time.Time, sen bool) []byte {
246	if o.TimeMap {
247		buf = append(buf, '{')
248		if sen {
249			buf = AppendSENString(buf, o.CreateKey, o.HTMLUnsafe)
250		} else {
251			buf = AppendJSONString(buf, o.CreateKey, o.HTMLUnsafe)
252		}
253		buf = append(buf, ':')
254		if sen {
255			if o.FullTypePath {
256				buf = append(buf, `"time/Time" value:`...)
257			} else {
258				buf = append(buf, "Time value:"...)
259			}
260		} else {
261			if o.FullTypePath {
262				buf = append(buf, `"time/Time","value":`...)
263			} else {
264				buf = append(buf, `"Time","value":`...)
265			}
266		}
267	} else if 0 < len(o.TimeWrap) {
268		buf = append(buf, '{')
269		if sen {
270			buf = AppendSENString(buf, o.TimeWrap, o.HTMLUnsafe)
271		} else {
272			buf = AppendJSONString(buf, o.TimeWrap, o.HTMLUnsafe)
273		}
274		buf = append(buf, ':')
275	}
276	switch o.TimeFormat {
277	case "", "nano":
278		buf = strconv.AppendInt(buf, t.UnixNano(), 10)
279	case "second":
280		// Decimal format but float is not accurate enough so build the output
281		// in two parts.
282		nano := t.UnixNano()
283		secs := nano / int64(time.Second)
284		if 0 < nano {
285			buf = append(buf, fmt.Sprintf("%d.%09d", secs, nano-(secs*int64(time.Second)))...)
286		} else {
287			buf = append(buf, fmt.Sprintf("%d.%09d", secs, -(nano-(secs*int64(time.Second))))...)
288		}
289	default:
290		buf = append(buf, '"')
291		buf = t.AppendFormat(buf, o.TimeFormat)
292		buf = append(buf, '"')
293	}
294	if 0 < len(o.TimeWrap) || o.TimeMap {
295		buf = append(buf, '}')
296	}
297	return buf
298}
299
300// DecomposeTime encodes time in the format specified by the settings of the
301// options.
302func (o *Options) DecomposeTime(t time.Time) (v interface{}) {
303	switch o.TimeFormat {
304	case "time":
305		v = t
306	case "", "nano":
307		v = t.UnixNano()
308	case "second":
309		v = float64(t.UnixNano()) / float64(time.Second)
310	default:
311		v = t.Format(o.TimeFormat)
312	}
313	if o.TimeMap {
314		if o.FullTypePath {
315			v = map[string]interface{}{o.CreateKey: "time/Time", "value": v}
316		} else {
317			v = map[string]interface{}{o.CreateKey: "Time", "value": v}
318		}
319	} else if 0 < len(o.TimeWrap) {
320		v = map[string]interface{}{o.TimeWrap: v}
321	}
322	return
323}
324