Repository navigation
release: FixedWidthParser 2.0.0 - #82
Merged
Merged
Conversation
Bumps Meziantou.Analyzer from 3.0.123 to 3.0.137 Bumps SonarAnalyzer.CSharp from 10.29.0.143774 to 10.31.0.145097 --- updated-dependencies: - dependency-name: Meziantou.Analyzer dependency-version: 3.0.137 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: analyzers - dependency-name: SonarAnalyzer.CSharp dependency-version: 10.31.0.145097 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: analyzers ... Signed-off-by: dependabot[bot] <support@github.com>
--- updated-dependencies: - dependency-name: Microsoft.Testing.Extensions.TrxReport dependency-version: 2.3.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.Testing.Extensions.TrxReport dependency-version: 2.3.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…lop/analyzers-cc99cccae6 Bump the analyzers group with 2 updates
…s/FixedWidthParser.Generator.Tests/develop/multi-664984852b Bump Microsoft.Testing.Extensions.TrxReport from 2.3.2 to 2.3.3
--- updated-dependencies: - dependency-name: CsCheck dependency-version: 4.9.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
--- updated-dependencies: - dependency-name: Microsoft.Testing.Extensions.CodeCoverage dependency-version: 18.12.0 dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.Testing.Extensions.CodeCoverage dependency-version: 18.12.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Bumps xunit.runner.visualstudio from 3.1.5 to 4.0.1 Bumps xunit.v3.mtp-v2 from 3.2.2 to 4.0.2 --- updated-dependencies: - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: test - dependency-name: xunit.runner.visualstudio dependency-version: 4.0.1 dependency-type: direct:production update-type: version-update:semver-major dependency-group: test - dependency-name: xunit.v3.mtp-v2 dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: test - dependency-name: xunit.v3.mtp-v2 dependency-version: 4.0.2 dependency-type: direct:production update-type: version-update:semver-major dependency-group: test ... Signed-off-by: dependabot[bot] <support@github.com>
--- updated-dependencies: - dependency-name: System.IO.Pipelines dependency-version: 10.0.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com>
…FixedWidthParser/develop/System.IO.Pipelines-10.0.13 Bump System.IO.Pipelines from 10.0.10 to 10.0.13
…s/FixedWidthParser.Generator.Tests/develop/test-8eaacfb595 Bump the test group with 2 updates
…s/FixedWidthParser.Generator.Tests/develop/multi-203f4b367c Bump Microsoft.Testing.Extensions.CodeCoverage from 18.9.0 to 18.12.0
…s/FixedWidthParser.Tests/develop/CsCheck-4.9.1 Bump CsCheck from 4.7.0 to 4.9.1
2.0 keeps one parse path. The char parser, FixedWidthUtf8, FixedWidthByteReader, Utf8FixedWidthParser, IUtf8FixedWidthModel and IUtf8FixedWidthConverter are gone: there is one parser, one reader, one writer and one generated TryParse/TryFormat, all over bytes. Column positions are bytes; text columns, dates parsed with a format and types that only parse from characters are decoded with an ASCII-compatible encoding, UTF-8 by default. The writer formats straight to the stream, batched into one pooled buffer, and never splits a multi-byte character when it truncates. The package targets net10.0 only. Reads take FixedWidthReaderOptions (format provider, encoding, string pool, buffer size, MaxLineLength, OnParseError) and the writer takes FixedWidthWriterOptions (format provider, encoding, new line). Generated code and IFixedWidthConverter<T> receive a FixedWidthContext by in reference, so a later setting arrives as a property instead of a new parameter. A line that fails to parse throws FixedWidthParseException, a FormatException that names the line, the column, its position and its raw text, or says how short the line is. The reader learns the column by parsing the failed line again with a failure sink, so lines that parse pay nothing. OnParseError can skip the line instead. MaxLineLength bounds the line buffer on stream, async and pipe reads. A source without line breaks used to double the buffer until memory ran out. The generator targets netstandard2.0, reports FWP012 for a non-ASCII Padding or TrimChar, and its tests now compile the generated code. The library tracks its public API (PublicAPI.*.txt), validates the package against 1.1.1 with the deliberate breaks listed in CompatibilitySuppressions.xml, and builds with warnings as errors.
Packing runs package validation, so a removed or changed public member fails the pull request instead of the release.
The README describes the byte-only reader and writer, the options, the errors, encodings and the versioning rules. The changelog separates what shipped in 1.1.0 and 1.1.1 from 2.0, with a migration table. CONTRIBUTING explains the public API tracking and package validation.
… doubles as scaled integers IUtf8SpanFormattable columns format directly into the line's slice and are aligned in place, with no char buffer, transcode or copy. The UTF-8 bytes are kept when the encoding is UTF-8 or they are ASCII; otherwise the value goes through the configured encoding as before. A double with no Format, at most four decimals and a magnitude under 1e9 is written as a scaled integer, byte-identical to double.TryFormat, for any culture whose decimal separator is one ASCII character and negative sign is "-". Left-aligned strings under UTF-8 transcode straight into the slice.
Extract was over MA0051's 90-line limit. The per-member work moves to ExtractColumn, which returns null for a member that is not a mapped column.
Expression-bodied methods, constructors, operators and local functions and if without braces warn in the IDE; properties and their accessors are exempt (IDE0027 off). Private consts are PascalCase. MA0051 is enabled at 90 lines / 50 statements instead of suppressed. .gitattributes pins eol=lf so a blob's line endings never depend on who committed it.
STYLEGUIDE.md and ARCHITECTURE.md follow ExcelReader's, adapted to this library's twins (runtime and generated, stream/async/pipe readers) and its untrusted-input rules, and add the rule to avoid else. docs/ holds the measurements behind design decisions, starting with the writer. CONTRIBUTING and SECURITY are rewritten in the same shape, and a code of conduct is added.
FixedWidthReader<T>.AggregateFileAsync and FixedWidth.AggregateFileAsync cut a file into byte ranges, fold each on a worker thread into an IRecordAccumulator<TSelf, TModel>, and merge the accumulators in file order. A line belongs to the range its first byte falls in. Fixed-width lines have no quoting, so a worker that starts one byte before its range and skips the line it lands inside finds the exact boundary: every line is parsed exactly once. Line numbers in errors match a sequential read; they are counted only when a line fails. One range is the sequential read, so DegreeOfParallelism = 1 and small files run the same code.
FixedWidthReader<T>.AggregateAsync(Stream) and FixedWidth.AggregateAsync fold a stream across several threads. A stream cannot be read at an offset, so one producer reads it into pooled 256 KB chunks cut after the last line break and hands them through a bounded queue to the workers; the accumulators are merged in chunk order. The producer counts the line breaks it hands over, so every chunk knows the number of its first line and errors carry the sequential line number. A producer failure lies past every chunk it emitted, so a worker's failure is thrown first. DegreeOfParallelism = 1 reads with the async enumerator. LineBufferState.LineNumber becomes a long: a chunk's first line number can pass int.MaxValue, and so could the sequential reader's count. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The dedicated PipeRecordEnumerator read a 104 MB file in 472 ms; the same pipe through PipeReader.AsStream() into the Stream reader took 187-230 ms. It also duplicated every line rule LineBufferState implements, so removing it leaves one line splitter for every reader. Measurements are in docs/pipe-reader.md. BREAKING CHANGE: ReadAsync(PipeReader) is removed from FixedWidthReader<T> and FixedWidth, with FixedWidthPipeRecordEnumerable and PipeRecordEnumerator. Pass pipeReader.AsStream() to the Stream overloads. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
FixedWidthLayout.FromAttributes<T>() builds a model's column parsers and formatters once and caches them; FixedWidth.TryParse and TryFormat take a layout. The runtime parser and writer now sit on top of it. A model that only one side can handle keeps that side's error until the side is first used.
FixedWidth.Read, ReadFile, ReadAsync, ReadFileAsync, AggregateFileAsync and AggregateAsync gain a layout overload next to each generated one. A LayoutLineParser<T> strategy replaces ReflectionLineParser<T>, so the enumerators and both parallel aggregators are unchanged.
FixedWidth.Write, WriteMany, WriteAsync and WriteManyAsync each come as a generated overload and a layout overload. Batching moves out of FixedWidthWriter<T> into RecordWriter, generic over an IRecordFormatter<T> struct like the read side. A generated model has no known line length, so a record that does not fit flushes the batch and, if still too long, grows it. Generated models can now write to a stream.
FixedWidthLayoutBuilder<T> binds each column with a ColumnGetter and a ColumnSetter: Column (string or UTF-8 parsable), DecodedColumn, NullableColumn, ConvertedColumn and Factory. Per-column settings come in FixedWidthColumnOptions, with FixedColumnAttribute's defaults. The builder reaches the same typed parse and format builders as the attribute path, by generics instead of reflection, so the two produce identical results and Build stays trimming- and AOT-safe.
FixedWidthParser<T>, FixedWidthReader<T> and FixedWidthWriter<T> are removed. Attribute-mapped models are read and written through FixedWidth.*(..., FixedWidthLayout.FromAttributes<T>(), ...), the same overloads fluent layouts use. Tests and benchmarks are migrated; the package validation suppressions now list the removed types. BREAKING CHANGE: replace new FixedWidthReader<T>(options).ReadFile(path) with FixedWidth.ReadFile(path, FixedWidthLayout.FromAttributes<T>(), options), and likewise for the parser and the writer.
…write timings tests/AotSmoke builds a layout with FixedWidthLayout.Build and round-trips 1,000 records through FixedWidth; publishing it with NativeAOT must report no trimming or AOT warning. docs/fluent-layout.md compares reading and writing through layouts with the removed classes: every figure is within noise.
README shows reading and writing through FixedWidth with a layout and adds a section on fluent layouts. ARCHITECTURE describes the layout object, its two sources and RecordWriter. STYLEGUIDE and CHANGELOG follow the removal of the runtime classes.
Under the parallel test run, the per-line allocation guard sometimes saw a one-off ~8 KB allocation on its thread during one read (2.02 B/line over 4,000 extra lines); the baseline commit shows the same kind of failure. The guard now keeps the smallest of three measurements: a real per-line allocation still shows in all three, as a deliberately injected 24 B/line one does.
- ColumnParserFactory: nine near-identical parse-then-assign builders become one Bind over a ColumnValueParser, fed the FixedWidthRuntime helpers as method groups; ForString and ForConverter stop forwarding. - ColumnFormatOptions.WriteInto reuses AlignInPlace instead of repeating the alignment and sign-leftmost logic. - LineBufferState.Fill(Stream) replaces the refill loop copied in the sync enumerator and the native table reader. - ResolveOverflow becomes one switch expression; the unused ParserBuilder.ComputeRequiredLineLength is gone. - FixedColumnAttribute docs no longer mention the removed IUtf8FixedWidthConverter<T>.
EpochDay and Pow10 were declared in both ColumnBuilder and TableFormatter; they move to NativeType. NativeLayout and WriteSettings each had the same Fail helper; NativeApi.InvalidArgument replaces both. GrowableNativeBuffer loses Last, CopyTo and ByteLength, which nothing called.
The hand-written Equals and GetHashCode over fourteen fields become the compiler's, with an IsExternalInit polyfill for netstandard2.0. ModelInfo keeps its own equality, since it leaves Location out. Drops a stale comment on ByteLiteral.
A date or time format made only of fixed-width digit fields (yyyy, MM, dd, HH, mm, ss) and the literals - . T / : is compiled once per column into a FastDate that reads the bytes directly, as ExcelReader's FastDate reads ISO 8601. It steps aside for a non-Gregorian calendar, and for "/" or ":" when the culture's separators differ. DateTime, DateOnly, TimeOnly and native TIMESTAMP columns use it: 85 -> 18 ns per value. int and long columns of plain digits, and the native INT64, implied- decimal FLOAT64 and DECIMAL columns, go through FastInteger, which reuses FastDouble's eight-digit step: 14 -> 9 ns per value. A leading "-" is taken only when the culture's negative sign is "-". Anything either refuses goes to the BCL. New tests check both against it in cultures where the fast paths must not apply (th-TH, de-DE, sv-SE). A single-threaded COTAHIST read through Python drops from 0.920 to 0.678 s; the parallel read barely moves (0.261 -> 0.250 s).
Timed inside the native read, every partition stayed busy but slowed as threads were added (1.0x at 2, 1.7x at 8, 3x at 16), with an in-memory source no better than a file. A read writes ~200 MB of columns: 50,000 demand-zero page faults on one thread, 100,000 in parallel because the concatenation copies everything into fresh memory again. - A partition sizes its column buffers once from its byte count, which fixed-width lines turn into a row count, instead of doubling them from 256 elements. - skip_invalid skips a bad line through an internal OnSkip hook instead of building its exception: numbering B3's trailer counted line breaks from the start of the file on one thread (~27 ms). - Publish concatenates one column per thread. COTAHIST through Python, 1M lines: fw.read 0.250 -> 0.169 s, one thread 0.678 -> 0.580 s.
bench_write.py reads a synthetic COTAHIST file once into pandas, polars and pyarrow tables, then times each writer turning the table back into its record lines, asserting every one reproduces the file's body byte for byte. pandas and polars have no fixed-width writer, so the baselines are numpy.savetxt with printf fields and polars pad + concat_str. The generator now caps a day's quantity at 10^9 shares: its 16-digit volumes did not survive a float64, so no writer could reproduce them. 1M lines: fw.write (pyarrow) 0.259 s, polars 0.722 s, savetxt 4.285 s.
…n parallel At 16 threads fw_write_file took 240 ms where fw_write_memory took 141, with the two equal on one thread: Windows runs one synchronous I/O at a time on a handle, so the parallel batches queued. The file is now opened for asynchronous I/O; each batch still waits for its write, but they run together (240 -> 170 ms). A right-aligned int or long with no format writes its digits from the right straight into the column, then the padding, with "-" kept leftmost (19 -> 9 ns): formatting at the start and aligning after cost a copy. It applies only when the provider's negative sign is "-". A date or time whose format is only digit fields reuses the reader's compiled FastDate to write its digits (47 -> 23 ns). A test checks both against the BCL's char path in four cultures, both alignments, and zero and space padding. COTAHIST through Python, 1M lines: fw.write 0.259 -> 0.162 s, one thread 0.838 -> 0.550 s.
A whole-source read allocates the final columns once and lends each partition the slice its byte offset gives, with the first line's length as the stride. Numeric, date and time values and STRING bytes are parsed in place; publishing moves each partition down to close the gaps skipped lines and trimmed text leave, and hands the buffers over. A partition that outgrows its slice copies its column out, and that column is concatenated as before. STRING offsets are still copied. A parallel COTAHIST read through Python: fw.read 0.184 -> 0.153 s, fw.read_polars 0.198 -> 0.165 s.
Follows ExcelReader's release workflow. A v* tag now builds the native library on Windows x64, macOS arm64 and x64, and Linux glibc and musl on x64 and arm64; tests and builds one wheel per platform with the tag's version; publishes the NuGet package, the wheels to PyPI through Trusted Publishing, and the native libraries and an SBOM to the GitHub Release. A tag with a "-" is a prerelease. check_wheel_tag.py and build_musl_wheel.sh come from ExcelReader. pyarrow has no musllinux wheels, so the pandas, polars, Arrow and write tests skip without it and the musl job tests the NumPy read.
Follows ExcelReader's workflows. - benchmark-pr is opt-in through the run-benchmarks label, reads its groups from tests/Benchmarks/benchmark-groups.json (shared with benchmark-pages), guards that every benchmark belongs to a group, and comments a delta against the published master series. benchmark-pages runs only when its paths change. The stale IntHeavyParserBenchmarks pattern is gone. - ci filters by path and skips drafts. The required Build & Test jobs always start and skip their steps. Linux builds the solution with the analyzers, checks whitespace, packs and runs every test with coverage; Windows and macOS build and run the library tests without analyzers. The dependency audit moves to audit.yml, weekly and on dependency changes. - codeql filters by path the same way, adds Python, and builds only the shipped projects without analyzers. - native-bindings splits into Python, C++ and musl jobs with their own filters, tests every platform a release ships, and builds the wheel. - codecov.yml flags C# and Python coverage with carryforward; coverage.runsettings leaves out generated code and the C ABI exports. - release promotes PublicAPI.Unshipped to Shipped in a PR after a stable tag. - global.json pins SDK 10.0.402, which every workflow now installs and caches. - Dependabot groups follow the test and benchmark packages in use.
feat!: FixedWidthParser 2.0 — bytes-only API, native library and Python package
--- updated-dependencies: - dependency-name: Microsoft.Testing.Extensions.TrxReport dependency-version: 2.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
--- updated-dependencies: - dependency-name: Microsoft.Testing.Extensions.TrxReport dependency-version: 2.5.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #82 +/- ##
==========================================
+ Coverage 89.18% 94.37% +5.19%
==========================================
Files 37 44 +7
Lines 1738 2917 +1179
Branches 322 478 +156
==========================================
+ Hits 1550 2753 +1203
+ Misses 117 108 -9
+ Partials 71 56 -15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ve library Follows ExcelReader's C++ packaging. The wrapper resolves the native library from FIXEDWIDTH_NATIVE_LIB, then the repo's published copy, then the GitHub Release asset for FIXEDWIDTH_VERSION (the checked-out tag by default), saved under the library's own name so the Windows import library and the Linux SONAME match it. Consumers need no .NET SDK. FIXEDWIDTH_INSTALL installs the headers, the library, the Windows import library and a fixedwidth config package; find_package(fixedwidth) never downloads. cpp/tests/package builds against an installed prefix, and the C++ CI job now installs and runs it on Linux, Windows and macOS. The README covers FetchContent, the installed package and the settings.
As ExcelReader does: an enum column reads a member's name in any case or its number, zero-padded codes included (02), and rejects a number the enum does not define and a combination of [Flags] members. It writes the name, or the number with Format = "D". FixedWidthRuntime.TryEnum ports ExcelReader's EnumCache, one frozen case-insensitive map of names and numbers per enum, and widens ASCII text straight to chars, so a column costs what an int column does (~17 ns, against ~29 ns for Enum.TryParse with Enum.IsDefined). Attribute layouts and generated models take an enum property as it is, nullable included; fluent layouts add EnumColumn and NullableEnumColumn.
Follows ExcelReader's xl_stream. An fw_stream is a read callback, which returns the bytes it copied, 0 at the end or -1 after fw_set_source_error, and an optional release the library calls once it is done, including when opening fails. fw_reader_open_stream and fw_read_arrow_stream_stream read it forward in constant memory, skipping the header lines through a buffer instead of seeking. Python's readers take any object with readinto() or read(): read_batches and read_arrow_stream pull from it as batches are taken, the whole-table reads read it into memory first, and an io.BytesIO is read in place from its position. A Python exception in read() reaches the caller with its message. The C++ wrapper takes a std::istream& in Reader::open_stream, TypedReader<T>::open_stream and read_arrow_stream.
A fresh 1,000,000-line run against the current build: fw.read takes 0.125 s, 123x pandas.read_fwf, read_pandas 96x and read_polars 114x, and fw.write 0.148 s. The README and CHANGELOG no longer claim 52x, and the benchmark page lists the versions it ran with. The Python and native READMEs now say what holds across threads: every call releases the GIL for its native work, a layout is immutable and shared, a failure's message and line number belong to the thread whose call failed, a reader is used by one thread at a time, and many calls at once are better off with degree_of_parallelism 1 each.
The partitioned read parses into slices of the final columns sized from the first line's length, then closes the gaps in place or copies a column out when a slice overflows. A CsCheck property drives every path through it with 2,000 random files: invalid, empty and short lines, trailing bytes and CRLF that change the line length, a byte order mark, header lines, multi-byte text, and Latin-1 text longer as UTF-8 than its column. Whatever the chunk size and thread count, the table, the skipped count and a failure's line and column must be the sequential read's. Breaking the in-place compaction or the copy out of an outgrown slice on purpose fails it.
The last copy a partitioned read made was each STRING column's offsets. The arena now lends them as well: a slice's index 0 is the previous slice's last offset, so only the first slice writes the column's leading 0, a later one claims its index 0 without writing it, and Truncate knows row 0 starts at 0 instead of reading it. Publishing rebases the offsets in place, moving down in order as values do; a slice that outgrows its room copies out and the column is copied as before. The copy was ~32 MB on the COTAHIST benchmark: fw.read 0.125 -> 0.121 s, within noise. The property test crashes when Truncate reads index 0.
A column of few distinct values (a record type, a market code, a currency) costs far less as a category than as strings. fw_column_spec gains dictionary, allowed on STRING columns only: the Arrow reads and streams hand such a column over as dictionary<int32, utf8>, indices in the order values first appear, so pandas reads it as category and polars as Categorical. Each column is encoded once at export with an open-addressing table, the columns in parallel; a stream batch carries a dictionary of its own. fw_table and fw.read keep plain strings, and Table.to_arrow() encodes the same columns. Categorical input writes like strings. Python takes fw.Column(dictionary=True), C++ ColumnSpec::dictionary. On the synthetic COTAHIST file with all nine string columns encoded, a read costs 23-41 ms more and the pandas frame drops from 294 to 209 MiB; its values are mostly unique, so real low-cardinality columns save more. fw_column_spec grows from 72 to 80 bytes; the C smoke test pins it.
fw_write_stream pushes the lines to an fw_sink, a write callback that takes every byte it is given and returns 0, or -1 after fw_set_source_error. A window of one batch per thread formats in parallel, then goes to the sink in order from the calling thread, so memory stays at a window of batches whatever the table's size. Python's fw.write takes a binary stream as well as a path: an open file, gzip.open(..., "wb"), a socket's makefile. The standard streams get the native buffer itself; anything else gets a copy it may keep. Writing to an open file takes 0.150 s on the 1M-line benchmark against 0.146 s for its path. A Python exception in write() reaches the caller with its message. The C++ wrapper has write_stream(std::ostream&, ...). The C smoke test now also pins fw_stream and fw_sink and checks every stream export resolves.
…s/FixedWidthParser.Tests/develop/Microsoft.Testing.Extensions.TrxReport-2.5.1 Bump Microsoft.Testing.Extensions.TrxReport from 2.3.3 to 2.5.1
…s/FixedWidthParser.Generator.Tests/develop/Microsoft.Testing.Extensions.TrxReport-2.5.1 Bump Microsoft.Testing.Extensions.TrxReport from 2.3.3 to 2.5.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Merges
developintomasterfor the 2.0.0 release. Almost all of it is the 2.0 work from #79: a bytes-only API with a singleFixedWidthentry point, a NativeAOT C library, and Python and C++ bindings over it. The rest is dependency updates and CI changes.Full details are in #79, and the user-facing changes, with a 1.x → 2.0 migration table, are in the CHANGELOG.
What's in it
FixedWidthLayout<T>, options objects, root namespace,net10.0only, csFastFloat removed. New: parallel aggregation,FixedWidthParseException,ref structmodels, the native library (fixedwidth.h), thefixedwidthPython package and the C++23 wrapper.v*tag publishes the NuGet package, Python wheels to PyPI asfixedwidthparser(Windows x64, macOS arm64/x64, Linux glibc/musl on x64/arm64), each platform's native library and an SBOM to the GitHub Release. After a stable tag it opens a PR that promotes the public API.run-benchmarks.Bindings resultgathers the bindings jobs into one check that branch protection can require.Validation
develop: Build & Test (Linux, Windows, macOS), NativeAOT smoke, CodeQL (C#, Python), Bindings result.developthrough the release workflow: NuGet2.0.0-rc.1, PyPI2.0.0rc1, and native libraries on the GitHub Release.read_pandas,read_polars,read_arrow, and a byte-identicalwrite_bytesround trip.net10.0app: the source generator ships and runs, and generated and attribute-layout reads give the same rows.PublicAPI.Unshipped.txt.Release steps
masterkeeps the history the tag points at.v2.0.0on the merge commit. The release workflow publishes to NuGet, PyPI and the GitHub Release.chore: promote public API for v2.0.0PR that the workflow opens intodevelop.develop, re-enable package validation with baseline 2.0.0 (see the note insrc/FixedWidthParser/FixedWidthParser.csproj).