Repository navigation
Library
z-fasta is also a Zig package. It gives a Zig program the same FASTA rules as the CLI, without starting a process:
-
IndexedReaderopens a FASTA and its index once and fetches many intervals from it. -
RecordReaderstreams whole records from anystd.Io.Reader, with no index. -
validationchecks a FASTA stream in one pass and can write a repaired copy in the same pass, with no index.
Choose by the work: stream when you read every record once, and open an index when you fetch selected records or intervals repeatedly. A program can do both at once, and streaming never needs an index.
The package needs Zig 0.16.0 or later. Fetch a release and add the module in build.zig:
zig fetch --save git+https://github.com/eneskemalergin/z-fastaconst z_fasta = b.dependency("z_fasta", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("z-fasta", z_fasta.module("z-fasta"));Then const zfasta = @import("z-fasta");. Every optimize mode builds the package; only the z-fasta executable itself rejects ReleaseSmall.
Every call that can fail takes a caller-owned zfasta.diagnostics.Failure and returns a zfasta.diagnostics.Error. Nothing prints or exits. Several codes share one error tag, so read the exact reason from the failure:
var failure: diagnostics.Failure = undefined;
try testing.expectError(error.InvalidRequest, reader.fetchInto(0, .{ .start = 3, .end = 2 }, &buf, &failure));
try testing.expectEqual(diagnostics.Code.@"request.end_before_start", failure.primary().?.code());Error diagnostics lists every code and its fields.
The package counts from zero; diagnostics and the CLI count from one.
- An ordinal is a record's position in the index, from 0, and it reaches every record, duplicates included.
- An interval is zero-based and half-open,
[start, end), and is never clipped: an end past the record fails withrequest.end_after_sequence. The CLI regionchr1:5-9is the interval.{ .start = 4, .end = 9 }. - In a failure,
record,line, andcolumnare one-based, as the CLI prints them: the record at ordinal 0 is reported as record 1.
Build the index once with the CLI (z-fasta index ref.fa), then:
var failure: diagnostics.Failure = undefined;
var reader = try zfasta.IndexedReader.open(allocator, io, fasta, .{}, &failure);
defer reader.deinit();
try testing.expectEqual(@as(usize, 2), reader.recordCount());
const a = (try reader.lookup("a", &failure)).?;
try testing.expectEqualStrings("a", a.name);
try testing.expectEqual(@as(u64, 12), a.length);
var buf: [16]u8 = undefined;
try testing.expectEqualStrings("ACGTA", try reader.fetchInto(a.ordinal, .{ .start = 4, .end = 9 }, &buf, &failure));
try testing.expect(try reader.lookup("missing", &failure) == null);
try testing.expect(try reader.record(2, &failure) == null);-
opentakes.{ .index = .automatic }by default, which prefersref.fa.zfiand thenref.fa.fai, asgetdoes..{ .index = .{ .fai = path } }or.{ .zfi = path }opens exactly that file and never falls back. -
openmaps a.zfiand checks its header, source identity, and layout, so it costs the same for any index size;record,lookup, andfetchIntocheck each record, side table, table entry, and name the first time they read it, and fail with its index diagnostic. A.faiis mapped and its names read into a name map atopen; each line is parsed and checked the first time it is used. Runz-fasta validate --indexto check a whole index. -
record(ordinal, &failure)andlookup(name, &failure)read no sequence and return null past the last record or for an unknown name. The returnednameborrows the reader untildeinit.lookupreturns the first record with that name; later duplicates are reachable by ordinal. -
fetchIntochecks the ordinal, the interval, and thatdstholdsend - startbytes before it writes anything; a read failure after that can leave a prefix. It returns the slice ofdstit filled. - A reader keeps the files open until
deinit. It is not safe to share between threads, and itsiomust outlive it. A gzip input fails withinput.unsupported_format.
var source: std.Io.Reader = .fixed(input);
var storage: [512]u8 = undefined;
var failure: diagnostics.Failure = undefined;
var records = zfasta.RecordReader.init(&source, &storage, &failure);
const first = (try records.next()).?;
try testing.expectEqualStrings("sp|P99999|TEST_HUMAN", first.id);
try testing.expectEqualStrings("Test protein one OS=Homo sapiens OX=9606 GN=TST1 PE=1 SV=2", first.description);
try testing.expectEqualStrings(line1 ++ line2 ++ line3, first.sequence);-
storageholds one record: its header line and its bases. A record that does not fit fails withresource.line_buffer_exceeded, which reports how many bytes it needed at least; nothing is allocated. - A record borrows
storageuntil the next call. After a failure, every later call returns the same error. - Records follow
index's rules: the identifier ends at the first space, tab, CR, or LF; whitespace and line ends are dropped from the sequence; a record without bases is skipped; duplicates are all returned. Bytes before the first header, a BOM included, fail withfasta.content_before_header.
const report = try testing.allocator.create(zfasta.validation.Report);
defer testing.allocator.destroy(report);
var buffer: [16]u8 = undefined;
var failure: diagnostics.Failure = undefined;
var reader = std.Io.Reader.fixed(data);
try zfasta.validation.validate(testing.allocator, &reader, &buffer, null, .{}, report, &failure);
try testing.expectEqual(@as(u64, 1), report.finding(.@"fasta.duplicate_identifier").occurrences);
try testing.expectEqual(@as(u8, 1), report.status(false, false));
reader = std.Io.Reader.fixed(data);
var output: std.Io.Writer.Allocating = .init(testing.allocator);
defer output.deinit();
try zfasta.validation.repair(testing.allocator, &reader, &buffer, null, .{}, 4, &output.writer, report, &failure);
try testing.expectEqualStrings(">a desc\nACGT\nACG\n>a\nAC?\n", output.written());- The rules, counts, and repair are the CLI's; see Validation and repair.
validation.Rulesselects the alphabet, header schema and limit, and the duplicate check's memory budget;.{}is the CLI's default. -
bufferis the read window; the CLI usesvalidation.READ_BUFFER_BYTES(256 KiB). Results do not depend on its size. -
Reportis a fixed-size value of about 16 KiB, so allocate it rather than placing it on a small stack.report.finding(code)gives a code's occurrences, records, and first occurrence;report.status(strict, repaired)gives the CLI's exit status. - Pass a
validation.NameSource(the open file and its size) when the input is a file: the duplicate check then keeps a fingerprint and an offset per identifier and re-reads names from the file. Withnull, it copies the identifiers. -
repairwrites the copy to any writer, wrapped at the given width, in the same pass. On any error, includingrepair.blocked, discard what the writer received. - The index comparison (
validate --index) is CLI-only for now.
z-fasta v0.4.0 | Home | Quick start | Cheat sheet | Troubleshooting | Releases | MIT License
z-fasta Wiki
Start
Use z-fasta
Understand behavior
- Index formats
- Coordinates and names
- Limits and supported formats
- Performance and correctness
- Tool comparison
Solve a problem
Contribute