Help us shape the future of Go! Take the 2026 Go Developer Survey

corpus

package
v0.3.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 1, 2026 License: BSD-3-Clause Imports: 18 Imported by: 0

Documentation

Overview

Package corpus builds and records the populations of real PDFs that the conformance tools are run against.

A corpus is a directory of files and a MANIFEST.tsv beside them. The manifest is what makes the corpus a measurement rather than a pile: it says where each file came from, when, how big it is and what its bytes hash to, so a figure quoted from it can be reproduced and a file that changed underneath can be noticed.

Index

Constants

View Source
const ManifestName = "MANIFEST.tsv"

ManifestName is the file a corpus records itself in, beside its documents.

Variables

View Source
var Now = func() time.Time { return time.Now() }

Now is the clock the harvester stamps entries with, so a test can hold it still.

Functions

func Digest

func Digest(r io.Reader, w io.Writer) (string, int64, error)

Digest is the SHA-256 of what r yields, and how many bytes that was.

func Origins

func Origins(entries []Entry) map[string]int

Origins counts the documents in each population, which is the number any prevalence has to be divided by.

func Write

func Write(dir string, entries []Entry) error

Write records a corpus, sorted by path so that two runs that fetched the same documents in a different order produce the same file.

Types

type Archive

type Archive struct {
	// Base is the site, so a test can point this at its own server.
	Base string
	// Client is the one used for every request; nil means http.DefaultClient.
	Client *http.Client
}

An Archive fetches documents from the Internet Archive, which is where mass digitisation lives — and therefore where the codecs a form corpus never exercises are found. A scanned page is a fax: JBIG2 and CCITT are what it is stored in, and almost nothing else uses them.

func (*Archive) Fetch

func (a *Archive) Fetch(ctx context.Context, identifier, name string, w io.Writer) (string, int64, error)

Fetch writes one of an item's files to w and returns its digest and size.

func (*Archive) PDF

func (a *Archive) PDF(ctx context.Context, identifier string) (File, error)

PDF finds an item's PDF and how big it is. An item may hold several — a scanned book often has both a full-colour one and a bitonal one — and the smallest is taken, because the bitonal one is the scanned page as a fax, which is the thing worth having here.

func (*Archive) Search

func (a *Archive) Search(ctx context.Context, query string, rows, page int) ([]string, error)

Search returns the identifiers of items matching a query, one page at a time. rows above a few hundred are refused by the site, so a caller after more asks for more pages rather than a bigger page.

func (*Archive) URL

func (a *Archive) URL(identifier, name string) string

URL is where one of an item's files lives, which the manifest records so that a document can be fetched again.

type Entry

type Entry struct {
	// Path is relative to the corpus directory, so a corpus can be moved.
	Path string
	// Origin is the population the document was drawn from — an issuing body,
	// an archive collection — and is what a prevalence is computed over. Two
	// documents from different origins are not interchangeable evidence.
	Origin string
	// Source is the URL it was fetched from.
	Source string
	Bytes  int64
	// SHA256 is of the bytes as fetched, so a figure can be tied to the exact
	// document that produced it. Empty when the manifest did not say.
	SHA256 string
	// Fetched is when it was taken. The zero time means the manifest did not
	// say — some documents predate the record that describes them.
	Fetched time.Time
}

An Entry is one document and where it came from.

func Harvest

func Harvest(ctx context.Context, a *Archive, p Plan) ([]Entry, error)

Harvest adds documents to a corpus until the plan's origin holds Want of them, and records every one in the manifest.

It is resumable by construction: what is already in the manifest is skipped, so an interrupted run is continued by running it again, and a corpus is extended by asking for a larger Want.

func Read

func Read(dir string) ([]Entry, error)

Read loads a corpus's manifest.

The header says what the columns are, so a manifest written by an earlier tool is still readable: the forms corpus gathered before this repository existed says "issuer|file|url|bytes|sha256-8|fetched", which is the same six facts under other names. Making the format self-describing was cheaper than re-fetching two thousand documents, and a corpus that has to be re-fetched to be read is not much of a record.

A directory with no manifest is an empty corpus rather than an error: that is what a corpus about to be built looks like.

type File

type File struct {
	Name  string
	Bytes int64
}

A File is one of an item's files: its name and how big it is.

type Plan

type Plan struct {
	// Dir is the corpus. It is created if it is not there.
	Dir string
	// Origin names the population these documents belong to. It is also the
	// subdirectory they land in, so a corpus of several populations stays
	// countable — a prevalence is per origin or it is not a prevalence.
	Origin string
	// Query is handed to the archive's search.
	Query string
	// Want is how many documents to end up with in this origin.
	Want int
	// MaxBytes refuses a document larger than this. A scanned book can be
	// hundreds of megabytes and says no more about which codec it uses than a
	// small one does.
	MaxBytes int64
	// Workers is how many are fetched at once. Be modest: this is somebody
	// else's server.
	Workers int
	// Log, when set, is told what happened to each document.
	Log func(string, ...any)
}

A Plan says what to add to a corpus and from where.

type Problem

type Problem struct {
	// Path is relative to the corpus directory, as the manifest writes it.
	Path string
	// Kind is why: "missing", "unreadable", "size", "digest" or
	// "unrecorded".
	Kind string
	// Want and Got say what the manifest records and what is on disk. Both
	// are empty for a kind where there is nothing to compare.
	Want, Got string
}

A Problem is one way a corpus and the manifest beside it disagree.

func Check

func Check(dir string) ([]Problem, error)

Check reports every way a corpus directory and its manifest disagree.

The package comment says the manifest is what makes a corpus a measurement rather than a pile, "so a figure quoted from it can be reproduced and a file that changed underneath can be noticed". Nothing noticed: the hash was recorded and never read back, and a file in no row was invisible to every tool here.

It is not a hypothetical. Measured 2026-10-04 over the two corpora this repository is run against, FOUR files of `pdfscans` were on disk and in no manifest row -- and three of those four are documents our reader refuses, so `images` and `compare`, which walk the manifest, counted 63 refusals where a sweep that walked the directory counted 66. Two true numbers for one corpus, and no way to tell which a figure came from. See baseline/README.md §33.

The digest is compared over AS MANY CHARACTERS AS THE MANIFEST RECORDS. The forms corpus, gathered before this repository existed, keeps sixteen of them under the header "sha256-8"; the scans corpus keeps all sixty-four. Sixteen hex characters is sixty-four bits, which answers "did this file change" perfectly well, and a check that demanded all sixty-four would call every one of 2 268 rows changed and be deleted by the first person to run it.

func (Problem) String

func (p Problem) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL