688 lines
25 KiB
Text
Executable file
688 lines
25 KiB
Text
Executable file
[WebMetadata::] Web Structure.
|
|
|
|
To read the structure of a literate programming web from a path in the file
|
|
system.
|
|
|
|
@ \section{Introduction.}
|
|
Webs are literate programs for the Inweb LP system. A single web consists of
|
|
a number of chapters (though sometimes just one, called "Sections"), each
|
|
of which consists of a number of sections. A web can represent a stand-alone
|
|
program, or a library to be used in multiple programs, in which case it is
|
|
called a "module".
|
|
|
|
Inweb syntax has gradually shifted over the years, but there are two main
|
|
versions: the second was cleaned up and simplified from the first in 2019.
|
|
|
|
<<*>>=
|
|
enum V1_SYNTAX from 1
|
|
enum V2_SYNTAX
|
|
|
|
@ \section{Web MD.}
|
|
No relation to the website of the same name: MD here stands for metadata.
|
|
Our task in this section will be to read a web from the filing system and
|
|
produce the following metadata structure.
|
|
|
|
Each web produces a single instance of [[web_md]]:
|
|
|
|
<<*>>=
|
|
typedef struct web_md {
|
|
struct pathname *path_to_web; /* relative to the current working directory */
|
|
struct filename *single_file; /* relative to the current working directory */
|
|
struct linked_list *bibliographic_data; /* of [[web_bibliographic_datum]] */
|
|
struct semantic_version_number version_number; /* as deduced from bibliographic data */
|
|
int default_syntax; /* which version syntax the sections will have */
|
|
int chaptered; /* has the author explicitly divided it into named chapters? */
|
|
struct text_stream *main_language_name; /* in which most of the sections are written */
|
|
|
|
struct module *as_module; /* the root of a small dependency graph */
|
|
|
|
struct filename *contents_filename; /* or [[NULL]] for a single-file web */
|
|
struct linked_list *tangle_target_names; /* of [[text_stream]] */
|
|
struct linked_list *header_filenames; /* of [[filename]] */
|
|
|
|
struct linked_list *chapters_md; /* of [[chapter_md]] */
|
|
struct linked_list *sections_md; /* of [[section_md]] */
|
|
CLASS_DEFINITION
|
|
} web_md;
|
|
|
|
@ The [[chapters_md]] list in a [[web_md]] contains these as its entries:
|
|
|
|
<<*>>=
|
|
typedef struct chapter_md {
|
|
struct text_stream *ch_range; /* e.g., [[P| for Preliminaries, |7| for Chapter 7, |C]] for Appendix C */
|
|
struct text_stream *ch_title; /* e.g., "Chapter 3: Fresh Water Fish" */
|
|
struct text_stream *ch_basic_title; /* e.g., "Chapter 3" */
|
|
struct text_stream *ch_decorated_title; /* e.g., "Fresh Water Fish" */
|
|
struct text_stream *rubric; /* optional; without double-quotation marks */
|
|
|
|
struct text_stream *ch_language_name; /* in which most of the sections are written */
|
|
|
|
int imported; /* from a different web? */
|
|
|
|
struct linked_list *sections_md; /* of [[section_md]] */
|
|
CLASS_DEFINITION
|
|
} chapter_md;
|
|
|
|
@ And the [[sections_md]] list in a [[chapter_md]] contains these as its entries:
|
|
|
|
<<*>>=
|
|
typedef struct section_md {
|
|
struct text_stream *sect_title; /* e.g., "Program Control" */
|
|
struct text_stream *sect_range; /* e.g., "2/ct" */
|
|
int using_syntax; /* which syntax the web is written in */
|
|
int is_a_singleton; /* is this the only section in its entire web? */
|
|
|
|
struct filename *source_file_for_section;
|
|
|
|
struct text_stream *tag_name;
|
|
struct text_stream *sect_independent_language;
|
|
struct text_stream *sect_language_name;
|
|
struct text_stream *titling_line_to_insert;
|
|
|
|
struct module *owning_module;
|
|
CLASS_DEFINITION
|
|
} section_md;
|
|
|
|
@ \section{Reading from the file system.}
|
|
Webs can be stored in two ways: as a directory containing a multitude of files,
|
|
in which case the pathname [[P]] is supplied; or as a single file with everything
|
|
in one (and thus, implicitly, a single chapter and a single section), in which
|
|
case a filename [[alt_F]] is supplied.
|
|
|
|
<<*>>=
|
|
web_md *WebMetadata::get_without_modules(pathname *P, filename *alt_F) {
|
|
return WebMetadata::get(P, alt_F, V2_SYNTAX, NULL, FALSE, FALSE, NULL);
|
|
}
|
|
|
|
web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
|
|
module_search *I, int verbosely, int including_modules, pathname *path_to_inweb) {
|
|
if ((including_modules) && (I == NULL)) I = WebModules::make_search_path(NULL);
|
|
web_md *Wm = CREATE(web_md);
|
|
<<Begin the bibliographic data>>;
|
|
<<Initialise the rest of the web MD>>;
|
|
WebMetadata::read_contents_page(Wm, Wm->as_module, I, verbosely,
|
|
including_modules, NULL, path_to_inweb);
|
|
<<Consolidate the bibliographic data>>;
|
|
<<Work out the section ranges>>;
|
|
return Wm;
|
|
}
|
|
|
|
<<Begin the bibliographic data>>=
|
|
Wm->bibliographic_data = NEW_LINKED_LIST(web_bibliographic_datum);
|
|
Bibliographic::initialise_data(Wm);
|
|
|
|
<<Initialise the rest of the web MD>>=
|
|
if (P) {
|
|
Wm->path_to_web = P;
|
|
Wm->single_file = NULL;
|
|
Wm->contents_filename = WebMetadata::contents_filename(P);
|
|
} else {
|
|
Wm->path_to_web = Filenames::up(alt_F);
|
|
Wm->single_file = alt_F;
|
|
Wm->contents_filename = NULL;
|
|
}
|
|
Wm->version_number = VersionNumbers::null();
|
|
Wm->default_syntax = syntax_version;
|
|
Wm->chaptered = FALSE;
|
|
Wm->sections_md = NEW_LINKED_LIST(sections_md);
|
|
Wm->chapters_md = NEW_LINKED_LIST(chapter_md);
|
|
Wm->tangle_target_names = NEW_LINKED_LIST(text_stream);
|
|
Wm->main_language_name = Str::new();
|
|
Wm->header_filenames = NEW_LINKED_LIST(filename);
|
|
Wm->as_module = WebModules::create_main_module(Wm);
|
|
|
|
<<Consolidate the bibliographic data>>=
|
|
Bibliographic::check_required_data(Wm);
|
|
BuildFiles::set_bibliographic_data_for(Wm);
|
|
BuildFiles::deduce_semver(Wm);
|
|
|
|
@ If no range is supplied, we make one ourselves.
|
|
|
|
<<Work out the section ranges>>=
|
|
int sequential = FALSE; /* are we numbering sections sequentially? */
|
|
if (Str::eq(Bibliographic::get_datum(Wm, I"Sequential Section Ranges"), I"On"))
|
|
sequential = TRUE;
|
|
chapter_md *Cm;
|
|
section_md *Sm;
|
|
LOOP_OVER_LINKED_LIST(Cm, chapter_md, Wm->chapters_md) {
|
|
int section_counter = 1;
|
|
LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) {
|
|
if (Str::len(Sm->sect_range) == 0)
|
|
<<Concoct a range for section Sm in chapter Cm in web Wm>>;
|
|
section_counter++;
|
|
}
|
|
}
|
|
|
|
<<Concoct a range for section Sm in chapter Cm in web Wm>>=
|
|
if (sequential) {
|
|
WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range);
|
|
WRITE_TO(Sm->sect_range, "s%d", section_counter);
|
|
} else {
|
|
text_stream *from = Sm->sect_title;
|
|
int letters_from_each_word = 5;
|
|
do {
|
|
Str::clear(Sm->sect_range);
|
|
WRITE_TO(Sm->sect_range, "%S/", Cm->ch_range);
|
|
<<Make the tail using this many consonants from each word>>;
|
|
if (--letters_from_each_word == 0) break;
|
|
} while (Str::len(Sm->sect_range) > 5);
|
|
|
|
<<Terminate with disambiguating numbers in case of collisions>>;
|
|
}
|
|
|
|
@ We collapse words to an initial letter plus consonants: thus "electricity"
|
|
would be "elctrcty", since we don't count "y" as a vowel here.
|
|
|
|
<<Make the tail using this many consonants from each word>>=
|
|
int sn = 0, sw = Str::len(Sm->sect_range);
|
|
if (Platform::is_folder_separator(Str::get_at(from, sn))) sn++;
|
|
int letters_from_current_word = 0;
|
|
while ((Str::get_at(from, sn)) && (Str::get_at(from, sn) != '.')) {
|
|
if (Str::get_at(from, sn) == ' ') letters_from_current_word = 0;
|
|
else {
|
|
if (letters_from_current_word < letters_from_each_word) {
|
|
if (Str::get_at(from, sn) != '-') {
|
|
wchar_t l = Characters::tolower(Str::get_at(from, sn));
|
|
if ((letters_from_current_word == 0) ||
|
|
((l != 'a') && (l != 'e') && (l != 'i') &&
|
|
(l != 'o') && (l != 'u'))) {
|
|
Str::put_at(Sm->sect_range, sw++, l);
|
|
Str::put_at(Sm->sect_range, sw, 0);
|
|
letters_from_current_word++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sn++;
|
|
}
|
|
|
|
@ We never want two sections to have the same range.
|
|
|
|
<<Terminate with disambiguating numbers in case of collisions>>=
|
|
TEMPORARY_TEXT(original_range)
|
|
Str::copy(original_range, Sm->sect_range);
|
|
int disnum = 0, collision = FALSE;
|
|
do {
|
|
if (disnum++ > 0) {
|
|
int ldn = 4;
|
|
if (disnum >= 1000) ldn = 3;
|
|
else if (disnum >= 100) ldn = 2;
|
|
else if (disnum >= 10) ldn = 1;
|
|
else ldn = 0;
|
|
Str::clear(Sm->sect_range);
|
|
WRITE_TO(Sm->sect_range, "%S", original_range);
|
|
Str::truncate(Sm->sect_range, Str::len(Sm->sect_range) - ldn);
|
|
WRITE_TO(Sm->sect_range, "%d", disnum);
|
|
}
|
|
collision = FALSE;
|
|
chapter_md *Cm2;
|
|
section_md *Sm2;
|
|
LOOP_OVER_LINKED_LIST(Cm2, chapter_md, Wm->chapters_md)
|
|
LOOP_OVER_LINKED_LIST(Sm2, section_md, Cm2->sections_md)
|
|
if ((Sm2 != Sm) && (Str::eq(Sm2->sect_range, Sm->sect_range))) {
|
|
collision = TRUE; break;
|
|
}
|
|
} while (collision);
|
|
DISCARD_TEXT(original_range)
|
|
|
|
@ \section{Reading the contents page.}
|
|
Making the web begins by reading the contents section, which really isn't a
|
|
section at all (and perhaps we shouldn't pretend that it is by the use of the
|
|
[[.w]] file extension, but we probably want it to have the same file extension,
|
|
and its syntax is chosen so that syntax-colouring for regular sections doesn't
|
|
make it look odd). When the word "section" is used in the Inweb code, it
|
|
almost always means "section other than the contents".
|
|
|
|
Because a contents page can, by importing a module, cause a further contents
|
|
page to be read, we set this up as a recursion.
|
|
|
|
We then run through an individual contents page line by line, using the
|
|
following slate of variables to keep track of where we are.
|
|
|
|
With a single-file web, the "contents section" doesn't exist as a file in its
|
|
own right: instead, it's the top few lines of the single file. We handle that
|
|
by halting at the junction point.
|
|
|
|
<<*>>=
|
|
typedef struct reader_state {
|
|
struct web_md *Wm;
|
|
struct filename *contents_filename;
|
|
int in_biblio;
|
|
int in_purpose; /* Reading the bit just after the new chapter? */
|
|
struct chapter_md *chapter_being_scanned;
|
|
struct text_stream *chapter_dir_name; /* Where sections in the current chapter live */
|
|
struct text_stream *titling_line_to_insert; /* To be inserted automagically */
|
|
struct pathname *path_to; /* Where web material is being read from */
|
|
struct module *reading_from;
|
|
struct module_search *import_from; /* Where imported webs are */
|
|
struct pathname *path_to_inweb;
|
|
int scan_verbosely;
|
|
int including_modules;
|
|
int main_web_not_module; /* Reading the original web, or an included one? */
|
|
int halt_at_at; /* Used for reading contents pages of single-file webs */
|
|
int halted; /* Set when such a halt has occurred */
|
|
int section_count;
|
|
struct section_md *last_section;
|
|
} reader_state;
|
|
|
|
void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
|
|
module_search *import_path, int verbosely,
|
|
int including_modules, pathname *path, pathname *X) {
|
|
reader_state RS;
|
|
<<Initialise the reader state>>;
|
|
|
|
int cl = TextFiles::read(RS.contents_filename, FALSE, "can't open contents file",
|
|
TRUE, WebMetadata::read_contents_line, NULL, &RS);
|
|
if (verbosely) {
|
|
if (Wm->single_file) {
|
|
PRINT("Read %d lines of contents part at top of file\n", cl);
|
|
} else {
|
|
PRINT("Read contents section (%d lines)\n", cl);
|
|
}
|
|
}
|
|
if (RS.section_count == 1) RS.last_section->is_a_singleton = TRUE;
|
|
}
|
|
|
|
<<Initialise the reader state>>=
|
|
RS.Wm = Wm;
|
|
RS.reading_from = of_module;
|
|
RS.in_biblio = TRUE;
|
|
RS.in_purpose = FALSE;
|
|
RS.chapter_being_scanned = NULL;
|
|
RS.chapter_dir_name = Str::new();
|
|
RS.titling_line_to_insert = Str::new();
|
|
RS.scan_verbosely = verbosely;
|
|
RS.including_modules = including_modules;
|
|
RS.path_to = path;
|
|
RS.import_from = import_path;
|
|
RS.halted = FALSE;
|
|
RS.path_to_inweb = X;
|
|
|
|
if (path == NULL) {
|
|
path = Wm->path_to_web;
|
|
RS.main_web_not_module = TRUE;
|
|
} else {
|
|
RS.main_web_not_module = FALSE;
|
|
}
|
|
|
|
if (Wm->single_file) {
|
|
RS.contents_filename = Wm->single_file;
|
|
RS.halt_at_at = TRUE;
|
|
} else {
|
|
RS.contents_filename = WebMetadata::contents_filename(path);
|
|
RS.halt_at_at = FALSE;
|
|
}
|
|
RS.section_count = 0;
|
|
RS.last_section = NULL;
|
|
|
|
@ The contents section has a syntax quite different from all other sections,
|
|
and sets out bibliographic information about the web, the sections and their
|
|
organisation, and so on.
|
|
|
|
<<*>>=
|
|
void WebMetadata::read_contents_line(text_stream *line, text_file_position *tfp, void *X) {
|
|
reader_state *RS = (reader_state *) X;
|
|
if (RS->halted) return;
|
|
|
|
int begins_with_white_space = FALSE;
|
|
if (Characters::is_whitespace(Str::get_first_char(line)))
|
|
begins_with_white_space = TRUE;
|
|
Str::trim_white_space(line);
|
|
|
|
<<Act immediately if the web syntax version is changed>>;
|
|
int syntax = RS->Wm->default_syntax;
|
|
|
|
filename *filename_of_single_file_web = NULL;
|
|
if ((RS->halt_at_at) && (Str::get_at(line, 0) == '@'))
|
|
<<Halt at this point in the single file, and make the rest of it a one-chapter section>>;
|
|
|
|
<<Read regular contents material>>;
|
|
}
|
|
|
|
@ Since the web syntax version affects how the rest of the file is read, it's
|
|
no good simply to store this up for later: we have to change the web structure
|
|
immediately.
|
|
|
|
<<Act immediately if the web syntax version is changed>>=
|
|
if (Str::eq(line, I"Web Syntax Version: 1"))
|
|
RS->Wm->default_syntax = V1_SYNTAX;
|
|
else if (Str::eq(line, I"Web Syntax Version: 2"))
|
|
RS->Wm->default_syntax = V2_SYNTAX;
|
|
|
|
@ Suppose we're reading a single-file web, and we hit the first [[@]] marker.
|
|
The contents part has now ended, so we should halt scanning. But we also need
|
|
to give the web a single chapter ("Sections", range "S"), which contains a
|
|
single section ("All") consisting of the remainder of the single file.
|
|
|
|
<<Halt at this point in the single file, and make the rest of it a one-chapter section>>=
|
|
RS->halted = TRUE;
|
|
text_stream *new_chapter_range = I"S";
|
|
text_stream *language_name = NULL;
|
|
line = I"Sections";
|
|
<<Create the new chapter with these details>>;
|
|
line = I"All";
|
|
filename_of_single_file_web = tfp->text_file_filename;
|
|
<<Read about, and read in, a new section>>;
|
|
return;
|
|
|
|
@ With those two complications out of the way, we now know that we're reading
|
|
a line of contents material. At the start of the contents, this will be a
|
|
series of bibliographic data values; then there's a blank line, and then
|
|
we're into the section listing.
|
|
|
|
<<Read regular contents material>>=
|
|
if (Str::len(line) == 0) <<End bibliographic data here, at the blank line>>
|
|
else if (RS->in_biblio) <<Read the bibliographic data block at the top>>
|
|
else <<Read the roster of sections at the bottom>>;
|
|
|
|
@ At this point we've gone through the bibliographic lines at the top of the
|
|
contents page, and are soon going to read in the sections.
|
|
|
|
<<End bibliographic data here, at the blank line>>=
|
|
RS->in_biblio = FALSE;
|
|
|
|
@ The bibliographic data gives lines in any order specifying values of
|
|
variables with fixed names; a blank line ends the block.
|
|
|
|
<<Read the bibliographic data block at the top>>=
|
|
if (RS->main_web_not_module) {
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, line, L"(%c+?): (%c+?) *")) {
|
|
TEMPORARY_TEXT(key)
|
|
Str::copy(key, mr.exp[0]);
|
|
TEMPORARY_TEXT(value)
|
|
Str::copy(value, mr.exp[1]);
|
|
<<Set bibliographic key-value pair>>;
|
|
DISCARD_TEXT(key)
|
|
DISCARD_TEXT(value)
|
|
} else {
|
|
TEMPORARY_TEXT(err)
|
|
WRITE_TO(err, "expected 'Setting: Value' but found '%S'", line);
|
|
Errors::in_text_file_S(err, tfp);
|
|
DISCARD_TEXT(err)
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
}
|
|
|
|
<<Set bibliographic key-value pair>>=
|
|
if (Bibliographic::datum_can_be_declared(RS->Wm, key)) {
|
|
if (Bibliographic::datum_on_or_off(RS->Wm, key)) {
|
|
if ((Str::ne_wide_string(value, L"On")) && (Str::ne_wide_string(value, L"Off"))) {
|
|
TEMPORARY_TEXT(err)
|
|
WRITE_TO(err, "this setting must be 'On' or 'Off': %S", key);
|
|
Errors::in_text_file_S(err, tfp);
|
|
DISCARD_TEXT(err)
|
|
Str::clear(value);
|
|
WRITE_TO(value, "Off");
|
|
}
|
|
}
|
|
Bibliographic::set_datum(RS->Wm, key, value);
|
|
} else {
|
|
TEMPORARY_TEXT(err)
|
|
WRITE_TO(err, "no such bibliographic datum: %S", key);
|
|
Errors::in_text_file_S(err, tfp);
|
|
DISCARD_TEXT(err)
|
|
}
|
|
|
|
@ In the bulk of the contents, we find indented lines for sections and
|
|
unindented ones for chapters.
|
|
|
|
<<Read the roster of sections at the bottom>>=
|
|
if (begins_with_white_space == FALSE) {
|
|
if (Str::get_first_char(line) == '"') {
|
|
RS->in_purpose = TRUE; Str::delete_first_character(line);
|
|
}
|
|
if (RS->in_purpose == TRUE) <<Record the purpose of the current chapter>>
|
|
else <<Read about a new chapter>>;
|
|
} else <<Read about, and read in, a new section>>;
|
|
|
|
@ After a declared chapter heading, subsequent lines form its purpose, until
|
|
we reach a closed quote: we then stop, but remove the quotation marks. Because
|
|
we like a spoonful of syntactic sugar on our porridge, that's why.
|
|
|
|
<<Record the purpose of the current chapter>>=
|
|
if ((Str::len(line) > 0) && (Str::get_last_char(line) == '"')) {
|
|
Str::truncate(line, Str::len(line)-1); RS->in_purpose = FALSE;
|
|
}
|
|
if (RS->chapter_being_scanned) {
|
|
text_stream *r = RS->chapter_being_scanned->rubric;
|
|
if (Str::len(r) > 0) WRITE_TO(r, " ");
|
|
WRITE_TO(r, "%S", line);
|
|
}
|
|
|
|
@ The title tells us everything we need to know about a chapter:
|
|
|
|
<<Read about a new chapter>>=
|
|
TEMPORARY_TEXT(new_chapter_range) /* e.g., S, P, 1, 2, 3, A, B, ... */
|
|
TEMPORARY_TEXT(pdf_leafname)
|
|
text_stream *language_name = NULL;
|
|
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, line, L"(%c*%C) %(Independent(%c*)%)")) {
|
|
text_stream *title_alone = mr.exp[0];
|
|
language_name = mr.exp[1];
|
|
<<Mark this chapter as an independent tangle target>>;
|
|
Str::copy(line, title_alone);
|
|
}
|
|
int this_is_a_chapter = TRUE;
|
|
Str::clear(RS->chapter_dir_name);
|
|
if (Str::eq_wide_string(line, L"Sections")) {
|
|
WRITE_TO(new_chapter_range, "S");
|
|
WRITE_TO(RS->chapter_dir_name, "Sections");
|
|
WRITE_TO(pdf_leafname, "Sections.pdf");
|
|
RS->Wm->chaptered = FALSE;
|
|
Str::clear(RS->titling_line_to_insert);
|
|
} else if (Str::eq_wide_string(line, L"Preliminaries")) {
|
|
WRITE_TO(new_chapter_range, "P");
|
|
WRITE_TO(RS->chapter_dir_name, "Preliminaries");
|
|
Str::clear(RS->titling_line_to_insert);
|
|
WRITE_TO(RS->titling_line_to_insert, "%S.", line);
|
|
WRITE_TO(pdf_leafname, "Preliminaries.pdf");
|
|
RS->Wm->chaptered = TRUE;
|
|
} else if (Str::eq_wide_string(line, L"Manual")) {
|
|
WRITE_TO(new_chapter_range, "M");
|
|
WRITE_TO(RS->chapter_dir_name, "Manual");
|
|
Str::clear(RS->titling_line_to_insert);
|
|
WRITE_TO(RS->titling_line_to_insert, "%S.", line);
|
|
WRITE_TO(pdf_leafname, "Manual.pdf");
|
|
RS->Wm->chaptered = TRUE;
|
|
} else if (Regexp::match(&mr, line, L"Header: (%c+)")) {
|
|
pathname *P = RS->path_to;
|
|
if (P == NULL) P = RS->Wm->path_to_web;
|
|
P = Pathnames::down(P, I"Headers");
|
|
filename *HF = Filenames::in(P, mr.exp[0]);
|
|
ADD_TO_LINKED_LIST(HF, filename, RS->Wm->header_filenames);
|
|
this_is_a_chapter = FALSE;
|
|
} else if (Regexp::match(&mr, line, L"Import: (%c+)")) {
|
|
if (RS->halt_at_at)
|
|
Errors::in_text_file_S(I"single-file webs cannot Import modules", tfp);
|
|
else if (RS->import_from) {
|
|
module *imported =
|
|
WebModules::find(RS->Wm, RS->import_from, mr.exp[0], RS->path_to_inweb);
|
|
if (imported == NULL) {
|
|
TEMPORARY_TEXT(err)
|
|
WRITE_TO(err, "unable to find module: %S", line);
|
|
Errors::in_text_file_S(err, tfp);
|
|
DISCARD_TEXT(err)
|
|
} else {
|
|
if (RS->including_modules) {
|
|
int save_syntax = RS->Wm->default_syntax;
|
|
WebMetadata::read_contents_page(RS->Wm, imported, RS->import_from,
|
|
RS->scan_verbosely, RS->including_modules,
|
|
imported->module_location, RS->path_to_inweb);
|
|
RS->Wm->default_syntax = save_syntax;
|
|
}
|
|
}
|
|
}
|
|
this_is_a_chapter = FALSE;
|
|
} else if (Regexp::match(&mr, line, L"Chapter (%d+): %c+")) {
|
|
int n = Str::atoi(mr.exp[0], 0);
|
|
WRITE_TO(new_chapter_range, "%d", n);
|
|
WRITE_TO(RS->chapter_dir_name, "Chapter %d", n);
|
|
Str::clear(RS->titling_line_to_insert);
|
|
WRITE_TO(RS->titling_line_to_insert, "%S.", line);
|
|
WRITE_TO(pdf_leafname, "Chapter-%d.pdf", n);
|
|
RS->Wm->chaptered = TRUE;
|
|
} else if (Regexp::match(&mr, line, L"Appendix (%c): %c+")) {
|
|
text_stream *letter = mr.exp[0];
|
|
Str::copy(new_chapter_range, letter);
|
|
WRITE_TO(RS->chapter_dir_name, "Appendix %S", letter);
|
|
Str::clear(RS->titling_line_to_insert);
|
|
WRITE_TO(RS->titling_line_to_insert, "%S.", line);
|
|
WRITE_TO(pdf_leafname, "Appendix-%S.pdf", letter);
|
|
RS->Wm->chaptered = TRUE;
|
|
} else {
|
|
TEMPORARY_TEXT(err)
|
|
WRITE_TO(err, "segment not understood: %S", line);
|
|
Errors::in_text_file_S(err, tfp);
|
|
WRITE_TO(STDERR, "(Must be 'Chapter <number>: Title', "
|
|
"'Appendix <letter A to O>: Title',\n");
|
|
WRITE_TO(STDERR, "'Manual', 'Preliminaries' or 'Sections')\n");
|
|
DISCARD_TEXT(err)
|
|
}
|
|
|
|
if (this_is_a_chapter) <<Create the new chapter with these details>>;
|
|
DISCARD_TEXT(new_chapter_range)
|
|
DISCARD_TEXT(pdf_leafname)
|
|
Regexp::dispose_of(&mr);
|
|
|
|
@ A chapter whose title marks it as Independent becomes a new tangle target,
|
|
with the same language as the main web unless stated otherwise.
|
|
|
|
<<Mark this chapter as an independent tangle target>>=
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, language_name, L" *"))
|
|
language_name = Bibliographic::get_datum(RS->Wm, I"Language");
|
|
else if (Regexp::match(&mr, language_name, L" *(%c*?) *"))
|
|
language_name = mr.exp[0];
|
|
Regexp::dispose_of(&mr);
|
|
|
|
<<Create the new chapter with these details>>=
|
|
chapter_md *Cm = CREATE(chapter_md);
|
|
Cm->ch_range = Str::duplicate(new_chapter_range);
|
|
if (line == NULL) PRINT("Nullity!\n");
|
|
Cm->ch_title = Str::duplicate(line);
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, Cm->ch_title, L"(%c*?): *(%c*)")) {
|
|
Cm->ch_basic_title = Str::duplicate(mr.exp[0]);
|
|
Cm->ch_decorated_title = Str::duplicate(mr.exp[1]);
|
|
} else {
|
|
Cm->ch_basic_title = Str::duplicate(Cm->ch_title);
|
|
Cm->ch_decorated_title = Str::new();
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
Cm->rubric = Str::new();
|
|
Cm->ch_language_name = language_name;
|
|
Cm->imported = TRUE;
|
|
Cm->sections_md = NEW_LINKED_LIST(section_md);
|
|
if (RS->main_web_not_module) Cm->imported = FALSE;
|
|
|
|
ADD_TO_LINKED_LIST(Cm, chapter_md, RS->Wm->chapters_md);
|
|
ADD_TO_LINKED_LIST(Cm, chapter_md, RS->reading_from->chapters_md);
|
|
RS->chapter_being_scanned = Cm;
|
|
|
|
@ That's enough on creating chapters. This is the more interesting business
|
|
of registering a new section within a chapter -- more interesting because
|
|
we also read in and process its file.
|
|
|
|
<<Read about, and read in, a new section>>=
|
|
section_md *Sm = CREATE(section_md);
|
|
<<Initialise the section structure>>;
|
|
<<Add the section to the web and the current chapter>>;
|
|
<<Work out the language and tangle target for the section>>;
|
|
|
|
if (Sm->source_file_for_section == NULL)
|
|
<<Work out the filename of this section file>>;
|
|
|
|
<<Initialise the section structure>>=
|
|
Sm->source_file_for_section = filename_of_single_file_web;
|
|
Sm->using_syntax = syntax;
|
|
Sm->is_a_singleton = FALSE;
|
|
Sm->titling_line_to_insert = Str::duplicate(RS->titling_line_to_insert);
|
|
Sm->sect_range = Str::new();
|
|
Str::clear(RS->titling_line_to_insert);
|
|
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, line, L"(%c+) %^\"(%c+)\" *")) {
|
|
Sm->sect_title = Str::duplicate(mr.exp[0]);
|
|
Sm->tag_name = Str::duplicate(mr.exp[1]);
|
|
} else {
|
|
Sm->sect_title = Str::duplicate(line);
|
|
Sm->tag_name = NULL;
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
Sm->owning_module = RS->reading_from;
|
|
|
|
<<Add the section to the web and the current chapter>>=
|
|
chapter_md *Cm = RS->chapter_being_scanned;
|
|
RS->section_count++;
|
|
RS->last_section = Sm;
|
|
ADD_TO_LINKED_LIST(Sm, section_md, Cm->sections_md);
|
|
ADD_TO_LINKED_LIST(Sm, section_md, RS->Wm->sections_md);
|
|
ADD_TO_LINKED_LIST(Sm, section_md, RS->reading_from->sections_md);
|
|
|
|
<<Work out the language and tangle target for the section>>=
|
|
Sm->sect_language_name = RS->chapter_being_scanned->ch_language_name; /* by default */
|
|
match_results mr = Regexp::create_mr();
|
|
if (Regexp::match(&mr, line, L"(%c*%C) %(Independent (%c*) *%)")) {
|
|
text_stream *title_alone = mr.exp[0];
|
|
text_stream *language_name = mr.exp[1];
|
|
<<Mark this section as an independent tangle target>>;
|
|
Str::copy(Sm->sect_title, title_alone);
|
|
}
|
|
Regexp::dispose_of(&mr);
|
|
|
|
<<Mark this section as an independent tangle target>>=
|
|
text_stream *p = language_name;
|
|
if (Str::len(p) == 0) p = Bibliographic::get_datum(RS->Wm, I"Language");
|
|
Sm->sect_independent_language = Str::duplicate(p);
|
|
|
|
@ If we're told that a section is called "Bells and Whistles", what filename
|
|
is it stored in? Firstly, the leafname is normally [[Bells and Whistles.w]],
|
|
but the extension used doesn't have to be [[.w]]: for Inform 6 template files,
|
|
the extension needs to be [[.i6t]]. We allow either.
|
|
|
|
<<Work out the filename of this section file>>=
|
|
TEMPORARY_TEXT(leafname_to_use)
|
|
WRITE_TO(leafname_to_use, "%S.i6t", Sm->sect_title);
|
|
pathname *P = RS->path_to;
|
|
if (P == NULL) P = RS->Wm->path_to_web;
|
|
if (Str::len(RS->chapter_dir_name) > 0)
|
|
P = Pathnames::down(P, RS->chapter_dir_name);
|
|
Sm->source_file_for_section = Filenames::in(P, leafname_to_use);
|
|
if (TextFiles::exists(Sm->source_file_for_section) == FALSE) {
|
|
Str::clear(leafname_to_use);
|
|
WRITE_TO(leafname_to_use, "%S.w", Sm->sect_title);
|
|
Sm->source_file_for_section = Filenames::in(P, leafname_to_use);
|
|
}
|
|
DISCARD_TEXT(leafname_to_use)
|
|
|
|
@ \section{Relative pathnames or filenames.}
|
|
|
|
<<*>>=
|
|
int WebMetadata::directory_looks_like_a_web(pathname *P) {
|
|
return TextFiles::exists(WebMetadata::contents_filename(P));
|
|
}
|
|
|
|
filename *WebMetadata::contents_filename(pathname *P) {
|
|
return Filenames::in(P, I"Contents.w");
|
|
}
|
|
|
|
@ \section{Statistics.}
|
|
|
|
<<*>>=
|
|
int WebMetadata::chapter_count(web_md *Wm) {
|
|
int n = 0;
|
|
chapter_md *Cm;
|
|
LOOP_OVER_LINKED_LIST(Cm, chapter_md, Wm->chapters_md) n++;
|
|
return n;
|
|
}
|
|
int WebMetadata::section_count(web_md *Wm) {
|
|
int n = 0;
|
|
chapter_md *Cm;
|
|
LOOP_OVER_LINKED_LIST(Cm, chapter_md, Wm->chapters_md) {
|
|
section_md *Sm;
|
|
LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md) n++;
|
|
}
|
|
return n;
|
|
}
|