A requirement is a way to specify some subset of works: for example, those with a given title, and/or version number.
§1. Creation. A requirement is, in effect, the criteria for performing a search. We can
specify the title, and/or the author name, and/or the genre -- all given
in the work field below, with those unspecified left blank -- and/or
we can give a semantic version number range:
typedef struct inbuild_requirement { struct inbuild_work *work; struct semver_range *version_range; CLASS_DEFINITION } inbuild_requirement;
- The structure inbuild_requirement is accessed in 2/edt, 2/cps, 2/ce, 2/jm, 3/bg, 3/is, 3/is2, 4/em, 4/ebm, 4/km, 4/lm, 4/pm, 4/pbm, 4/pfm, 4/tm, 5/es, 5/ks, 5/ls, 5/ps2, 6/st, 6/hdn, 6/inc, 7/tm, 7/eip, 7/ti, 7/tc, 7/dc, 7/dr and here.
inbuild_requirement *Requirements::new(inbuild_work *work, semver_range *R) { inbuild_requirement *req = CREATE(inbuild_requirement); req->work = work; req->version_range = R; return req; } inbuild_requirement *Requirements::any_version_of(inbuild_work *work) { return Requirements::new(work, VersionNumberRanges::any_range()); } inbuild_requirement *Requirements::anything_of_genre(inbuild_genre *G) { return Requirements::any_version_of(Works::new(G, I"", I"")); } inbuild_requirement *Requirements::anything(void) { return Requirements::anything_of_genre(NULL); }
genre=extension,author=Emily Short,title=Locksmith,min=6.1-alpha.2,max=17.2
We should return a requirement if this is valid, and write an error message if it is not. (If the text has multiple things wrong with it, we write only the first error message arising.)
At the top level, we have a comma-separated list of clauses. Note that the empty text is legal here, and produces an unlimited requirement.
inbuild_requirement *Requirements::from_text(text_stream *T, text_stream *errors) { inbuild_requirement *req = Requirements::anything(); int from = 0; for (int at = 0; at < Str::len(T); at++) { inchar32_t c = Str::get_at(T, at); if (c == ',') { TEMPORARY_TEXT(initial) Str::substr(initial, Str::at(T, from), Str::at(T, at)); Requirements::impose_clause(req, initial, errors); DISCARD_TEXT(initial) from = at + 1; } } if (from < Str::len(T)) { TEMPORARY_TEXT(final) Str::substr(final, Str::at(T, from), Str::end(T)); Requirements::impose_clause(req, final, errors); DISCARD_TEXT(final) } return req; }
void Requirements::impose_clause(inbuild_requirement *req, text_stream *T, text_stream *errors) { Str::trim_white_space(T); if (Str::eq(T, I"all")) return; TEMPORARY_TEXT(term) TEMPORARY_TEXT(value) for (int at = 0; at < Str::len(T); at++) { inchar32_t c = Str::get_at(T, at); if (c == '=') { Str::substr(term, Str::start(T), Str::at(T, at)); Str::substr(value, Str::at(T, at+1), Str::end(T)); break; } } Str::trim_white_space(term); Str::trim_white_space(value); if ((Str::len(term) > 0) && (Str::len(value) > 0)) { Deal with a term-value pair4.1; } else { if (Str::len(errors) == 0) WRITE_TO(errors, "clause not in the form 'term=value': '%S'", T); } DISCARD_TEXT(term) DISCARD_TEXT(value) }
§4.1. Deal with a term-value pair4.1 =
if (Str::eq(term, I"genre")) { inbuild_genre *G = Genres::by_name(value); if (G) req->work->genre = G; else if (Str::len(errors) == 0) WRITE_TO(errors, "not a valid genre: '%S'", value); } else if (Str::eq(term, I"title")) { Str::copy(req->work->title, value); } else if (Str::eq(term, I"author")) { Str::copy(req->work->author_name, value); } else if (Str::eq(term, I"version")) { semantic_version_number V = Requirements::semver(value, errors); req->version_range = VersionNumberRanges::compatibility_range(V); } else if (Str::eq(term, I"min")) { semantic_version_number V = Requirements::semver(value, errors); req->version_range = VersionNumberRanges::at_least_range(V); } else if (Str::eq(term, I"max")) { semantic_version_number V = Requirements::semver(value, errors); req->version_range = VersionNumberRanges::at_most_range(V); } else { if (Str::len(errors) == 0) WRITE_TO(errors, "no such term as '%S'", term); }
- This code is used in §4.
semantic_version_number Requirements::semver(text_stream *value, text_stream *errors) { semantic_version_number V = VersionNumbers::from_text(value); if (VersionNumbers::is_null(V)) if (Str::len(errors) == 0) WRITE_TO(errors, "not a valid version number: '%S'", value); return V; }
void Requirements::write(OUTPUT_STREAM, inbuild_requirement *req) { if (req == NULL) { WRITE("<none>"); return; } int claused = FALSE; if (req->work->genre) { if (claused) WRITE(","); claused = TRUE; WRITE("genre=%S", req->work->genre->genre_name); } if (Str::len(req->work->title) > 0) { if (claused) WRITE(","); claused = TRUE; WRITE("work=%S", req->work->title); } if (Str::len(req->work->author_name) > 0) { if (claused) WRITE(","); claused = TRUE; WRITE("author=%S", req->work->author_name); } if (VersionNumberRanges::is_any_range(req->version_range) == FALSE) { if (claused) WRITE(","); claused = TRUE; WRITE("range="); VersionNumberRanges::write_range(OUT, req->version_range); } if (claused == FALSE) WRITE("all"); }
§7. Meeting requirements. Finally, we actually use these intricacies for something. Given an edition,
we return TRUE if it meets the requirements and FALSE if it does not.
Note that requirements are based on the edition, not on the copy. If one copy on file of Version 3.2 of Monkey Puzzle Trees by Capability Brown meets a requirement, then so will all other copies of it.
int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) { if (req == NULL) return TRUE; if (req->work) { if (req->work->genre) if (Genres::equivalent(req->work->genre, edition->work->genre) == FALSE) return FALSE; if (Str::len(req->work->title) > 0) if (Str::ne_insensitive(req->work->title, edition->work->title)) return FALSE; if (Str::len(req->work->author_name) > 0) if (Str::ne_insensitive(req->work->author_name, edition->work->author_name)) return FALSE; } return VersionNumberRanges::in_range(edition->version, req->version_range); }
§8. This is a very weak form of testing that requirement A is stronger than
requirement B concerning the same work; it only catches the case where B
imposes no version constraints.
int Requirements::trumps(inbuild_requirement *A, inbuild_requirement *B) { if (B == NULL) return TRUE; if (A == NULL) return FALSE; if (B->work) { if (B->work->genre) if (B->work->genre != A->work->genre) return FALSE; if (Str::len(B->work->title) > 0) if (Str::ne_insensitive(B->work->title, A->work->title)) return FALSE; if (Str::len(B->work->author_name) > 0) if (Str::ne_insensitive(B->work->author_name, A->work->author_name)) return FALSE; } if (VersionNumberRanges::is_any_range(B->version_range)) return TRUE; return FALSE; }