Registries are nests provided with metadata and intended to be presented as an online source from which Inform resources can be downloaded.
- §1. Creation
- §3. The roster
- §5. Building
- §24. Escapology
- §25. Looking up by textual ID
- §28. Simpler preprocessing
§1. Creation. To "create" a registry here does not mean actually altering the file system, for example by making a directory: registries here are merely notes in memory of positions in the file system hierarchy which may or may not exist.
classdef inbuild_registry { struct pathname *location; struct inbuild_nest *nest; struct JSON_value *roster; }
- The structure inbuild_registry is accessed in 1/ic, 2/nst, 3/bg, 3/is2, 4/em, 4/ebm, 4/km, 4/lm, 4/pm, 4/tm, 5/ps2, 6/inc, 7/eip, 7/mrp and here.
inbuild_registry *Registries::new(pathname *P) { inbuild_registry *N = CREATE(inbuild_registry); N->location = P; N->nest = Nests::new(Pathnames::down(P, I"payloads")); N->roster = NULL; return N; }
§3. The roster. This is a JSON file called roster.json, whose schema must match the one specified
by the file registry-metadata.jsonr in the standard Inform distribution.
The following silently returns TRUE if it does, or prints errors and returns
FALSE if not (or if it doesn't exist).
int Registries::read_roster(inbuild_registry *R) { if (R == NULL) internal_error("no registry"); R->roster = NULL; filename *F = Filenames::in(R->location, I"roster.json"); if (TextFiles::exists(F) == FALSE) { WRITE_TO(STDERR, "%f: roster file does not exist\n", F); return FALSE; } TEMPORARY_TEXT(contents) TextFiles::read(F, FALSE, "unable to read file of JSON metadata", TRUE, &JSONMetadata::read_metadata_file_helper, NULL, contents); text_file_position tfp = TextFiles::at(F, 1); JSON_value *obj = JSON::decode(contents, &tfp); DISCARD_TEXT(contents) if ((obj) && (obj->JSON_type == ERROR_JSONTYPE)) { WRITE_TO(STDERR, "%f: JSON syntax error: %S\n", F, obj->if_error); return FALSE; } else { JSON_requirement *req = Registries::requirements(); linked_list *validation_errors = NEW_LINKED_LIST(text_stream); if (JSON::validate(obj, req, validation_errors) == FALSE) { text_stream *err; LOOP_OVER_LINKED_LIST(err, text_stream, validation_errors) { WRITE_TO(STDERR, "%f: metadata did not validate: '%S'\n", F, err); } return FALSE; } } R->roster = obj; return TRUE; }
§4. The following schema validates the metadata for a registry, and is cached so that it only needs to load once.
dictionary *JSON_registry_metadata_requirements = NULL; JSON_requirement *Registries::requirements(void) { if (JSON_registry_metadata_requirements == NULL) { filename *F = InstalledFiles::filename(REGISTRY_JSON_REQS_IRES); JSON_registry_metadata_requirements = JSON::read_requirements_file(NULL, F); } JSON_requirement *req = JSON::look_up_requirements(JSON_registry_metadata_requirements, I"registry-metadata"); if (req == NULL) internal_error("JSON metadata file did not define <registry-metadata>"); return req; }
§5. Building. To "build" a registry doesn't involve very much: just putting some indexing files together, using the preprocessor built into foundation.
If the registry is R, we preprocess each file R/source/X.Y into R/X.Y:
void Registries::build(inbuild_registry *R) { if (R == NULL) internal_error("no registry"); linked_list *ML = NEW_LINKED_LIST(preprocessor_macro); Construct the list of custom macros for this sort of preprocessing5.1; pathname *S = Pathnames::down(R->location, I"source"); linked_list *L = Directories::listing(S); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry)) == FALSE) { filename *F = Filenames::in(S, entry); TEMPORARY_TEXT(EXT) Filenames::write_extension(EXT, F); if (Str::len(EXT) > 0) { filename *T = Filenames::in(R->location, Filenames::get_leafname(F)); WRITE_TO(STDOUT, "%f -> %f\n", F, T); Preprocessor::preprocess(F, T, NULL, ML, STORE_POINTER_inbuild_registry(R), '#', UTF8_ENC); } DISCARD_TEXT(EXT) } } }
§5.1. Construct the list of custom macros for this sort of preprocessing5.1 =
Preprocessor::new_macro(ML, I"include", I"file: LEAFNAME", Registries::include_expander, NULL); Preprocessor::new_macro(ML, I"include-css", I"?platform: PLATFORM", Registries::css_expander, NULL); Preprocessor::new_macro(ML, I"process", I"file: LEAFNAME", Registries::process_expander, NULL); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"section", I"of: ID", Registries::section_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"subsection", I"of: ID", Registries::subsection_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"author", I"of: ID ?escape: WHICH", Registries::author_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"title", I"of: ID ?escape: WHICH", Registries::title_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"version", I"of: ID ?escape: WHICH", Registries::version_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"summary", I"of: ID ?escape: WHICH", Registries::summary_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"forum-thread", I"of: ID", Registries::thread_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"section-mark", I"of: ID", Registries::section_mark_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"section-title", I"of: ID", Registries::section_title_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"subsection-mark", I"of: ID", Registries::subsection_mark_expander, NULL)); Preprocessor::do_not_suppress_whitespace( Preprocessor::new_macro(ML, I"subsection-title", I"of: ID", Registries::subsection_title_expander, NULL)); Preprocessor::new_loop_macro(ML, I"sections", NULL, Registries::sections_expander, NULL); Preprocessor::new_loop_macro(ML, I"subsections", I"in: SECTION", Registries::subsections_expander, NULL); Preprocessor::new_loop_macro(ML, I"resources", I"in: SECTION", Registries::resources_expander, NULL); Preprocessor::new_loop_macro(ML, I"if-forum-thread", I"for: ID", Registries::if_forum_thread_expander, NULL);
- This code is used in §5.
§6. {include file:I} splices in the file R/source/include/I, unmodified.
It can contain any textual material, and even braces and backslashes pass
through exactly as written.
void Registries::include_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *leafname = parameter_values[0]; filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Registries::scan_line, NULL, PPS); } void Registries::scan_line(text_stream *line, text_file_position *tfp, void *X) { preprocessor_state *PPS = (preprocessor_state *) X; WRITE_TO(PPS->dest, "%S\n", line); }
§7. {process file:I} also splices in the file R/source/include/I, but runs
it through the preprocessor first. This means any macros it contains will be
expanded, and it has to comply with the syntax rules on use of braces and
backslash.
void Registries::process_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *leafname = parameter_values[0]; filename *prototype = Filenames::in(Pathnames::down(Pathnames::down(R->location, I"source"), I"include"), leafname); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Preprocessor::scan_line, NULL, PPS); }
§8. {include-css platform:P} splices in the Inform distribution's standard CSS
files for the named platform. It's an include, not a process.
void Registries::css_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { text_stream *platform = parameter_values[0]; filename *prototype = InstalledFiles::filename_for_platform(CSS_SET_BY_PLATFORM_IRES, platform); WRITE_TO(PPS->dest, "<style type=\"text/css\">\n"); WRITE_TO(PPS->dest, "<!--\n"); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Registries::scan_line, NULL, PPS); prototype = InstalledFiles::filename_for_platform(CSS_FOR_STANDARD_PAGES_IRES, platform); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Registries::scan_line, NULL, PPS); WRITE_TO(PPS->dest, "--></style>\n"); }
§9. {sections} ... {end-sections} is a loop construct, which loops over each
section of the registry's roster file. The loop variable {SECTIONID} holds
the ID text for the section; right now, that's just 0, 1, 2, ...
void Registries::sections_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); Preprocessor::set_loop_var_name(loop, I"SECTIONID"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int i = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { text_stream *sid = Str::new(); WRITE_TO(sid, "%d", i++); Preprocessor::add_loop_iteration(loop, sid); } }
§10. {subsections in: SID} ... {end-subsections} loops similarly over all
subsections in the section with id SID. The loop variable is {SUBSECTIONID}.
This also now counts up from 0 (but textually: all preprocessor variables are
text), but note that this SSID is unique in the registry: i.e., it doesn't go back
to 0 at the start of each section.
void Registries::subsections_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { text_stream *in = parameter_values[0]; inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); Preprocessor::set_loop_var_name(loop, I"SUBSECTIONID"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int i = 0, j = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { TEMPORARY_TEXT(sid) WRITE_TO(sid, "%d", i++); JSON_value *subsections = JSON::look_up_object(E, I"subsections"); if (subsections == NULL) internal_error("could not find roster subsections"); JSON_value *F; LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { if (Str::eq(sid, in)) { text_stream *ssid = Str::new(); WRITE_TO(ssid, "%d", j); Preprocessor::add_loop_iteration(loop, ssid); } j++; } DISCARD_TEXT(sid) } }
§11. {resources in: SSID} ... {end-resources} loops similarly over all
resources in the subsection with id SSID, or over absolutely all resources
if the id is given as ALL. The loop variable is {ID}.
void Registries::resources_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { text_stream *in = parameter_values[0]; inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); Preprocessor::set_loop_var_name(loop, I"ID"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int j = 0, k = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { JSON_value *subsections = JSON::look_up_object(E, I"subsections"); if (subsections == NULL) internal_error("could not find roster subsections"); JSON_value *F; LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { TEMPORARY_TEXT(ssid) WRITE_TO(ssid, "%d", j++); JSON_value *holdings = JSON::look_up_object(F, I"holdings"); if (holdings == NULL) internal_error("could not find roster holdings"); JSON_value *G; LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { if ((Str::eq(in, I"ALL")) || (Str::eq(ssid, in))) { text_stream *id = Str::new(); WRITE_TO(id, "%d", k); Preprocessor::add_loop_iteration(loop, id); } k++; } DISCARD_TEXT(ssid) } } }
§12. We now have a run of macros which give details of the resource ID.
First, {section of: ID} produces the SID of its section.
void Registries::section_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; TEMPORARY_TEXT(section) JSON_value *res = Registries::resource_from_textual_id(R, of, section, NULL); if (res) WRITE_TO(PPS->dest, "%S", section); DISCARD_TEXT(section) }
void Registries::subsection_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; TEMPORARY_TEXT(subsection) JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, subsection); if (res) WRITE_TO(PPS->dest, "%S", subsection); DISCARD_TEXT(subsection) }
§14. {author of: ID escape: ESC} produces the author's name, optionally escaped
with the system below.
void Registries::author_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; text_stream *escape = parameter_values[1]; JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); if (res) { JSON_value *author = JSON::look_up_object(res, I"author"); if (author == NULL) internal_error("could not find author"); Registries::write_escaped(PPS->dest, author->if_string, escape); } }
void Registries::title_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; text_stream *escape = parameter_values[1]; JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); if (res) { JSON_value *title = JSON::look_up_object(res, I"title"); if (title == NULL) internal_error("could not find title"); Registries::write_escaped(PPS->dest, title->if_string, escape); } }
void Registries::version_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; text_stream *escape = parameter_values[1]; JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); if (res) { JSON_value *version = JSON::look_up_object(res, I"version"); if (version == NULL) internal_error("could not find version"); Registries::write_escaped(PPS->dest, version->if_string, escape); } }
void Registries::summary_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; text_stream *escape = parameter_values[1]; JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); if (res) { JSON_value *summary = JSON::look_up_object(res, I"summary"); if (summary == NULL) internal_error("could not find summary"); Registries::write_escaped(PPS->dest, summary->if_string, escape); } }
§18. {thread of: ID} produces the forum thread number, if it exists, and prints
nothing if it does not.
void Registries::thread_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; JSON_value *res = Registries::resource_from_textual_id(R, of, NULL, NULL); if (res) { JSON_value *thread = JSON::look_up_object(res, I"forum-thread"); if (thread) WRITE_TO(PPS->dest, "%d", thread->if_integer); } }
§19. {if-forum-thread for: ID} ... {end-if-forum-thread} checks whether the
resource has a thread number, and if so, expands the material .... This is
crudely done as either a 0- or 1-term loop.
void Registries::if_forum_thread_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { text_stream *of_id = parameter_values[0]; inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); Preprocessor::set_loop_var_name(loop, I"ID"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int k = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { JSON_value *subsections = JSON::look_up_object(E, I"subsections"); if (subsections == NULL) internal_error("could not find roster subsections"); JSON_value *F; LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { JSON_value *holdings = JSON::look_up_object(F, I"holdings"); if (holdings == NULL) internal_error("could not find roster holdings"); JSON_value *G; LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { TEMPORARY_TEXT(id) WRITE_TO(id, "%d", k++); if (Str::eq(id, of_id)) { JSON_value *thread = JSON::look_up_object(G, I"forum-thread"); if (thread) Preprocessor::add_loop_iteration(loop, id); } DISCARD_TEXT(id) } } } }
void Registries::section_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; TEMPORARY_TEXT(mark) JSON_value *section = Registries::section_from_textual_id(R, of, mark); if (section) WRITE_TO(PPS->dest, "%S", mark); DISCARD_TEXT(mark) }
void Registries::section_title_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; JSON_value *section = Registries::section_from_textual_id(R, of, NULL); if (section) { JSON_value *title = JSON::look_up_object(section, I"title"); if (title == NULL) internal_error("could not find title"); WRITE_TO(PPS->dest, "%S", title->if_string); } }
void Registries::subsection_mark_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; TEMPORARY_TEXT(mark) JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, mark); if (subsection) WRITE_TO(PPS->dest, "%S", mark); DISCARD_TEXT(mark) }
void Registries::subsection_title_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { inbuild_registry *R = RETRIEVE_POINTER_inbuild_registry(PPS->specifics); text_stream *of = parameter_values[0]; JSON_value *subsection = Registries::subsection_from_textual_id(R, of, NULL, NULL); if (subsection) { JSON_value *title = JSON::look_up_object(subsection, I"title"); if (title == NULL) internal_error("could not find title"); WRITE_TO(PPS->dest, "%S", title->if_string); } }
§24. Escapology. quotes escapes single quotation marks by placing a backslash before them,
as is necessary in JavaScript string literals.
spaces escapes spaces as %20, as is necessary in URLs.
both does both; neither does neither.
void Registries::write_escaped(OUTPUT_STREAM, text_stream *text, text_stream *escape) { if (Str::eq(escape, I"quotes")) { LOOP_THROUGH_TEXT(pos, text) { inchar32_t c = Str::get(pos); if (c == '\'') { PUT('\\'); PUT('\''); } else { PUT(c); } } } else if (Str::eq(escape, I"spaces")) { LOOP_THROUGH_TEXT(pos, text) { inchar32_t c = Str::get(pos); if (c == ' ') { WRITE("%%20"); } else { PUT(c); } } } else if (Str::eq(escape, I"both")) { LOOP_THROUGH_TEXT(pos, text) { inchar32_t c = Str::get(pos); if (c == '\'') { PUT('\\'); PUT('\''); } else if (c == ' ') { WRITE("%%20"); } else { PUT(c); } } } else if ((Str::eq(escape, I"neither")) || (Str::len(escape) == 0)) { WRITE("%S", text); } else WRITE_TO(STDERR, "error: no such escape as '%S'\n", escape); }
§25. Looking up by textual ID. Given a textual resource id id, return the JSON object for it, or else
print an error and return NULL.
On success, the SID of its section is written to sectionid, and the SSID
of its subsection to subsectionid.
JSON_value *Registries::resource_from_textual_id(inbuild_registry *R, text_stream *id, text_stream *sectionid, text_stream *subsectionid) { if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int i = 0, j = 0, k = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { JSON_value *subsections = JSON::look_up_object(E, I"subsections"); if (subsections == NULL) internal_error("could not find roster subsections"); JSON_value *F; LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { JSON_value *holdings = JSON::look_up_object(F, I"holdings"); if (holdings == NULL) internal_error("could not find roster holdings"); JSON_value *G; LOOP_OVER_LINKED_LIST(G, JSON_value, holdings->if_list) { int match = FALSE; TEMPORARY_TEXT(this_id) WRITE_TO(this_id, "%d", k); if (Str::eq(id, this_id)) match = TRUE; DISCARD_TEXT(this_id) if (match) { WRITE_TO(sectionid, "%d", i); WRITE_TO(subsectionid, "%d", j); return G; } k++; } j++; } i++; } WRITE_TO(STDERR, "error: no such resource ID as '%S'\n", id); return NULL; }
JSON_value *Registries::section_from_textual_id(inbuild_registry *R, text_stream *sid, text_stream *mark) { if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int i = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { int match = FALSE; TEMPORARY_TEXT(this_sid) WRITE_TO(this_sid, "%d", i); if (Str::eq(sid, this_sid)) match = TRUE; DISCARD_TEXT(this_sid) if (match) { WRITE_TO(mark, "%d", i+1); return E; } i++; } WRITE_TO(STDERR, "error: no such section ID as '%S'\n", sid); return NULL; }
JSON_value *Registries::subsection_from_textual_id(inbuild_registry *R, text_stream *ssid, text_stream *mark, text_stream *submark) { if ((R == NULL) || (R->roster == NULL)) internal_error("bad registry"); JSON_value *sections = JSON::look_up_object(R->roster, I"sections"); if (sections == NULL) internal_error("could not find roster sections"); JSON_value *E; int i = 0, j = 0; LOOP_OVER_LINKED_LIST(E, JSON_value, sections->if_list) { JSON_value *subsections = JSON::look_up_object(E, I"subsections"); if (subsections == NULL) internal_error("could not find roster subsections"); int x = 1; JSON_value *F; LOOP_OVER_LINKED_LIST(F, JSON_value, subsections->if_list) { int match = FALSE; TEMPORARY_TEXT(this_ssid) WRITE_TO(this_ssid, "%d", j); if (Str::eq(ssid, this_ssid)) match = TRUE; DISCARD_TEXT(this_ssid) if (match) { WRITE_TO(mark, "%d", i+1); WRITE_TO(submark, "%d.%d", i+1, x); return F; } j++, x++; } i++; } WRITE_TO(STDERR, "error: no such subsection ID as '%S'\n", ssid); return NULL; }
§28. Simpler preprocessing. A simpler version of the above preprocessor is convenient as a way of manufacting small HTML files needed in the Inform apps: for example, to display advice text on the launcher panels. There's nothing interesting about those files except that they may need platform-specific CSS in order to display properly in Dark Mode, use congenial fonts, and so on.
We preprocess from F to T, except that we look to see if there's a platform
variant of the file F first: for example, if F is Fruits/bananas.html, and
the platform is wii, then we look for Fruits/bananas-wii.html and use that
instead. (If not, we just use F.) In practice, for example, this allows the
file in the apps which lists keyboard shortcuts to vary with platform.
void Registries::preprocess_HTML(filename *T, filename *F, text_stream *platform) { linked_list *ML = NEW_LINKED_LIST(preprocessor_macro); Preprocessor::new_macro(ML, I"include-css", I"?platform: PLATFORM", Registries::preprocess_css_expander, NULL); TEMPORARY_TEXT(variant) Filenames::write_unextended_leafname(variant, F); WRITE_TO(variant, "-%S", platform); Filenames::write_extension(variant, F); filename *variant_F = Filenames::in(Filenames::up(F), variant); if (TextFiles::exists(variant_F)) F = variant_F; DISCARD_TEXT(variant) WRITE_TO(STDOUT, "%f -> %f\n", F, T); Preprocessor::preprocess(F, T, NULL, ML, STORE_POINTER_text_stream(platform), '#', UTF8_ENC); } void Registries::preprocess_css_expander(preprocessor_macro *mm, preprocessor_state *PPS, text_stream **parameter_values, preprocessor_loop *loop, text_file_position *tfp) { text_stream *platform = parameter_values[0]; if (Str::len(platform) == 0) platform = RETRIEVE_POINTER_text_stream(PPS->specifics); filename *prototype = InstalledFiles::filename_for_platform(CSS_SET_BY_PLATFORM_IRES, platform); WRITE_TO(PPS->dest, "<style type=\"text/css\">\n"); WRITE_TO(PPS->dest, "<!--\n"); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Registries::scan_line, NULL, PPS); prototype = InstalledFiles::filename_for_platform(CSS_FOR_STANDARD_PAGES_IRES, platform); TextFiles::read(prototype, FALSE, "can't open include file", TRUE, Registries::scan_line, NULL, PPS); WRITE_TO(PPS->dest, "--></style>\n"); }