@@ -38,10 +38,6 @@ import (
3838const (
3939 generatorVersion = "1"
4040
41- // The document is an array with one record per line. Minified on one line
42- // and indented forms come later as a property.
43- prologue = "[\n "
44-
4541 emailDomain = "@example.com"
4642
4743 // Fixed widths, so the parts that are not the note stay predictable.
@@ -54,36 +50,14 @@ const (
5450 // be asked for.
5551 maxIDDigits = 19
5652
57- // The literal parts of a record, named so the arithmetic below is a
58- // constant expression rather than a number somebody has to keep in step.
59- openID = `{"id":`
60- openName = `,"name":"`
61- openMail = `","email":"`
62- openAmt = `","amount":`
63- openAct = `,"active":`
64- nullPart = `,"retired":null`
65- openTags = `,"tags":["`
66- tagSep = `","`
67- openAddr = `"],"address":{"city":"`
68- openZip = `","zip":"`
69- openNote = `"},"note":"`
70- closeRec = `"}`
71-
72- // A record either has another one after it or closes the array.
73- tailMore = closeRec + ",\n "
74- tailLast = closeRec + "\n ]\n "
75-
7653 // widestBool is "false", the longer of the two.
7754 widestBool = 5
78-
79- // fixedWidth is every literal byte of a closing record - everything except
80- // the record number, the name, the two tags, the city and the note.
81- fixedWidth = len (openID ) + len (openName ) + len (openMail ) + len (emailDomain ) +
82- len (openAmt ) + amountWidth + len (openAct ) + widestBool + len (nullPart ) +
83- len (openTags ) + len (tagSep ) + len (openAddr ) + len (openZip ) + zipWidth +
84- len (openNote ) + len (tailLast )
8555)
8656
57+ // The literal parts of a record live on the style, because there are three
58+ // layouts of them and the arithmetic has to measure whichever one is in use.
59+ // See style.go.
60+
8761func init () {
8862 format .Register (format.Descriptor {
8963 ID : "json" ,
@@ -95,7 +69,12 @@ func init() {
9569 // by naming a byte count - that is a shape request, and it arrives with
9670 // the record count property. The minimum here is the array and one whole
9771 // record.
98- MinBytes : minimumBytes (),
72+ //
73+ // The DEFAULT layout's minimum, the way CSV declares its default
74+ // dialect's. Every other layout answers for itself, through the same
75+ // refusal, and SmallestAccepted asks the generator rather than reading
76+ // this number.
77+ MinBytes : minimumBytes (defaultStyle ()),
9978
10079 Padding : format.PaddingChannel {
10180 Name : "the note value of the last record" ,
@@ -107,27 +86,38 @@ func init() {
10786 // structure under test. The file name and the manifest carry it instead.
10887 Label : format .LabelExternalOnly ,
10988 Oracle : "node-json" ,
110- // Nesting depth, key counts, value types, indentation and NDJSON come
111- // later. Declaring none now makes a recipe asking for them fail loudly.
112- Properties : nil ,
89+ // Nesting depth, key counts, value types and NDJSON come later.
90+ // Declaring only what is here makes a recipe asking for them fail
91+ // loudly rather than quietly producing something else.
92+ Properties : properties (),
11393 GeneratorVersion : generatorVersion ,
11494 Generator : generator {},
11595 })
11696}
11797
11898type generator struct {}
11999
120- type memo struct { seed uint64 }
100+ type memo struct {
101+ seed uint64
102+ s style
103+ }
121104
122105func (generator ) Plan (r format.Request ) (format.Plan , error ) {
123- min := minimumBytes ()
106+ s , err := parseStyle (r .Properties )
107+ if err != nil {
108+ return format.Plan {}, err
109+ }
110+
111+ min := minimumBytes (s )
124112 if r .Bytes < min {
125113 return format.Plan {}, & format.BelowMinimumError {
126114 Format : "JSON" ,
127115 Requested : r .Bytes ,
128116 Minimum : min ,
129- Reason : "a document holds whole records and one record with every value type needs that much" ,
130- Hint : fmt .Sprintf ("Ask for %d B or more." , min ),
117+ Reason : fmt .Sprintf (
118+ "a document holds whole records, and one %s record with every value type needs that much" ,
119+ s .name ),
120+ Hint : fmt .Sprintf ("Ask for %d B or more." , min ),
131121 }
132122 }
133123
@@ -136,15 +126,15 @@ func (generator) Plan(r format.Request) (format.Plan, error) {
136126 Exact : true ,
137127 Determinism : format .DeterminismByte ,
138128 Properties : map [string ]any {
139- "encoding" : "utf-8" ,
140- "formatting" : "record-per-line" ,
141- "root" : "array" ,
142- "depth" : 3 ,
129+ "encoding" : "utf-8" ,
130+ Formatting : s . name ,
131+ "root" : "array" ,
132+ "depth" : 3 ,
143133 // Stated even though it is always false here, so a test can assert
144134 // on it without knowing which formats carry a label internally.
145135 format .PropertyLabelEmbedded : false ,
146136 },
147- Memo : memo {seed : r .Seed },
137+ Memo : memo {seed : r .Seed , s : s },
148138 }, nil
149139}
150140
@@ -154,24 +144,28 @@ func (generator) Write(ctx context.Context, w io.Writer, p format.Plan) error {
154144 return fmt .Errorf ("json: the plan was not produced by this generator" )
155145 }
156146
157- if err := core .WriteAll (w , []byte (prologue )); err != nil {
147+ if err := core .WriteAll (w , []byte (m . s . prologue )); err != nil {
158148 return err
159149 }
160150
161151 rng := core .NewRand (m .seed )
162- return core .FillRecords (ctx , w , rng , p .Bytes - int64 (len (prologue )), & records {})
152+ return core .FillRecords (ctx , w , rng , p .Bytes - int64 (len (m . s . prologue )), & records {s : m . s })
163153}
164154
165155// records builds the objects inside the array. It carries the record number, so
166- // the id counts up the way a real export does.
167- type records struct { next int64 }
156+ // the id counts up the way a real export does, and the layout the document is
157+ // being written in.
158+ type records struct {
159+ next int64
160+ s style
161+ }
168162
169163// Shortest is the smallest record this builder can close a document with: the
170164// widest record number, the longest word in all five places a word appears, the
171165// longer of the two booleans, and an empty note. It has to hold for every draw
172166// rather than for the lucky one.
173167func (r * records ) Shortest () int64 {
174- return int64 (maxIDDigits + 5 * longestWord + fixedWidth )
168+ return int64 (maxIDDigits + 5 * longestWord + r . s . fixed () )
175169}
176170
177171func (r * records ) Append (dst []byte , rng * rand.Rand ) []byte {
@@ -211,47 +205,47 @@ func (r *records) append(dst []byte, rng *rand.Rand, want int64) []byte {
211205 city := words [rng .IntN (len (words ))]
212206 zip := 10000 + rng .IntN (90000 )
213207
214- dst = append (dst , openID ... )
208+ dst = append (dst , r . s . openID ... )
215209 dst = strconv .AppendInt (dst , r .next , 10 )
216- dst = append (dst , openName ... )
210+ dst = append (dst , r . s . openName ... )
217211 dst = append (dst , name ... )
218- dst = append (dst , openMail ... )
212+ dst = append (dst , r . s . openMail ... )
219213 dst = append (dst , name ... )
220214 dst = append (dst , emailDomain ... )
221- dst = append (dst , openAmt ... )
215+ dst = append (dst , r . s . openAmt ... )
222216 dst = strconv .AppendInt (dst , int64 (whole ), 10 )
223217 dst = append (dst , '.' )
224218 if cents < 10 {
225219 dst = append (dst , '0' )
226220 }
227221 dst = strconv .AppendInt (dst , int64 (cents ), 10 )
228- dst = append (dst , openAct ... )
222+ dst = append (dst , r . s . openAct ... )
229223 if active {
230224 dst = append (dst , "true" ... )
231225 } else {
232226 dst = append (dst , "false" ... )
233227 }
234- dst = append (dst , nullPart ... )
235- dst = append (dst , openTags ... )
228+ dst = append (dst , r . s . nullPart ... )
229+ dst = append (dst , r . s . openTags ... )
236230 dst = append (dst , tagA ... )
237- dst = append (dst , tagSep ... )
231+ dst = append (dst , r . s . tagSep ... )
238232 dst = append (dst , tagB ... )
239- dst = append (dst , openAddr ... )
233+ dst = append (dst , r . s . openAddr ... )
240234 dst = append (dst , city ... )
241- dst = append (dst , openZip ... )
235+ dst = append (dst , r . s . openZip ... )
242236 dst = strconv .AppendInt (dst , int64 (zip ), 10 )
243- dst = append (dst , openNote ... )
237+ dst = append (dst , r . s . openNote ... )
244238
245239 if want < 0 {
246240 dst = appendPhrase (dst , rng , 3 + rng .IntN (5 ))
247- return append (dst , tailMore ... )
241+ return append (dst , r . s . tailMore ... )
248242 }
249243
250244 // Everything written so far, plus the bytes that close the record and the
251245 // array.
252- used := int64 (len (dst )- start ) + int64 (len (tailLast ))
246+ used := int64 (len (dst )- start ) + int64 (len (r . s . tailLast ))
253247 dst = appendFiller (dst , want - used )
254- return append (dst , tailLast ... )
248+ return append (dst , r . s . tailLast ... )
255249}
256250
257251// appendPhrase writes a readable note. Words and single spaces only - a JSON
@@ -281,9 +275,9 @@ func appendFiller(dst []byte, n int64) []byte {
281275// minimumBytes is the opening bracket and one whole record, computed rather
282276// than written down so it cannot drift away from the template the way a number
283277// in a document would.
284- func minimumBytes () int64 {
285- var r records
286- return int64 (len (prologue )) + r .Shortest ()
278+ func minimumBytes (s style ) int64 {
279+ r := records { s : s }
280+ return int64 (len (s . prologue )) + r .Shortest ()
287281}
288282
289283// longestWord is the widest draw, because the minimum has to hold for every
0 commit comments