[ExtensionConverter::] The Converter. To convert an extension from the traditional one-file format to the more modern directory-based format. @ In all paths through this function, we must write either good news or bad to |OUT|: bad news usually following from the file system refusing to create a text file or directory. = void ExtensionConverter::go(inform_extension *E, text_stream *OUT) { Extensions::read_source_text_for(E); TEMPORARY_TEXT(dirname) Editions::write_canonical_leaf(dirname, E->as_copy->edition); WRITE_TO(dirname, ".i7xd"); pathname *P_home = Pathnames::down(Filenames::up(E->as_copy->location_if_file), dirname); pathname *P_source = Pathnames::down(P_home, I"Source"); pathname *P_documentation = Pathnames::down(P_home, I"Documentation"); pathname *P_examples = Pathnames::down(P_documentation, I"Examples"); @; @; @; @; WRITE("migrated to directory '%S'\n", dirname); DISCARD_TEXT(dirname) } @ = if (Directories::exists(P_home)) { WRITE("can't make this into %S because directory '%p' already exists", dirname, P_home); return; } if (ExtensionConverter::mkdir(E, OUT, P_home) == FALSE) return; @ = JSON_value *JM = JSON::new_object(); JSON_value *is = JSON::new_object(); JSON::add_to_object(JM, I"is", is); JSON::add_to_object(is, I"type", JSON::new_string(I"extension")); JSON::add_to_object(is, I"title", JSON::new_string(E->as_copy->edition->work->title)); JSON::add_to_object(is, I"author", JSON::new_string(E->as_copy->edition->work->author_name)); semantic_version_number V = E->as_copy->edition->version; if (VersionNumbers::is_null(V) == FALSE) { TEMPORARY_TEXT(vt) WRITE_TO(vt, "%v", &V); JSON::add_to_object(is, I"version", JSON::new_string(vt)); DISCARD_TEXT(vt) } filename *JF = Filenames::in(P_home, I"extension_metadata.json"); text_stream JSONF_struct; text_stream *JS = &JSONF_struct; if (ExtensionConverter::fopen(E, OUT, JS, JF) == FALSE) return; JSON::encode(JS, JM); STREAM_CLOSE(JS); @ = if (ExtensionConverter::mkdir(E, OUT, P_source) == FALSE) return; TEMPORARY_TEXT(sleaf) Editions::write_canonical_leaf(sleaf, E->as_copy->edition); WRITE_TO(sleaf, ".i7x"); filename *SF = Filenames::in(P_source, sleaf); DISCARD_TEXT(sleaf) text_stream SRCF_struct; text_stream *SS = &SRCF_struct; if (ExtensionConverter::fopen(E, OUT, SS, SF) == FALSE) return; WRITE_TO(SS, "%S", E->read_into_file->body_text); STREAM_CLOSE(SS); @ = text_stream *source = E->read_into_file->torn_off_documentation; if (Str::is_whitespace(source) == FALSE) { if (ExtensionConverter::mkdir(E, OUT, P_documentation) == FALSE) return; filename *F_documentation = Filenames::in(P_documentation, I"Documentation.md"); text_stream DOCF_struct; text_stream *S_documentation = &DOCF_struct; if (ExtensionConverter::fopen(E, OUT, S_documentation, F_documentation) == FALSE) return; @; STREAM_CLOSE(S_documentation); } @ We work through the documentation attached to the original extension (if there was any) in two passes. On pass 1, we do nothing except to count the number of section and chapter headings. On pass 2, we split up the content into the main file and individual example files. @ = int chapter_count = 0, section_count = 0, example_count = 0; text_stream EG_struct; for (int pass = 1; pass <= 2; pass++) { text_stream *dest = S_documentation, *S_example = NULL; TEMPORARY_TEXT(line) int indentation = 0, space_count = 0; for (int i=0; i; Str::clear(line); indentation = 0; space_count = 0; } else if ((Str::len(line) == 0) && (Characters::is_whitespace(c))) { if (c == '\t') indentation++; if (c == ' ') space_count++; if (space_count == 4) { indentation++; space_count = 0; } } else { PUT_TO(line, c); } } if (Str::len(line) > 0) @; DISCARD_TEXT(line) if (pass == 2) @; } @ = Str::trim_white_space(line); match_results mr = Regexp::create_mr(); if ((Regexp::match(&mr, line, L"Section *: *(%c+?)")) || (Regexp::match(&mr, line, L"Section *- *(%c+?)"))) { if (pass == 1) section_count++; if (pass == 2) @; } else if ((Regexp::match(&mr, line, L"Chapter *: *(%c+?)")) || (Regexp::match(&mr, line, L"Chapter *- *(%c+?)"))) { if (pass == 1) chapter_count++; if (pass == 2) { @; @; } } if (pass == 2) { if ((Regexp::match(&mr, line, L"Example *: *(%**) *(%c+?)")) || (Regexp::match(&mr, line, L"Example *- *(%**) *(%c+?)"))) @ else @; } Regexp::dispose_of(&mr); @ Old single-file extensions tended to use Chapters, intended as major headings, with not much content, and not to use Sections at all. Because we now split off Chapters into their own HTML pages, we don't want that, so when converting an old extension which has chapters but no sections, we downgrade all the chapters to sections. @ = if (section_count == 0) { Str::clear(line); WRITE_TO(line, "Section: %S", mr.exp[0]); } @ = text_stream *stars = mr.exp[0]; text_stream *title = mr.exp[1]; text_stream *desc = NULL; match_results mr2 = Regexp::create_mr(); if (Regexp::match(&mr2, title, L" *(%c+?) - *(%c+) *")) { title = mr2.exp[0]; desc = mr2.exp[1]; } @; Regexp::dispose_of(&mr2); @ = if ((example_count++ == 0) && (ExtensionConverter::mkdir(E, OUT, P_examples) == FALSE)) return; TEMPORARY_TEXT(eleaf) for (int i=0, last_was_ws=TRUE; i 0) WRITE_TO(dest, "Description: %S\n", desc); @ = if (S_example) { STREAM_CLOSE(S_example); S_example = NULL; dest = S_documentation; } @ Note that we amend the old-style paste marker to the new style, though both are legal. @ = for (int i=0; i