To install or uninstall an extension into an Inform project, producing an HTML page as a report on what happened.
§1. Making the report page. Both the installer and uninstaller make use of:
filename *inbuild_report_HTML = NULL; void ExtensionInstaller::set_filename(filename *F) { inbuild_report_HTML = F; } text_stream inbuild_report_file_struct; The actual report file text_stream *inbuild_report_file = NULL; As a text_stream * text_stream *ExtensionInstaller::begin(text_stream *title, text_stream *subtitle) { if (inbuild_report_HTML == NULL) return NULL; inbuild_report_file = &inbuild_report_file_struct; if (STREAM_OPEN_TO_FILE(inbuild_report_file, inbuild_report_HTML, UTF8_ENC) == FALSE) Errors::fatal("can't open report file"); text_stream *OUT = inbuild_report_file; InformPages::header(OUT, title, JAVASCRIPT_FOR_STANDARD_PAGES_IRES, NULL); ExtensionWebsite::add_home_breadcrumb(NULL); ExtensionWebsite::add_breadcrumb(title, NULL); ExtensionWebsite::titling_and_navigation(OUT, subtitle); return OUT; } void ExtensionInstaller::end(void) { if (inbuild_report_file) { text_stream *OUT = inbuild_report_file; HTML_TAG("hr"); InformPages::footer(OUT); } inbuild_report_file = NULL; }
§2. The installer. This works in two stages. First it is called with confirmed false, and it produces an HTML report on the feasibility of making the installation, with a clickable Confirm button. Then, assuming the user does click that button, the Installer is called again, with confirmed true. It takes action and also produces a second report.
void ExtensionInstaller::install(inbuild_copy *C, int confirmed, pathname *to_tool, int meth) { inform_project *project = Supervisor::project_set_at_command_line(); if (project == NULL) Errors::fatal("-project not set at command line"); TEMPORARY_TEXT(pname) WRITE_TO(pname, "'%S'", project->as_copy->edition->work->title); text_stream *OUT = NULL; if ((C->edition->work->genre == extension_genre) || (C->edition->work->genre == extension_bundle_genre)) { int N = LinkedLists::len(C->errors_reading_source_text); if (N > 0) Begin report on a damaged extension2.3 else Begin report on a valid extension2.2; if (OUT) { Copies::get_source_text(project->as_copy, I"graphing for installer"); build_vertex *V = Copies::construct_project_graph(project->as_copy); if (confirmed) Make confirmed report2.5 else Make unconfirmed report2.4; } } else { Report on something else2.1; } if (OUT) { ExtensionInstaller::end(); } DISCARD_TEXT(pname) }
§2.1. Report on something else2.1 =
OUT = ExtensionInstaller::begin(I"Not an extension...", Genres::name(C->edition->work->genre)); HTML_OPEN("p"); WRITE("Despite its file/directory name, this doesn't seem to be an extension, "); WRITE("and it can't be installed or uninstalled."); HTML_CLOSE("p");
§2.2. Begin report on a valid extension2.2 =
TEMPORARY_TEXT(desc) TEMPORARY_TEXT(version) Works::write(desc, C->edition->work); semantic_version_number V = C->edition->version; if (VersionNumbers::is_null(V)) { WRITE_TO(version, "An extension"); } else { WRITE_TO(version, "Version %v of an extension", &V); } OUT = ExtensionInstaller::begin(desc, version); DISCARD_TEXT(desc) DISCARD_TEXT(version)
- This code is used in §2.
§2.3. Begin report on a damaged extension2.3 =
TEMPORARY_TEXT(desc) WRITE_TO(desc, "This may be: "); Editions::inspect(desc, C->edition); OUT = ExtensionInstaller::begin(I"Warning: Damaged extension", desc);
- This code is used in §2.
§2.4. Make unconfirmed report2.4 =
if (N > 0) Report on damage to extension2.4.1 else Report that extension seems valid2.4.2; HTML_TAG("hr"); Explain about extensions2.4.3; linked_list *L = NEW_LINKED_LIST(inbuild_search_result); int same = 0, earlier = 0, later = 0; Search the extensions currently installed in the project2.4.4; Count how many versions of the same extension are already installed2.4.5; HTML_TAG("hr"); Come to the point2.4.6; Finish up with a big red or green button2.4.7;
- This code is used in §2.
§2.4.1. Report on damage to extension2.4.1 =
HTML_OPEN("p"); WRITE("This extension is broken, and needs repair before it can be used. "); WRITE("Specifically:"); HTML_CLOSE("p"); Copies::list_attached_errors_to_HTML(OUT, C); text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C)); if (Str::len(rubric) > 0) { WRITE("The extension says this about itself:"); HTML_CLOSE("p"); HTML_OPEN("blockquote"); WRITE("%S", rubric); HTML_CLOSE("blockquote"); }
- This code is used in §2.4.
§2.4.2. Report that extension seems valid2.4.2 =
HTML_OPEN("p"); WRITE("This looks like a valid extension"); text_stream *rubric = Extensions::get_rubric(Extensions::from_copy(C)); if (Str::len(rubric) > 0) { WRITE(", and says this about itself:"); HTML_CLOSE("p"); HTML_OPEN("blockquote"); WRITE("%S", rubric); HTML_CLOSE("blockquote"); } else { WRITE(", but does not say what it is for."); HTML_CLOSE("p"); } Make documentation2.4.2.1;
- This code is used in §2.4.
§2.4.3. Explain about extensions2.4.3 =
HTML_OPEN("p"); WRITE("Extensions are additional Inform features, often contributed by Inform " "authors from around the world. Authors download them as needed. Each " "project wanting to use an extension must install it into the 'Extensions' " "subfolder of its '.materials' folder. Authors are free to do that by hand, but " "this installer is more convenient. For more on extensions, see: "); DocReferences::link(OUT, I"EXTENSIONS"); HTML_CLOSE("p"); HTML_OPEN("p"); WRITE("The '.materials' folder for %S is here: ", pname); pathname *area = Projects::materials_path(project); PasteButtons::open_file(OUT, area, NULL, "border=\"0\" src=\"inform:/doc_images/folder.png\""); HTML_CLOSE("p");
- This code is used in §2.4.
§2.5.1. List the extensions currently Included by the project2.5.1 =
int rc = 0, bic = 0, ic = 0; ExtensionInstaller::show_extensions(OUT, V, Graphs::get_unique_graph_scan_count(), FALSE, &bic, FALSE, &ic, FALSE, &rc); if (ic > 0) { HTML_OPEN("p"); WRITE("The project %S uses the following extensions (on the ", pname); WRITE("basis of what it Includes, and what they in turn Include), which it has installed:"); HTML_CLOSE("p"); HTML_OPEN("ul"); ExtensionInstaller::show_extensions(OUT, V, Graphs::get_unique_graph_scan_count(), FALSE, &bic, TRUE, &ic, FALSE, &rc); HTML_CLOSE("ul"); if (bic > 0) { HTML_OPEN("p"); WRITE("not counting extensions built into Inform which do not need to be installed ("); bic = 0; ExtensionInstaller::show_extensions(OUT, V, Graphs::get_unique_graph_scan_count(), TRUE, &bic, FALSE, &ic, FALSE, &rc); WRITE(")."); HTML_OPEN("p"); } } else if (bic > 0) { HTML_OPEN("p"); WRITE("Installing extensions is not the same thing as actually using them. " "The project %S uses only extensions ", pname); WRITE("built into Inform which do not need to be installed ("); bic = 0; ExtensionInstaller::show_extensions(OUT, V, Graphs::get_unique_graph_scan_count(), TRUE, &bic, FALSE, &ic, FALSE, &rc); WRITE(") and are included automatically."); HTML_CLOSE("p"); HTML_OPEN("p"); WRITE("Except for those ones, " "extensions take effect only if the source contains a sentence like " "'Include EXTENSION TITLE by EXTENSION AUTHOR.' At present, the source " "doesn't contain any sentences like that."); HTML_CLOSE("p"); } if (rc > 0) { HTML_OPEN("p"); WRITE("The project asks to Include the following, not yet installed:"); HTML_CLOSE("p"); HTML_OPEN("ul"); ExtensionInstaller::show_extensions(OUT, V, Graphs::get_unique_graph_scan_count(), FALSE, &bic, FALSE, &ic, TRUE, &rc); HTML_CLOSE("ul"); }
- This code is used in §2.5.
§2.4.4. Search the extensions currently installed in the project2.4.4 =
inbuild_requirement *req = Requirements::anything_of_genre(extension_bundle_genre); linked_list *search_list = NEW_LINKED_LIST(inbuild_nest); ADD_TO_LINKED_LIST(Projects::materials_nest(project), inbuild_nest, search_list); Nests::search_for(req, search_list, L);
§2.5.2. List the extensions currently installed in the project2.5.2 =
inbuild_search_result *search_result; int unused = 0, broken = 0; LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) { if (LinkedLists::len(search_result->copy->errors_reading_source_text) > 0) broken++; else if (ExtensionInstaller::seek_extension_in_graph(search_result->copy, V) == FALSE) unused++; } if (unused + broken > 0) { if (unused > 0) { HTML_OPEN("p"); WRITE("The following are currently installed for %S, but not (yet) " "Included and so not used. (You can click the 'paste' buttons to " "paste a suitable Include sentence into the source text.)", pname); HTML_CLOSE("p"); HTML_OPEN("ul"); LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) { if (LinkedLists::len(search_result->copy->errors_reading_source_text) == 0) { if (ExtensionInstaller::seek_extension_in_graph(search_result->copy, V) == FALSE) { HTML_OPEN("li"); Copies::write_copy(OUT, search_result->copy); WRITE(" "); TEMPORARY_TEXT(inclusion_text) WRITE_TO(inclusion_text, "Include %X.\n\n\n", search_result->copy->edition->work); PasteButtons::paste_text_new_style(OUT, inclusion_text); DISCARD_TEXT(inclusion_text) WRITE(" <i>'Include'</i>"); HTML_CLOSE("li"); } } } HTML_CLOSE("ul"); } }
- This code is used in §2.5.
§2.4.5. Count how many versions of the same extension are already installed2.4.5 =
inbuild_search_result *search_result; LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) if (Works::cmp(C->edition->work, search_result->copy->edition->work) == 0) { int c = VersionNumbers::cmp(C->edition->version, search_result->copy->edition->version); if (c == 0) same++; else if (c > 0) earlier++; else if (c < 0) later++; }
- This code is used in §2.4.
§2.4.2.1. Make documentation2.4.2.1 =
ExtensionWebsite::document_extension(Extensions::from_copy(C), project); HTML_OPEN("p"); WRITE("Documentation about %S ", C->edition->work->title); TEMPORARY_TEXT(link) TEMPORARY_TEXT(URL) WRITE_TO(URL, "%f", ExtensionWebsite::page_filename(project, C->edition, 0)); WRITE_TO(link, "href='"); Works::escape_apostrophes(link, URL); WRITE_TO(link, "' style=\"text-decoration: none\""); HTML_OPEN_WITH("a", "%S", link); DISCARD_TEXT(link) WRITE("can be read here."); HTML_CLOSE("a"); HTML_CLOSE("p");
- This code is used in §2.4.2.
§2.4.6. Come to the point2.4.6 =
HTML_OPEN("p"); WRITE("So, then, click the button below to install %S to the Materials folder of %S. ", C->edition->work->title, pname); WRITE("If you prefer not to, simply do something else: nothing needs to be cancelled."); HTML_CLOSE("p");
- This code is used in §2.4.
§2.4.7. Finish up with a big red or green button2.4.7 =
if (same > 0) { HTML_OPEN("p"); WRITE("<b>Note</b>. The same version of this same extension seems to be installed already. "); WRITE("You can go ahead and install, but if you do the old copy will be removed."); HTML_CLOSE("p"); HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"dangerousbutton\""); WRITE("Replace %S in %S with this new copy", C->edition->work->title, pname); HTML_CLOSE("button"); HTML_CLOSE("a"); } else if (earlier > 0) { HTML_OPEN("p"); WRITE("<b>Note</b>. An earlier version of this same extension seems to be installed already. "); WRITE("You can go ahead and install, and this new one will take precedence over the old "); WRITE("one, but it won't be thrown away. (You can remove it by hand if you want it gone.)"); HTML_CLOSE("p"); HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"dangerousbutton\""); WRITE("Install this later copy of %S to %S", C->edition->work->title, pname); HTML_CLOSE("button"); HTML_CLOSE("a"); } else if (later > 0) { HTML_OPEN("p"); WRITE("<b>Note</b>. A later version of this same extension seems to be installed already. "); WRITE("You can go ahead and install, but this new one is unlikely to change anything "); WRITE("because Inform will normally prefer to use the later version, which is already "); WRITE("there. (You can remove it by hand if you want it gone.)"); HTML_CLOSE("p"); HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"dangerousbutton\""); WRITE("Install this earlier copy of %S to %S", C->edition->work->title, pname); HTML_CLOSE("button"); HTML_CLOSE("a"); } else if (N > 0) { HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"dangerousbutton\""); WRITE("Install this anyway"); HTML_CLOSE("button"); HTML_CLOSE("a"); } else { HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"safebutton\""); WRITE("Install %S to %S", C->edition->work->title, pname); HTML_CLOSE("button"); HTML_CLOSE("a"); }
- This code is used in §2.4.
§2.5. Make confirmed report2.5 =
build_methodology *BM = BuildMethodology::new(Pathnames::up(to_tool), TRUE, meth); int no_trashed = 0; TEMPORARY_TEXT(trash_report) Trash any identically-versioned copies currently present2.5.3; Copy the new one into place2.5.4; if (Str::len(trash_report) > 0) { HTML_OPEN("p"); WRITE("Since an extension with the same title, author name and version number " "was already installed in this project, some tidying-up was needed:"); HTML_CLOSE("p"); HTML_OPEN("ul"); WRITE("%S", trash_report); HTML_CLOSE("ul"); } HTML_TAG("hr"); DISCARD_TEXT(trash_report) ExtensionWebsite::update(project); linked_list *L = NEW_LINKED_LIST(inbuild_search_result); List the extensions currently Included by the project2.5.1; Search the extensions currently installed in the project2.4.4; List the extensions currently installed in the project2.5.2; inbuild_search_result *search_result; int broken = 0; LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) if (LinkedLists::len(search_result->copy->errors_reading_source_text) > 0) broken++; if (broken > 0) { HTML_TAG("hr"); HTML_OPEN("p"); WRITE("Although installed, the following have errors and will not work. " "They may need to be repaired, or may simply not be extensions at all:"); HTML_CLOSE("p"); HTML_OPEN("ul"); LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) { if (LinkedLists::len(search_result->copy->errors_reading_source_text) > 0) { HTML_OPEN("li"); Copies::write_copy(OUT, search_result->copy); if (search_result->copy->location_if_file) { HTML_TAG("br"); WRITE("at "); Filenames::to_text_relative(OUT, search_result->copy->location_if_file, Pathnames::up(Projects::materials_path(project))); } else if (search_result->copy->location_if_path) { HTML_TAG("br"); WRITE("at "); Pathnames::to_text_relative(OUT, Pathnames::up(Projects::materials_path(project)), search_result->copy->location_if_path); } Copies::list_attached_errors_to_HTML(OUT, search_result->copy); HTML_CLOSE("li"); } } HTML_CLOSE("ul"); }
- This code is used in §2.
§2.5.3. Trash any identically-versioned copies currently present2.5.3 =
linked_list *L = NEW_LINKED_LIST(inbuild_search_result); Search the extensions currently installed in the project2.4.4; inbuild_search_result *search_result; LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) if ((Works::cmp(C->edition->work, search_result->copy->edition->work) == 0) && (VersionNumbers::cmp(C->edition->version, search_result->copy->edition->version) == 0)) no_trashed += ExtensionInstaller::trash(trash_report, project, search_result->copy, BM);
- This code is used in §2.5.
§2.5.4. Copy the new one into place2.5.4 =
if (Copies::copy_to(C, Projects::materials_nest(project), TRUE, BM) == 0) { HTML_OPEN("p"); WRITE("This extension has now been installed in the materials folder for %S, as:", pname); HTML_CLOSE("p"); HTML_OPEN("ul"); HTML_OPEN("li"); HTML_OPEN("p"); if (C->edition->work->genre == extension_bundle_genre) { pathname *P = ExtensionBundleManager::pathname_in_nest(Projects::materials_nest(project), C->edition); WRITE("the folder "); HTML_OPEN("b"); Pathnames::to_text_relative(OUT, Pathnames::up(Projects::materials_path(project)), P); HTML_CLOSE("b"); } else { filename *F = ExtensionManager::filename_in_nest(Projects::materials_nest(project), C->edition); WRITE("the file "); HTML_OPEN("b"); Filenames::to_text_relative(OUT, F, Pathnames::up(Projects::materials_path(project))); HTML_CLOSE("b"); } HTML_CLOSE("li"); HTML_CLOSE("ul"); } else { HTML_OPEN("p"); WRITE("A file-system error occurred when this was installed in the materials folder for %S, as:", pname); HTML_CLOSE("p"); HTML_OPEN("ul"); HTML_OPEN("li"); HTML_OPEN("p"); if (C->edition->work->genre == extension_bundle_genre) { pathname *P = ExtensionBundleManager::pathname_in_nest(Projects::materials_nest(project), C->edition); WRITE("the folder "); HTML_OPEN("b"); Pathnames::to_text_relative(OUT, Pathnames::up(Projects::materials_path(project)), P); HTML_CLOSE("b"); } else { filename *F = ExtensionManager::filename_in_nest(Projects::materials_nest(project), C->edition); WRITE("the file "); HTML_OPEN("b"); Filenames::to_text_relative(OUT, F, Pathnames::up(Projects::materials_path(project))); HTML_CLOSE("b"); } HTML_CLOSE("li"); HTML_CLOSE("ul"); }
- This code is used in §2.5.
§3. The uninstaller. This works in two stages, exactly like the installer, but it's much simpler.
void ExtensionInstaller::uninstall(inbuild_copy *C, int confirmed, pathname *to_tool, int meth) { inform_project *project = Supervisor::project_set_at_command_line(); if (project == NULL) Errors::fatal("-project not set at command line"); TEMPORARY_TEXT(pname) WRITE_TO(pname, "'%S'", project->as_copy->edition->work->title); text_stream *OUT = NULL; if ((C->edition->work->genre == extension_genre) || (C->edition->work->genre == extension_bundle_genre)) { Begin uninstaller report3.1; if (OUT) { if (confirmed) Make confirmed uninstaller report3.3 else Make unconfirmed uninstaller report3.2; } } else { Report on something else2.1; } if (OUT) { ExtensionInstaller::end(); } DISCARD_TEXT(pname) }
§3.1. Begin uninstaller report3.1 =
TEMPORARY_TEXT(desc) TEMPORARY_TEXT(version) Works::write(desc, C->edition->work); semantic_version_number V = C->edition->version; if (VersionNumbers::is_null(V)) { WRITE_TO(version, "An extension"); } else { WRITE_TO(version, "Version %v of an extension", &V); } OUT = ExtensionInstaller::begin(desc, version); DISCARD_TEXT(desc) DISCARD_TEXT(version)
- This code is used in §3.
§3.2. Make unconfirmed uninstaller report3.2 =
HTML_OPEN("p"); WRITE("Click the red button to confirm that you would like to uninstall this " "extension from the materials folder for %S: ", pname); if (C->edition->work->genre == extension_bundle_genre) { pathname *P = ExtensionBundleManager::pathname_in_nest(Projects::materials_nest(project), C->edition); WRITE("the folder "); HTML_OPEN("b"); Pathnames::to_text_relative(OUT, Pathnames::up(Projects::materials_path(project)), P); HTML_CLOSE("b"); } else { filename *F = ExtensionManager::filename_in_nest(Projects::materials_nest(project), C->edition); WRITE("the file "); HTML_OPEN("b"); Filenames::to_text_relative(OUT, F, Pathnames::up(Projects::materials_path(project))); HTML_CLOSE("b"); } WRITE(" which is in nest %p", Nests::get_location(C->nest_of_origin)); HTML_CLOSE("p"); HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"dangerousbutton\""); WRITE("Uninstall %S", C->edition->work->title); HTML_CLOSE("button"); HTML_CLOSE("a");
- This code is used in §3.
§3.3. Make confirmed uninstaller report3.3 =
build_methodology *BM = BuildMethodology::new(Pathnames::up(to_tool), TRUE, meth); TEMPORARY_TEXT(trash_report) ExtensionInstaller::trash(trash_report, project, C, BM); HTML_OPEN("p"); WRITE("Uninstalling this extension from the materials folder for %S:", pname); HTML_CLOSE("p"); HTML_OPEN("ul"); WRITE("%S", trash_report); HTML_CLOSE("ul"); HTML_TAG("hr"); DISCARD_TEXT(trash_report) ExtensionWebsite::update(project);
- This code is used in §3.
int ExtensionInstaller::trash(OUTPUT_STREAM, inform_project *proj, inbuild_copy *C, build_methodology *BM) { int succeeded = FALSE; HTML_OPEN("li"); pathname *super_trash_folder = Pathnames::down( Pathnames::down( Pathnames::down( Projects::materials_path(proj), I"Extensions"), I"Reserved"), I"Trash"); TEMPORARY_TEXT(dateleaf) WRITE_TO(dateleaf, "Trashed on %04d-%02d-%02d at %02d%02d", the_present->tm_year+1900, the_present->tm_mon, the_present->tm_mday, the_present->tm_hour, the_present->tm_min); DISCARD_TEXT(dateleaf) pathname *trash_folder = Pathnames::down(super_trash_folder, dateleaf); TEMPORARY_TEXT(reported) Pathnames::to_text_relative(reported, Pathnames::up(Projects::materials_path(proj)), trash_folder); if (C->location_if_file) { TEMPORARY_TEXT(leaf) int n = 1; filename *TF = NULL; do { Str::clear(leaf); Filenames::write_unextended_leafname(leaf, C->location_if_file); if (n > 1) WRITE_TO(leaf, " %d", n); n++; WRITE_TO(leaf, ".i7x"); TF = Filenames::in(trash_folder, leaf); } while (TextFiles::exists(TF)); DISCARD_TEXT(leaf) if (BM->methodology == DRY_RUN_METHODOLOGY) { WRITE("This is only a dry run, but I now want to create the directory " "%p as a trash folder and move the file %f to become %f. ", trash_folder, C->location_if_file, TF); } else { if ((Pathnames::create_in_file_system(super_trash_folder) == FALSE) || (Pathnames::create_in_file_system(trash_folder) == FALSE)) { WRITE("I tried to move the copy installed as '%S' to the trash (%S), " "but was unable to create this trash directory, perhaps because " "of some file-system problem? ", Filenames::get_leafname(C->location_if_file), reported); } else if (Filenames::move_file(C->location_if_file, TF)) { WRITE("I have moved the copy previously installed as '%S' to the " "project's trash. (If you need it, you can find it in %S.) ", Filenames::get_leafname(C->location_if_file), reported); C->location_if_file = TF; succeeded = TRUE; } else { WRITE("I tried to move the copy installed as '%S' to the trash (%S), " "but was unable to, perhaps because of some file-system problem? ", Filenames::get_leafname(C->location_if_file), reported); } } } else { TEMPORARY_TEXT(leaf) int n = 1; pathname *TD = NULL; do { Str::clear(leaf); WRITE_TO(leaf, "%S", Pathnames::directory_name(C->location_if_path)); if (Str::get_at(leaf, Str::len(leaf)-5) == '.') Str::truncate(leaf, Str::len(leaf)-5); if (n > 1) WRITE_TO(leaf, " %d", n); n++; WRITE_TO(leaf, ".i7xd"); TD = Pathnames::down(trash_folder, leaf); } while (Directories::exists(TD)); DISCARD_TEXT(leaf) if (BM->methodology == DRY_RUN_METHODOLOGY) { WRITE("This is only a dry run, but I now want to create the directory " "%p as a trash folder and move the directory %p to become %p. ", trash_folder, C->location_if_path, TD); } else { if ((Pathnames::create_in_file_system(super_trash_folder) == FALSE) || (Pathnames::create_in_file_system(trash_folder) == FALSE)) { WRITE("I tried to move the copy installed as '%S' to the trash (%S), " "but was unable to create this trash directory, perhaps because " "of some file-system problem? ", Pathnames::directory_name(C->location_if_path), reported); } else if (Pathnames::move_directory(C->location_if_path, TD)) { WRITE("I have moved the copy previously installed as '%S' to the " "project's trash. (If you need it, you can find it in %S.) ", Pathnames::directory_name(C->location_if_path), reported); C->location_if_path = TD; succeeded = TRUE; } else { WRITE("I tried to move the copy installed as '%S' to the trash (%S), " "but was unable to, perhaps because of some file-system problem? ", Pathnames::directory_name(C->location_if_path), reported); } } } HTML_CLOSE("li"); return succeeded; }
void ExtensionInstaller::show_extensions(OUTPUT_STREAM, build_vertex *V, int scan_count, int built_in, int *built_in_count, int installed, int *installed_count, int required, int *requirements_count) { if (V->type == COPY_VERTEX) { inbuild_copy *C = V->as_copy; if ((C->edition->work->genre == extension_genre) || (C->edition->work->genre == extension_bundle_genre)) { if (C->last_scanned != scan_count) { if (required == FALSE) { C->last_scanned = scan_count; if ((C->nest_of_origin) && (Nests::get_tag(C->nest_of_origin) == INTERNAL_NEST_TAG)) { (*built_in_count)++; if (built_in) { if ((*built_in_count) > 1) WRITE(", "); WRITE("%S v%v", C->edition->work->title, &(C->edition->version)); } } else { (*installed_count)++; if (installed) { HTML_OPEN("li"); Copies::write_copy(OUT, C); HTML_CLOSE("li"); } } } } } } if (V->type == REQUIREMENT_VERTEX) { if ((V->as_requirement->work->genre == extension_genre) || (V->as_requirement->work->genre == extension_bundle_genre)) { (*requirements_count)++; if (required) { HTML_OPEN("li"); Works::write(OUT, V->as_requirement->work); if (VersionNumberRanges::is_any_range(V->as_requirement->version_range) == FALSE) { WRITE(" (need version in range "); VersionNumberRanges::write_range(OUT, V->as_requirement->version_range); WRITE(")"); } else { WRITE(" (any version will do)"); } HTML_CLOSE("li"); } } } build_vertex *W; LOOP_OVER_LINKED_LIST(W, build_vertex, V->build_edges) ExtensionInstaller::show_extensions(OUT, W, scan_count, built_in, built_in_count, installed, installed_count, required, requirements_count); LOOP_OVER_LINKED_LIST(W, build_vertex, V->use_edges) ExtensionInstaller::show_extensions(OUT, W, scan_count, built_in, built_in_count, installed, installed_count, required, requirements_count); }
int ExtensionInstaller::seek_extension_in_graph(inbuild_copy *C, build_vertex *V) { if (V->type == COPY_VERTEX) { inbuild_copy *VC = V->as_copy; if (Editions::cmp(C->edition, VC->edition) == 0) return TRUE; } build_vertex *W; LOOP_OVER_LINKED_LIST(W, build_vertex, V->build_edges) if (ExtensionInstaller::seek_extension_in_graph(C, W)) return TRUE; LOOP_OVER_LINKED_LIST(W, build_vertex, V->use_edges) if (ExtensionInstaller::seek_extension_in_graph(C, W)) return TRUE; return FALSE; }
void ExtensionInstaller::install_button(OUTPUT_STREAM, inform_project *proj, inbuild_copy *C) { TEMPORARY_TEXT(js_path) Get the extension path escaped for use in Javascript7.1 HTML_OPEN_WITH("a", "class=\"actionlink\" href='javascript:project().install(\"%S\")'", js_path); DISCARD_TEXT(js_path) ExtensionInstaller::install_icon(OUT); HTML_CLOSE("a"); } void ExtensionInstaller::install_icon(OUTPUT_STREAM) { WRITE("<span class=\"actionbutton\">install</span>"); } void ExtensionInstaller::uninstall_button(OUTPUT_STREAM, inform_project *proj, inbuild_copy *C) { TEMPORARY_TEXT(js_path) Get the extension path escaped for use in Javascript7.1 HTML_OPEN_WITH("a", "class=\"actionlink\" href='javascript:project().uninstall(\"%S\")'", js_path); DISCARD_TEXT(js_path) ExtensionInstaller::uninstall_icon(OUT); HTML_CLOSE("a"); } void ExtensionInstaller::uninstall_icon(OUTPUT_STREAM) { WRITE("<span class=\"actionbutton\">uninstall</span>"); } void ExtensionInstaller::modernise_button(OUTPUT_STREAM, inform_project *proj, inbuild_copy *C) { TEMPORARY_TEXT(js_path) Get the extension path escaped for use in Javascript7.1 HTML_OPEN_WITH("a", "class=\"actionlink\" href='javascript:project().modernise(\"%S\")'", js_path); DISCARD_TEXT(js_path) ExtensionInstaller::modernise_icon(OUT); HTML_CLOSE("a"); } void ExtensionInstaller::modernise_icon(OUTPUT_STREAM) { WRITE("<span class=\"actionbutton\">modernise</span>"); }
§7.1. Get the extension path escaped for use in Javascript7.1 =
TEMPORARY_TEXT(path) if (C->location_if_file) WRITE_TO(path, "%f", C->location_if_file); else WRITE_TO(path, "%p", C->location_if_path); LOOP_THROUGH_TEXT(pos, path) { inchar32_t c = Str::get(pos); if (c == '\\') WRITE_TO(js_path, "\\\\"); else PUT_TO(js_path, c); } DISCARD_TEXT(path)
void ExtensionInstaller::open_test_link(OUTPUT_STREAM, inform_project *proj, inform_extension *E, text_stream *intest_command, text_stream *intest_case) { inbuild_copy *C = E->as_copy; TEMPORARY_TEXT(js_path) Get the extension path escaped for use in Javascript7.1 HTML_OPEN_WITH("a", "class=\"registrycontentslink\" href='javascript:project().test(\"%S\", \"%S\", \"%S\")'", js_path, intest_command, intest_case); DISCARD_TEXT(js_path) } void ExtensionInstaller::close_test_link(OUTPUT_STREAM, inform_project *proj, inform_extension *E, text_stream *intest_command, text_stream *intest_case) { HTML_CLOSE("a"); }
§9. The moderniser. This works in two stages. First it is called with confirmed false, and it produces an HTML report on the feasibility of making the installation, with a clickable Confirm button. Then, assuming the user does click that button, the Installer is called again, with confirmed true. It takes action and also produces a second report.
void ExtensionInstaller::modernise(inbuild_copy *C, int confirmed, pathname *to_tool, int meth) { inform_project *project = Supervisor::project_set_at_command_line(); if (project == NULL) Errors::fatal("-project not set at command line"); TEMPORARY_TEXT(pname) WRITE_TO(pname, "'%S'", project->as_copy->edition->work->title); text_stream *OUT = NULL; if ((C->edition->work->genre == extension_genre) || (C->edition->work->genre == extension_bundle_genre)) { Begin modernisation page9.1; if (OUT) { if (confirmed) Make confirmed modernisation page9.3 else Make unconfirmed modernisation page9.2; ExtensionInstaller::end(); } } DISCARD_TEXT(pname) }
§9.1. Begin modernisation page9.1 =
TEMPORARY_TEXT(desc) TEMPORARY_TEXT(version) Works::write(desc, C->edition->work); semantic_version_number V = C->edition->version; if (VersionNumbers::is_null(V)) { WRITE_TO(version, "An extension"); } else { WRITE_TO(version, "Version %v of an extension", &V); } OUT = ExtensionInstaller::begin(desc, version); DISCARD_TEXT(desc) DISCARD_TEXT(version)
- This code is used in §9.
§9.2. Make unconfirmed modernisation page9.2 =
HTML_OPEN("p"); WRITE("If you click the button below to confirm, I will 'modernise' this " "extension. It's currently stored in the single file format which goes " "back to the early 2000s, meaning that the extension occupies a single " "file in the materials folder for this project:"); HTML_CLOSE("p"); HTML_OPEN("ul"); HTML_OPEN("li"); HTML_OPEN("b"); Filenames::to_text_relative(OUT, C->location_if_file, Pathnames::up(Projects::materials_path(project))); HTML_CLOSE("b"); HTML_CLOSE("li"); HTML_CLOSE("ul"); HTML_OPEN("p"); WRITE("When modernised, it will become a small directory in the same place, " "with its name ending '.i7xd' not '.i7x'. It should then continue to work " "as before."); HTML_CLOSE("p"); HTML_OPEN("p"); WRITE("Only the copy of the extension in this project's materials folder " "will be changed, so no other project should be affected."); HTML_CLOSE("p"); HTML_OPEN("p"); WRITE("Just as a precaution, the old version will be moved to the trash for " "this project, rather than actually deleted, but you shouldn't need it again."); HTML_CLOSE("p"); HTML_OPEN_WITH("a", "href='javascript:project().confirmAction()'"); HTML_OPEN_WITH("button", "class=\"safebutton\""); WRITE("Modernise %S", C->edition->work->title); HTML_CLOSE("button"); HTML_CLOSE("a");
- This code is used in §9.
§9.3. Make confirmed modernisation page9.3 =
HTML_OPEN("p"); WRITE("Modernising:"); HTML_CLOSE("p"); TEMPORARY_TEXT(converter_report) Extensions::modernise(Extensions::from_copy(C), converter_report); HTML_OPEN("p"); HTML_OPEN("ul"); HTML_OPEN("li"); WRITE("%S", converter_report); HTML_CLOSE("li"); HTML_CLOSE("ul"); HTML_CLOSE("p"); DISCARD_TEXT(converter_report) build_methodology *BM = BuildMethodology::new(Pathnames::up(to_tool), TRUE, meth); TEMPORARY_TEXT(trash_report) ExtensionInstaller::trash(trash_report, project, C, BM); HTML_OPEN("p"); WRITE("Moving the single file form of extension to the trash folder for %S:", pname); HTML_CLOSE("p"); HTML_OPEN("ul"); WRITE("%S", trash_report); HTML_CLOSE("ul"); DISCARD_TEXT(trash_report) ExtensionWebsite::update(project);
- This code is used in §9.