Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@ because it turns other people's test suites red.

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

- **A file can be sent under a name no disk stores.** `send-as` in a recipe,
`--send-as` on the command line and Send as in the window take a name such
Expand Down
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ is the module's own MIT, carried unchanged.
| `github.com/jsummers/gobmp` | v0.0.0-20230614200233-a9de23ed2e25 | MIT | (c) 2012-2015 Jason Summers |
| `github.com/mattn/go-runewidth` | v0.0.24 | MIT | (c) 2016 Yasuhiro Matsumoto |
| `github.com/nfnt/resize` | v0.0.0-20180221191011-83c6a9932646 | ISC | (c) 2012, Jan Schlicht |
| `github.com/nicksnyder/go-i18n/v2` | v2.6.1 | MIT | (c) 2014 Nick Snyder https://github.com/nicksnyder |
| `github.com/nicksnyder/go-i18n/v2` | v2.7.0 | MIT | (c) 2014 Nick Snyder https://github.com/nicksnyder |
| `github.com/rymdport/portal` | v0.4.2 | Apache-2.0 | none stated, see below |
| `github.com/srwiley/oksvg` | v0.0.0-20221011165216-be6e8873101c | BSD-3-Clause | (c) 2018, Steven R Wiley |
| `github.com/srwiley/rasterx` | v0.0.0-20220730225603-2ab79fcdd4ef | BSD-3-Clause | (c) 2018, Steven R Wiley |
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ require (
github.com/gen2brain/gav1d v0.2.5
github.com/gen2brain/jxl v0.2.0
github.com/goccy/go-yaml v1.19.2
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/nicksnyder/go-i18n/v2 v2.7.0
golang.org/x/sys v0.49.0
golang.org/x/text v0.43.0
)
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/a
github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/nicksnyder/go-i18n/v2 v2.7.0 h1:oiVQUmuCeNggvm/fUVb6/R0aP82BDVphZnmVHwut+J0=
github.com/nicksnyder/go-i18n/v2 v2.7.0/go.mod h1:ZMYiWg8ndQSGNk4zbzbyJjrrVjQjuQ8pERFrnBik+c8=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand All @@ -68,8 +68,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/image v0.46.0 h1:b1+oYj0Jbp6K5MDT4i4/eZpYlk3V8SJhhDKh6LBHAyQ=
golang.org/x/image v0.46.0/go.mod h1:3B3W05VGVQyuXucLINLjXKrqISASfi4Xj+iCVkLMwew=
golang.org/x/net v0.62.0 h1:18ELalfM5+XNRwzM2nYqXsZQ9ByS1uhj2O/U7+5jqRY=
Expand Down
32 changes: 30 additions & 2 deletions internal/cli/formats.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (
"flag"
"fmt"
"io"
"maps"
"slices"
"strings"

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

// smallestAccepted is the number this command prints as the minimum.
Expand Down Expand Up @@ -95,7 +104,7 @@ func entryFor(d format.Descriptor) formatEntry {
props := make([]propertyEntry, 0, len(d.Properties))
for _, p := range d.Properties {
props = append(props, propertyEntry{
Name: p.Name, Kind: string(p.Kind), Min: p.Min, Max: p.Max,
Name: p.Name, Kind: string(p.Kind), Min: p.Min, Max: p.Max, Extensions: p.Extensions,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
Unit: p.Unit, Choices: p.Choices, Default: p.Default, Detail: p.Detail,
Group: p.Group,
})
Expand All @@ -120,7 +129,7 @@ func describeOne(d format.Descriptor, out io.Writer) {
fmt.Fprintf(out, "%s - %s fidelity, %s deterministic, minimum %s\n",
d.ID, d.Fidelity, d.Determinism, core.ExactBytes(smallestAccepted(d)))
fmt.Fprintf(out, " name %s\n", d.Name)
fmt.Fprintf(out, " extension %s\n", d.Extension)
fmt.Fprintf(out, " extension %s\n", endings(d))
fmt.Fprintf(out, " padding %s\n", d.Padding.Name)
fmt.Fprintf(out, " label %s\n", d.Label)
fmt.Fprintf(out, " oracle %s\n", d.Oracle)
Expand Down Expand Up @@ -151,6 +160,25 @@ func describeOne(d format.Descriptor, out io.Writer) {
}
}

// endings is the extension a file of d gets, and after it each one a setting
// gives instead with the values that give it: ".jsonl, .json with formatting
// indented or minified". Somebody choosing a layout learns the name before
// the file is made, which is the point of declaring it.
func endings(d format.Descriptor) string {
line := d.Extension
for _, p := range d.Properties {
giving := map[string][]string{}
for value, ext := range p.Extensions {
giving[ext] = append(giving[ext], value)
}
for _, ext := range slices.Sorted(maps.Keys(giving)) {
slices.Sort(giving[ext])
line += fmt.Sprintf(", %s with %s %s", ext, p.Name, strings.Join(giving[ext], " or "))
}
}
return line
}

// What a property accepts is asked of the declaration itself, through
// Property.Allowed. It used to be worked out here, which put the sentence one
// import away from the window - and the window draws its field from the same
Expand Down
8 changes: 5 additions & 3 deletions internal/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,9 @@ const indexToken = "{index:04}"
// becoming one, across a flag and a preset that answer the same question.
var boundaryRoles = [3]string{"under_1b", "at_limit", "over_1b"}

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

Expand Down
5 changes: 4 additions & 1 deletion internal/engine/plantarget.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type planning struct {
// position is the target's place in the recipe, counted from one, and it is
// what every refusal below carries so a window can mark the right box.
func (pl *planning) files(ctx context.Context, t *Target, desc format.Descriptor, targetSeed uint64, position int) error {
// One ending for every file of the target, because it follows the
// target's settings and every file shares them.
ext := desc.ExtensionFor(t.Properties)
for idx, size := range t.Sizes {
fileSeed := core.FileSeed(targetSeed, idx)

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

name, err := renderName(t, desc, idx)
name, err := renderName(t, ext, idx)
if err != nil {
return atTarget(position, t, err)
}
Expand Down
41 changes: 41 additions & 0 deletions internal/format/extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package format

import (
"maps"
"slices"
)

// ExtensionFor is what the name of a file of this format ends in when it is
// made with props: the ending the chosen value of a setting names, and
// Extension when none does.
//
// A setting that is absent or empty counts at its default, the way every
// generator reads it, and a default never names an ending of its own - so that
// is Extension too. The value is compared as it is spelled, as Allows compares
// it, so the value the engine accepted is the value looked up here.
//
// The engine asks once for each target rather than once for each file: the
// settings belong to the target, and a target can be a million files.
func (d Descriptor) ExtensionFor(props map[string]string) string {
for _, p := range d.Properties {
if ext, named := p.Extensions[props[p.Name]]; named {
return ext
}
}
return d.Extension
}

// Extensions is every ending a file of this format can have: Extension first,
// then each one a setting can give instead, in order and once each. It is for
// a surface that lists formats, and has no settings in hand to ask ExtensionFor
// with.
func (d Descriptor) Extensions() []string {
others := map[string]bool{}
for _, p := range d.Properties {
for _, ext := range p.Extensions {
others[ext] = true
}
}
delete(others, d.Extension)
return append([]string{d.Extension}, slices.Sorted(maps.Keys(others))...)
}
14 changes: 14 additions & 0 deletions internal/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,20 @@ type Property struct {
// has (docs/NARZEDZIA-SUMY-2026-09-29.md §14, the owner's decision of
// 2026-09-30). Ignored by every kind but text.
Long bool

// Extensions are the values of a choice that change what the name of a
// file ends in, each with the ending it gets - a layout that makes one
// JSON document of a file that is JSON Lines by default. A value left out
// keeps the format's Extension, and so does the default, which is never
// listed: the default's ending has one place to live, and a guard refuses
// a second.
//
// Declared rather than decided by the generator, so the name a file will
// get is known before the file is made: tfg formats prints it, a script
// reads it from --json, and a window shows it in the setting's sentence.
// The engine names every file by Descriptor.ExtensionFor. Ignored by every
// kind but choice. docs/ROZSZERZENIE-UKLADU-2026-11-11.md.
Extensions map[string]string
}

// Descriptor is everything a format announces about itself. A format missing
Expand Down
8 changes: 6 additions & 2 deletions internal/format/otlp/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,12 @@ func fileProperties() []format.Property {
Name: Formatting, Kind: format.PropertyChoice,
Choices: []string{Indented, Minified, RecordPerLine},
Default: RecordPerLine,
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. " +
"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. " +
// A file of one request is one JSON document, and .jsonl would
// tell a reader that picks its parser by the name to read it line
// by line. docs/ROZSZERZENIE-UKLADU-2026-11-11.md.
Extensions: map[string]string{Indented: ".json", Minified: ".json"},
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. " +
"minified and indented make the whole file one request, the body to post to an OTLP endpoint, and the file name ends in .json. " +
"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.",
},
{
Expand Down
Loading
Loading