Claiming and creating copies of the kit genre: used for kits of precompiled Inter code.
- §1. Genre definition
- §4. Claiming
- §6. Searching
- §7. Copying
- §8. Build graph
- §9. Source text
- §10. Documentation
- §11. Modernisation
§1. Genre definition. The extension_bundle_genre can be summarised as follows.
void ExtensionBundleManager::start(void) { extension_bundle_genre = Genres::new(I"extensionbundle", I"extension", TRUE); Genres::place_in_class(extension_bundle_genre, 1); METHOD_ADD(extension_bundle_genre, GENRE_WRITE_WORK_MTID, ExtensionBundleManager::write_work); METHOD_ADD(extension_bundle_genre, GENRE_CLAIM_AS_COPY_MTID, ExtensionBundleManager::claim_as_copy); METHOD_ADD(extension_bundle_genre, GENRE_SEARCH_NEST_FOR_MTID, ExtensionBundleManager::search_nest_for); METHOD_ADD(extension_bundle_genre, GENRE_COPY_TO_NEST_MTID, ExtensionBundleManager::copy_to_nest); METHOD_ADD(extension_bundle_genre, GENRE_CONSTRUCT_GRAPH_MTID, ExtensionBundleManager::construct_graph); METHOD_ADD(extension_bundle_genre, GENRE_BUILDING_SOON_MTID, ExtensionBundleManager::building_soon); METHOD_ADD(extension_bundle_genre, GENRE_DOCUMENT_MTID, ExtensionBundleManager::document); METHOD_ADD(extension_bundle_genre, GENRE_READ_SOURCE_TEXT_FOR_MTID, ExtensionBundleManager::read_source_text_for); METHOD_ADD(extension_bundle_genre, GENRE_MODERNISE_MTID, ExtensionBundleManager::modernise); } void ExtensionBundleManager::write_work(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) { WRITE("%S", work->title); }
§2. Extensions live in their namesake subdirectory of a nest:
pathname *ExtensionBundleManager::path_within_nest(inbuild_nest *N) { if (N == NULL) internal_error("no nest"); return Pathnames::down(N->location, I"Extensions"); }
§3. Extension copies are annotated with a structure called an inform_extension, which stores data about extensions used by the Inform compiler.
inform_extension *ExtensionBundleManager::from_copy(inbuild_copy *C) { if ((C) && (C->edition->work->genre == extension_bundle_genre)) { return RETRIEVE_POINTER_inform_extension(C->metadata); } return NULL; } dictionary *eb_copy_cache = NULL; inbuild_copy *ExtensionBundleManager::new_copy(text_stream *name, pathname *P, inbuild_nest *N, semantic_version_number apparent_V, int force_renaming) { if (eb_copy_cache == NULL) eb_copy_cache = Dictionaries::new(16, FALSE); TEMPORARY_TEXT(key) WRITE_TO(key, "%p", P); inbuild_copy *C = NULL; if (Dictionaries::find(eb_copy_cache, key)) C = Dictionaries::read_value(eb_copy_cache, key); if (C == NULL) { inbuild_work *work = Works::new_raw(extension_bundle_genre, Str::duplicate(name), Str::duplicate(Pathnames::directory_name(Pathnames::up(P)))); inbuild_edition *edition = Editions::new(work, VersionNumbers::null()); C = Copies::new_in_path(edition, P, N); Extensions::scan(C); if ((force_renaming == FALSE) && (VersionNumbers::ne(apparent_V, C->edition->version))) force_renaming = TRUE; if ((force_renaming == FALSE) && (Str::ne(name, C->edition->work->title))) force_renaming = TRUE; if (force_renaming == TRUE) { TEMPORARY_TEXT(new_name) TEMPORARY_TEXT(new_version) WRITE_TO(new_version, "%v", &(C->edition->version)); LOOP_THROUGH_TEXT(pos, new_version) if (Str::get(pos) == '.') Str::put(pos, '_'); WRITE_TO(new_name, "%S-v%S.i7xd", C->edition->work->title, new_version); if (Extensions::rename_directory(P, new_name)) { Str::clear(key); WRITE_TO(key, "%p", P); apparent_V = C->edition->version; name = C->edition->work->title; } DISCARD_TEXT(new_name) DISCARD_TEXT(new_version) } Dictionaries::create(eb_copy_cache, key); Dictionaries::write_value(eb_copy_cache, key, C); if (VersionNumbers::is_null(apparent_V)) { if (Nests::get_tag(N) != INTERNAL_NEST_TAG) { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "an extension in directory format must have a directory name ending " "'-vN.i7xd', giving the version number: for example, " "'Advanced Algebra-v2_3_6.i7xd'"); Copies::attach_error(C, CopyErrors::new_T(EXT_BAD_DIRNAME_CE, -1, error_text)); DISCARD_TEXT(error_text) } } else if (VersionNumbers::ne(apparent_V, C->edition->version)) { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the version number '%v' is not the one implied by the directory " "name '%S', which would be '%v'", &(C->edition->version), Pathnames::directory_name(P), &apparent_V); Copies::attach_error(C, CopyErrors::new_T(EXT_BAD_DIRNAME_CE, -1, error_text)); DISCARD_TEXT(error_text) } } DISCARD_TEXT(key) return C; }
§4. Claiming. Here arg is a textual form of a filename or pathname, such as may have been supplied at the command line; ext is a substring of it, and is its extension (e.g., jpg if arg is Geraniums.jpg), or is empty if there isn't one; directory_status is true if we know for some reason that this is a directory not a file, false if we know the reverse, and otherwise not applicable.
An extension bundle can be recognised by containing a valid metadata file.
void ExtensionBundleManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C, text_stream *arg, text_stream *ext, int directory_status) { if (directory_status == FALSE) return; pathname *P = Pathnames::from_text(arg); *C = ExtensionBundleManager::claim_folder_as_copy(P, NULL); }
§5. And so we truncate to that length when turning the directory name into the copy name.
void ExtensionBundleManager::dismantle_name(text_stream *name, text_stream *to_title, text_stream *to_ext, semantic_version_number *to_V) { TEMPORARY_TEXT(ext) TEMPORARY_TEXT(title) int pos = Str::len(name) - 1, dotpos = -1; while (pos >= 0) { inchar32_t c = Str::get_at(name, pos); if (Platform::is_folder_separator(c)) break; if (c == '.') dotpos = pos; pos--; } if (dotpos >= 0) { Str::substr(ext, Str::at(name, dotpos+1), Str::end(name)); Str::substr(title, Str::start(name), Str::at(name, dotpos)); } else { WRITE_TO(title, "%S", name); } semantic_version_number V = VersionNumbers::null(); match_results mr = Regexp::create_mr(); if (Regexp::match(&mr, title, U"(%c+)-v([0-9_]+)")) { Str::clear(title); WRITE_TO(title, "%S", mr.exp[0]); LOOP_THROUGH_TEXT(pos, mr.exp[1]) if (Str::get(pos) == '_') Str::put(pos, '.'); V = VersionNumbers::from_text(mr.exp[1]); } Regexp::dispose_of(&mr); Str::copy(to_title, title); Str::copy(to_ext, ext); if (to_V) *to_V = V; DISCARD_TEXT(ext) DISCARD_TEXT(title) } inbuild_copy *ExtensionBundleManager::claim_folder_as_copy(pathname *P, inbuild_nest *N) { inbuild_copy *C = NULL; if (Directories::exists(P)) { filename *canary = Filenames::in(P, I"extension_metadata.json"); TEMPORARY_TEXT(ext) TEMPORARY_TEXT(title) semantic_version_number V = VersionNumbers::null(); ExtensionBundleManager::dismantle_name(Pathnames::directory_name(P), title, ext, &V); if ((Str::eq_insensitive(ext, I"i7xd")) || (TextFiles::exists(canary))) { if (Extensions::alternative_source_file(P) == NULL) { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the extension directory '%S' does not contain any source text " "(which should be in a '.i7x' file inside a 'Source' subdirectory)", Pathnames::directory_name(P)); Copies::attach_error(C, CopyErrors::new_T(EXT_BAD_DIRNAME_CE, -1, error_text)); DISCARD_TEXT(error_text) } else { int force_renaming = NOT_APPLICABLE; int save_repair_mode = repair_mode; if (Nests::get_tag(N) == MATERIALS_NEST_TAG) repair_mode = TRUE; if (repair_mode) force_renaming = FALSE; if (Str::eq_insensitive(ext, I"i7xd") == FALSE) { if (Nests::get_tag(N) == MATERIALS_NEST_TAG) { force_renaming = TRUE; } else { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the extension directory '%S' needs to have the extension '.i7xd' added to its name", Pathnames::directory_name(P)); Copies::attach_error(C, CopyErrors::new_T(EXT_BAD_DIRNAME_CE, -1, error_text)); DISCARD_TEXT(error_text) } } C = ExtensionBundleManager::new_copy(title, P, N, V, force_renaming); Police extraneous contents5.1; repair_mode = save_repair_mode; } } DISCARD_TEXT(ext) DISCARD_TEXT(title) } return C; }
§5.1. Police extraneous contents5.1 =
linked_list *L = Directories::listing(P); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { TEMPORARY_TEXT(subdir) WRITE_TO(subdir, "%S", entry); Str::delete_last_character(subdir); if (Str::eq(subdir, I"Source")) { Police Source contents5.1.1; } else if (Str::eq(subdir, I"Materials")) { Police Materials contents5.1.2; } else if (Str::eq(subdir, I"Documentation")) { Police Documentation contents5.1.3; } else if (Str::eq(subdir, I"RTPs")) { Police RTPs contents5.1.4; } else { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the extension directory '%S' contains a subdirectory called '%S', " "which I don't recognise", Pathnames::directory_name(P), subdir); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } DISCARD_TEXT(subdir) } else { if (Str::eq(entry, I"extension_metadata.json")) continue; TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the extension directory '%S' contains a file called '%S', " "which I don't recognise", Pathnames::directory_name(P), entry); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } }
- This code is used in §5.
§5.1.1. Police Source contents5.1.1 =
filename *canonical = Extensions::main_source_file(C); pathname *Q = Pathnames::down(P, subdir); linked_list *L = Directories::listing(Q); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { TEMPORARY_TEXT(subdir) WRITE_TO(subdir, "%S", entry); Str::delete_last_character(subdir); TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Source' subdirectory of the extension directory '%S' contains a " "further subdirectory called '%S', but should not have further subdirectories", Pathnames::directory_name(P), subdir); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) DISCARD_TEXT(subdir) } else { if (Str::eq(entry, Filenames::get_leafname(canonical))) continue; TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Source' subdirectory of the extension directory '%S' contains a " "file called '%S', but should only contain the source text file '%S'", Pathnames::directory_name(P), entry, Filenames::get_leafname(canonical)); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } }
- This code is used in §5.1.
§5.1.2. Police Materials contents5.1.2 =
pathname *Q = Pathnames::down(P, subdir); linked_list *L = Directories::listing(Q); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { TEMPORARY_TEXT(subdir) WRITE_TO(subdir, "%S", entry); Str::delete_last_character(subdir); if (Str::eq(subdir, I"Data")) { ; } else if (Str::eq(subdir, I"Figures")) { ; } else if (Str::eq(subdir, I"Inter")) { ; } else if (Str::eq(subdir, I"Sounds")) { ; } else if (Str::eq(subdir, I"Templates")) { ; } else if (Str::eq(subdir, I"Languages")) { ; } else { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Materials' subdirectory of the extension directory '%S' contains a " "further subdirectory called '%S', but this is not one of the possibilities " "allowed", Pathnames::directory_name(P), subdir); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } DISCARD_TEXT(subdir) } else { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Materials' subdirectory of the extension directory '%S' contains a " "file called '%S', but should not contain any files, only further subdirectories", Pathnames::directory_name(P), entry); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } }
- This code is used in §5.1.
§5.1.3. Police Documentation contents5.1.3 =
int doc_found = FALSE; pathname *Q = Pathnames::down(P, subdir); linked_list *L = Directories::listing(Q); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { TEMPORARY_TEXT(subdir) WRITE_TO(subdir, "%S", entry); Str::delete_last_character(subdir); if (Str::eq(subdir, I"Examples")) { ; } else if (Str::eq(subdir, I"Tests")) { ; } else if (Str::eq(subdir, I"Images")) { ; } else { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Documentation' subdirectory of the extension directory '%S' contains a " "further subdirectory called '%S', but this is not one of the possibilities " "allowed", Pathnames::directory_name(P), subdir); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } DISCARD_TEXT(subdir) } else { if (Str::ends_with(entry, I".md")) { doc_found = TRUE; continue; } if (Str::eq(entry, I"contents.txt")) { doc_found = TRUE; continue; } if (Str::eq(entry, I"sitemap.txt")) { doc_found = TRUE; continue; } TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Documentation' subdirectory of the extension directory '%S' contains a " "file called '%S', but I don't know what this would mean", Pathnames::directory_name(P), entry); Copies::attach_error(C, CopyErrors::new_T(EXT_RANEOUS_CE, -1, error_text)); DISCARD_TEXT(error_text) } } if (doc_found == FALSE) { TEMPORARY_TEXT(error_text) WRITE_TO(error_text, "the 'Documentation' subdirectory of the extension directory '%S' is optional, " "but if it does exist then it needs to contain a file called 'Documentation.md'", Pathnames::directory_name(P)); Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, error_text)); DISCARD_TEXT(error_text) }
- This code is used in §5.1.
§5.1.4. Police RTPs contents5.1.4 =
;
- This code is used in §5.1.
§6. Searching. Here we look through a nest to find all extension bundles:
void ExtensionBundleManager::search_nest_for(inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results) { if ((req->work->genre) && ((req->work->genre != extension_bundle_genre) && (req->work->genre != extension_genre))) return; pathname *P = ExtensionManager::path_within_nest(N); if (Str::len(req->work->author_name) > 0) { linked_list *L = Directories::listing(P); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { Str::delete_last_character(entry); if ((Str::ne(entry, I"Reserved")) && (Str::eq_insensitive(entry, req->work->author_name))) { pathname *Q = Pathnames::down(P, entry); ExtensionBundleManager::search_nest_for_r(Q, N, req, search_results, FALSE); } } } } else { ExtensionBundleManager::search_nest_for_r(P, N, req, search_results, TRUE); } } void ExtensionBundleManager::search_nest_for_r(pathname *P, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results, int recurse) { linked_list *L = Directories::listing(P); text_stream *entry; LOOP_OVER_LINKED_LIST(entry, text_stream, L) { if (Platform::is_folder_separator(Str::get_last_char(entry))) { Str::delete_last_character(entry); pathname *Q = Pathnames::down(P, entry); inbuild_copy *C = ExtensionBundleManager::claim_folder_as_copy(Q, N); if ((C) && (Requirements::meets(C->edition, req))) { Nests::add_search_result(search_results, N, C, req); } else if ((recurse) && (Str::ne(entry, I"Reserved")) && (Str::ne(entry, I"Source"))) { ExtensionBundleManager::search_nest_for_r(Q, N, req, search_results, TRUE); } } } }
§7. Copying. Now the task is to copy an extension bundle into place in a nest. Since it is a directory, we need to rsync it.
pathname *ExtensionBundleManager::pathname_in_nest(inbuild_nest *N, inbuild_edition *E) { pathname *EX = ExtensionManager::path_within_nest(N); TEMPORARY_TEXT(leaf) Editions::write_canonical_leaf(leaf, E); pathname *P = Pathnames::down(Pathnames::down(EX, E->work->author_name), leaf); DISCARD_TEXT(leaf) return P; } int ExtensionBundleManager::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing, build_methodology *meth) { pathname *dest_eb = ExtensionBundleManager::pathname_in_nest(N, C->edition); filename *dest_eb_metadata = Filenames::in(dest_eb, I"extension_metadata.json"); if (TextFiles::exists(dest_eb_metadata)) { if (syncing == FALSE) { Copies::overwrite_error(C, N); return 1; } } else { if (meth->methodology == DRY_RUN_METHODOLOGY) { TEMPORARY_TEXT(command) WRITE_TO(command, "mkdir -p "); Shell::quote_path(command, dest_eb); WRITE_TO(STDOUT, "%S\n", command); DISCARD_TEXT(command) } else { Pathnames::create_in_file_system(N->location); Pathnames::create_in_file_system(ExtensionBundleManager::path_within_nest(N)); Pathnames::create_in_file_system(Pathnames::up(dest_eb)); Pathnames::create_in_file_system(dest_eb); } } int rv = 0; if (meth->methodology == DRY_RUN_METHODOLOGY) { TEMPORARY_TEXT(command) WRITE_TO(command, "rsync -a --delete "); Shell::quote_path(command, C->location_if_path); Shell::quote_path(command, dest_eb); WRITE_TO(STDOUT, "%S\n", command); DISCARD_TEXT(command) } else { rv = Pathnames::rsync(C->location_if_path, dest_eb); } return rv; }
void ExtensionBundleManager::building_soon(inbuild_genre *gen, inbuild_copy *C, build_vertex **V) { *V = C->vertex; } void ExtensionBundleManager::construct_graph(inbuild_genre *G, inbuild_copy *C) { Extensions::construct_graph(ExtensionBundleManager::from_copy(C)); }
void ExtensionBundleManager::read_source_text_for(inbuild_genre *G, inbuild_copy *C) { Extensions::read_source_text_for(ExtensionBundleManager::from_copy(C)); }
void ExtensionBundleManager::document(inbuild_genre *gen, inbuild_copy *C, pathname *dest, filename *sitemap) { Extensions::document(Extensions::from_copy(C), dest, sitemap); }
int ExtensionBundleManager::modernise(inbuild_genre *gen, inbuild_copy *C, text_stream *OUT) { return Extensions::modernise(Extensions::from_copy(C), OUT); }