Skip to content

Commit 0fdccbd

Browse files
donislawdevclaude
andauthored
An otlp file of one request ends in .json, and tfg formats names each ending (#231)
* An otlp file of one request ends in .json, and tfg formats names each ending An otlp file in the minified or indented layout is one JSON document, and its name ended in .jsonl - which tells a reader that picks its parser by the name to read it line by line. A setting can now declare the endings its values give (format.Property.Extensions), and the engine names every file by Descriptor.ExtensionFor, once for each target. The bytes of every file are unchanged, and a name template somebody wrote still wins. tfg formats otlp prints the endings beside the extension, tfg formats --json carries them as "extensions" on the setting, the site lists both endings, and the sentence under formatting says them. Two guards hold the declaration and the names the engine plans, with three mutation entries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * build(deps): bump github.com/nicksnyder/go-i18n/v2 from 2.6.1 to 2.7.0 The change Dependabot proposed in #222, with the notices naming the version the window now links. go-i18n is linked by the window alone. Between 2.6.1 and 2.7.0: the CLDR v49 plural rules (Vietnamese, Galician and French move between groups), ParseMessageBytes, and fixes to the goi18n command. The window speaks English and Polish, whose rules did not change. go.yaml.in/yaml/v3 moves from 3.0.4 to 3.0.5 in go.sum only - nothing links it. Both LICENSE files are the same text as in the versions before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * tfg formats is held to the endings a setting gives, in its text and its JSON The guards on this change held the names the engine plans, and nothing held what tfg formats says about them: the card could go back to ".jsonl" alone, and --json could drop "extensions", with every guard green. A guard now reads both - the card of otlp with its line written out, and the JSON of every format whose settings give an ending against what they declare - with two mutation entries, one for each output. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 2007ddf commit 0fdccbd

39 files changed

Lines changed: 396 additions & 49 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,13 @@ because it turns other people's test suites red.
1818

1919
- **A new format: `otlp`, OpenTelemetry traces and logs as OTLP JSON.**
2020
`tfg generate --format otlp` writes spans or log records to an exact size,
21-
one batch on each line as the OTLP file exporter writes them, or as one
22-
request for an OTLP endpoint (`formatting`). Settings choose `signal`, the
23-
batch, the start time and rate, trace depth, response codes and severities.
24-
A file of lines comes back from the OpenTelemetry Collector byte for byte,
25-
and none of its lines passes 1 MiB, where the Collector's file receiver
26-
drops a line without saying so. Metrics are not written yet.
21+
one batch on each line as the OTLP file exporter writes them (`.jsonl`), or
22+
one request for an OTLP endpoint (`.json`), chosen by `formatting`. Settings
23+
choose `signal`, batch, start time, rate, trace depth, response codes and
24+
severities. A file of lines comes back from the OpenTelemetry Collector byte
25+
for byte, and no line passes 1 MiB, where the Collector's file receiver
26+
silently drops it. `tfg formats --json` names each ending. Metrics are not
27+
written yet.
2728

2829
- **A file can be sent under a name no disk stores.** `send-as` in a recipe,
2930
`--send-as` on the command line and Send as in the window take a name such

‎THIRD-PARTY-NOTICES.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ is the module's own MIT, carried unchanged.
283283
| `github.com/jsummers/gobmp` | v0.0.0-20230614200233-a9de23ed2e25 | MIT | (c) 2012-2015 Jason Summers |
284284
| `github.com/mattn/go-runewidth` | v0.0.24 | MIT | (c) 2016 Yasuhiro Matsumoto |
285285
| `github.com/nfnt/resize` | v0.0.0-20180221191011-83c6a9932646 | ISC | (c) 2012, Jan Schlicht |
286-
| `github.com/nicksnyder/go-i18n/v2` | v2.6.1 | MIT | (c) 2014 Nick Snyder https://github.com/nicksnyder |
286+
| `github.com/nicksnyder/go-i18n/v2` | v2.7.0 | MIT | (c) 2014 Nick Snyder https://github.com/nicksnyder |
287287
| `github.com/rymdport/portal` | v0.4.2 | Apache-2.0 | none stated, see below |
288288
| `github.com/srwiley/oksvg` | v0.0.0-20221011165216-be6e8873101c | BSD-3-Clause | (c) 2018, Steven R Wiley |
289289
| `github.com/srwiley/rasterx` | v0.0.0-20220730225603-2ab79fcdd4ef | BSD-3-Clause | (c) 2018, Steven R Wiley |

‎go.mod‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ require (
9393
github.com/gen2brain/gav1d v0.2.5
9494
github.com/gen2brain/jxl v0.2.0
9595
github.com/goccy/go-yaml v1.19.2
96-
github.com/nicksnyder/go-i18n/v2 v2.6.1
96+
github.com/nicksnyder/go-i18n/v2 v2.7.0
9797
golang.org/x/sys v0.49.0
9898
golang.org/x/text v0.43.0
9999
)

‎go.sum‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/a
5252
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
5353
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
5454
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
55-
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
56-
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
55+
github.com/nicksnyder/go-i18n/v2 v2.7.0 h1:oiVQUmuCeNggvm/fUVb6/R0aP82BDVphZnmVHwut+J0=
56+
github.com/nicksnyder/go-i18n/v2 v2.7.0/go.mod h1:ZMYiWg8ndQSGNk4zbzbyJjrrVjQjuQ8pERFrnBik+c8=
5757
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
5858
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
5959
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -68,8 +68,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
6868
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
6969
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
7070
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
71-
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
72-
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
71+
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
72+
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
7373
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
7474
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
7575
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=

‎internal/cli/formats.go‎

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import (
66
"flag"
77
"fmt"
88
"io"
9+
"maps"
10+
"slices"
11+
"strings"
912

1013
"github.com/donislawdev/TestingFilesGenerator/internal/core"
1114
"github.com/donislawdev/TestingFilesGenerator/internal/format"
@@ -65,6 +68,12 @@ type propertyEntry struct {
6568
Default string `json:"default,omitempty"`
6669
Detail string `json:"detail,omitempty"`
6770
Group string `json:"group,omitempty"`
71+
// Extensions are the values of this setting that change what a file's
72+
// name ends in, each with its ending, so a script knows the name before
73+
// the file is made. Absent for every setting that changes no name, which
74+
// is every one a preset, a damage or a tool takes. Added on 2026-11-11,
75+
// which widens this output and changes the meaning of no key in it.
76+
Extensions map[string]string `json:"extensions,omitempty"`
6877
}
6978

7079
// smallestAccepted is the number this command prints as the minimum.
@@ -95,7 +104,7 @@ func entryFor(d format.Descriptor) formatEntry {
95104
props := make([]propertyEntry, 0, len(d.Properties))
96105
for _, p := range d.Properties {
97106
props = append(props, propertyEntry{
98-
Name: p.Name, Kind: string(p.Kind), Min: p.Min, Max: p.Max,
107+
Name: p.Name, Kind: string(p.Kind), Min: p.Min, Max: p.Max, Extensions: p.Extensions,
99108
Unit: p.Unit, Choices: p.Choices, Default: p.Default, Detail: p.Detail,
100109
Group: p.Group,
101110
})
@@ -120,7 +129,7 @@ func describeOne(d format.Descriptor, out io.Writer) {
120129
fmt.Fprintf(out, "%s - %s fidelity, %s deterministic, minimum %s\n",
121130
d.ID, d.Fidelity, d.Determinism, core.ExactBytes(smallestAccepted(d)))
122131
fmt.Fprintf(out, " name %s\n", d.Name)
123-
fmt.Fprintf(out, " extension %s\n", d.Extension)
132+
fmt.Fprintf(out, " extension %s\n", endings(d))
124133
fmt.Fprintf(out, " padding %s\n", d.Padding.Name)
125134
fmt.Fprintf(out, " label %s\n", d.Label)
126135
fmt.Fprintf(out, " oracle %s\n", d.Oracle)
@@ -151,6 +160,25 @@ func describeOne(d format.Descriptor, out io.Writer) {
151160
}
152161
}
153162

163+
// endings is the extension a file of d gets, and after it each one a setting
164+
// gives instead with the values that give it: ".jsonl, .json with formatting
165+
// indented or minified". Somebody choosing a layout learns the name before
166+
// the file is made, which is the point of declaring it.
167+
func endings(d format.Descriptor) string {
168+
line := d.Extension
169+
for _, p := range d.Properties {
170+
giving := map[string][]string{}
171+
for value, ext := range p.Extensions {
172+
giving[ext] = append(giving[ext], value)
173+
}
174+
for _, ext := range slices.Sorted(maps.Keys(giving)) {
175+
slices.Sort(giving[ext])
176+
line += fmt.Sprintf(", %s with %s %s", ext, p.Name, strings.Join(giving[ext], " or "))
177+
}
178+
}
179+
return line
180+
}
181+
154182
// What a property accepts is asked of the declaration itself, through
155183
// Property.Allowed. It used to be worked out here, which put the sentence one
156184
// import away from the window - and the window draws its field from the same

‎internal/engine/engine.go‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,9 @@ const indexToken = "{index:04}"
839839
// becoming one, across a flag and a preset that answer the same question.
840840
var boundaryRoles = [3]string{"under_1b", "at_limit", "over_1b"}
841841

842-
func renderName(t *Target, d format.Descriptor, index int) (string, error) {
842+
// renderName is the name of one file of t, which ends in ext unless the user
843+
// wrote a template.
844+
func renderName(t *Target, ext string, index int) (string, error) {
843845
tmpl := t.NameTmpl
844846
switch {
845847
case tmpl != "":
@@ -848,9 +850,9 @@ func renderName(t *Target, d format.Descriptor, index int) (string, error) {
848850
// A boundary set says which file is which. The role goes in as literal
849851
// text rather than as a token, so it still passes the same name checks
850852
// below - the id comes from the user and can hold anything.
851-
tmpl = t.ID + "_" + boundaryRoles[index] + d.Extension
853+
tmpl = t.ID + "_" + boundaryRoles[index] + ext
852854
default:
853-
tmpl = t.ID + "_" + indexToken + d.Extension
855+
tmpl = t.ID + "_" + indexToken + ext
854856
}
855857
name := strings.ReplaceAll(tmpl, indexToken, fmt.Sprintf("%04d", index+1))
856858

‎internal/engine/plantarget.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ type planning struct {
3535
// position is the target's place in the recipe, counted from one, and it is
3636
// what every refusal below carries so a window can mark the right box.
3737
func (pl *planning) files(ctx context.Context, t *Target, desc format.Descriptor, targetSeed uint64, position int) error {
38+
// One ending for every file of the target, because it follows the
39+
// target's settings and every file shares them.
40+
ext := desc.ExtensionFor(t.Properties)
3841
for idx, size := range t.Sizes {
3942
fileSeed := core.FileSeed(targetSeed, idx)
4043

@@ -65,7 +68,7 @@ func (pl *planning) files(ctx context.Context, t *Target, desc format.Descriptor
6568
})
6669
}
6770

68-
name, err := renderName(t, desc, idx)
71+
name, err := renderName(t, ext, idx)
6972
if err != nil {
7073
return atTarget(position, t, err)
7174
}

‎internal/format/extension.go‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package format
2+
3+
import (
4+
"maps"
5+
"slices"
6+
)
7+
8+
// ExtensionFor is what the name of a file of this format ends in when it is
9+
// made with props: the ending the chosen value of a setting names, and
10+
// Extension when none does.
11+
//
12+
// A setting that is absent or empty counts at its default, the way every
13+
// generator reads it, and a default never names an ending of its own - so that
14+
// is Extension too. The value is compared as it is spelled, as Allows compares
15+
// it, so the value the engine accepted is the value looked up here.
16+
//
17+
// The engine asks once for each target rather than once for each file: the
18+
// settings belong to the target, and a target can be a million files.
19+
func (d Descriptor) ExtensionFor(props map[string]string) string {
20+
for _, p := range d.Properties {
21+
if ext, named := p.Extensions[props[p.Name]]; named {
22+
return ext
23+
}
24+
}
25+
return d.Extension
26+
}
27+
28+
// Extensions is every ending a file of this format can have: Extension first,
29+
// then each one a setting can give instead, in order and once each. It is for
30+
// a surface that lists formats, and has no settings in hand to ask ExtensionFor
31+
// with.
32+
func (d Descriptor) Extensions() []string {
33+
others := map[string]bool{}
34+
for _, p := range d.Properties {
35+
for _, ext := range p.Extensions {
36+
others[ext] = true
37+
}
38+
}
39+
delete(others, d.Extension)
40+
return append([]string{d.Extension}, slices.Sorted(maps.Keys(others))...)
41+
}

‎internal/format/format.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,20 @@ type Property struct {
215215
// has (docs/NARZEDZIA-SUMY-2026-09-29.md §14, the owner's decision of
216216
// 2026-09-30). Ignored by every kind but text.
217217
Long bool
218+
219+
// Extensions are the values of a choice that change what the name of a
220+
// file ends in, each with the ending it gets - a layout that makes one
221+
// JSON document of a file that is JSON Lines by default. A value left out
222+
// keeps the format's Extension, and so does the default, which is never
223+
// listed: the default's ending has one place to live, and a guard refuses
224+
// a second.
225+
//
226+
// Declared rather than decided by the generator, so the name a file will
227+
// get is known before the file is made: tfg formats prints it, a script
228+
// reads it from --json, and a window shows it in the setting's sentence.
229+
// The engine names every file by Descriptor.ExtensionFor. Ignored by every
230+
// kind but choice. docs/ROZSZERZENIE-UKLADU-2026-11-11.md.
231+
Extensions map[string]string
218232
}
219233

220234
// Descriptor is everything a format announces about itself. A format missing

‎internal/format/otlp/settings.go‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,12 @@ func fileProperties() []format.Property {
281281
Name: Formatting, Kind: format.PropertyChoice,
282282
Choices: []string{Indented, Minified, RecordPerLine},
283283
Default: RecordPerLine,
284-
Detail: "How the file is laid out. record-per-line puts one batch on each line, which is what the OTLP file exporter writes and the file receiver of the OpenTelemetry Collector reads. " +
285-
"minified and indented make the whole file one request, the body to post to an OTLP endpoint, and a file of that kind is one JSON document whatever its name ends in. " +
284+
// A file of one request is one JSON document, and .jsonl would
285+
// tell a reader that picks its parser by the name to read it line
286+
// by line. docs/ROZSZERZENIE-UKLADU-2026-11-11.md.
287+
Extensions: map[string]string{Indented: ".json", Minified: ".json"},
288+
Detail: "How the file is laid out. record-per-line puts one batch on each line, which is what the OTLP file exporter writes and the file receiver of the OpenTelemetry Collector reads, and the file name ends in .jsonl. " +
289+
"minified and indented make the whole file one request, the body to post to an OTLP endpoint, and the file name ends in .json. " +
286290
"Posted as a body, a file of several lines loses every line after the first in the Collector, and its file receiver reads nothing from an indented file.",
287291
},
288292
{

0 commit comments

Comments
 (0)