foundation-module: Chapter_8: Nowebify.
This commit is contained in:
parent
a15028316e
commit
dea875195b
5 changed files with 227 additions and 223 deletions
|
@ -3,11 +3,11 @@
|
|||
To manage key-value pairs of bibliographic data, metadata if you like,
|
||||
associated with a given web.
|
||||
|
||||
@h Storing data.
|
||||
@ \section{Storing data.}
|
||||
There are never more than a dozen or so key-value pairs, and it's more
|
||||
convenient to store them directly here than to use a dictionary.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct web_bibliographic_datum {
|
||||
struct text_stream *key;
|
||||
struct text_stream *value;
|
||||
|
@ -20,12 +20,13 @@ typedef struct web_bibliographic_datum {
|
|||
|
||||
@ We keep these in linked lists, and here's a convenient way to scan them:
|
||||
|
||||
@d LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm)
|
||||
<<*>>=
|
||||
#define LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm)
|
||||
LOOP_OVER_LINKED_LIST(bd, web_bibliographic_datum, Wm->bibliographic_data)
|
||||
|
||||
@ The following check the rules:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int Bibliographic::datum_can_be_declared(web_md *Wm, text_stream *key) {
|
||||
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
|
||||
if (bd == NULL) return FALSE;
|
||||
|
@ -38,10 +39,10 @@ int Bibliographic::datum_on_or_off(web_md *Wm, text_stream *key) {
|
|||
return bd->on_or_off;
|
||||
}
|
||||
|
||||
@h Initialising a web.
|
||||
@ \section{Initialising a web.}
|
||||
Each web has the following slate of data:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Bibliographic::initialise_data(web_md *Wm) {
|
||||
web_bibliographic_datum *bd;
|
||||
|
||||
|
@ -82,7 +83,7 @@ void Bibliographic::initialise_data(web_md *Wm) {
|
|||
@ Once the declarations for a web have been processed, the following is called
|
||||
to check that all the mandatory declarations have indeed been made:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void Bibliographic::check_required_data(web_md *Wm) {
|
||||
web_bibliographic_datum *bd;
|
||||
LOOP_OVER_BIBLIOGRAPHIC_DATA(bd, Wm)
|
||||
|
@ -92,10 +93,10 @@ void Bibliographic::check_required_data(web_md *Wm) {
|
|||
"The web does not specify '%S: ...'", bd->key);
|
||||
}
|
||||
|
||||
@h Reading bibliographic data.
|
||||
@ \section{Reading bibliographic data.}
|
||||
Key names are case-sensitive.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
text_stream *Bibliographic::get_datum(web_md *Wm, text_stream *key) {
|
||||
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
|
||||
if (bd) return bd->value;
|
||||
|
@ -118,20 +119,20 @@ web_bibliographic_datum *Bibliographic::look_up_datum(web_md *Wm, text_stream *k
|
|||
return NULL;
|
||||
}
|
||||
|
||||
@h Writing bibliographic data.
|
||||
@ \section{Writing bibliographic data.}
|
||||
Note that a key-value pair is created if the key doesn't exist at present,
|
||||
so this routine never fails.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
web_bibliographic_datum *Bibliographic::set_datum(web_md *Wm, text_stream *key, text_stream *val) {
|
||||
web_bibliographic_datum *bd = Bibliographic::look_up_datum(Wm, key);
|
||||
if (bd == NULL) @<Create a new datum, then@>
|
||||
if (bd == NULL) <<Create a new datum, then>>
|
||||
else Str::copy(bd->value, val);
|
||||
if (Str::eq_wide_string(key, L"Title")) @<Also set a capitalized form@>;
|
||||
if (Str::eq_wide_string(key, L"Title")) <<Also set a capitalized form>>;
|
||||
return bd;
|
||||
}
|
||||
|
||||
@<Create a new datum, then@> =
|
||||
<<Create a new datum, then>>=
|
||||
bd = CREATE(web_bibliographic_datum);
|
||||
bd->key = Str::duplicate(key);
|
||||
bd->value = Str::duplicate(val);
|
||||
|
@ -146,7 +147,7 @@ written to the "Title" key, then a full-caps "WUTHERING HEIGHTS" is
|
|||
written to a "Capitalized Title" key. (This enables cover sheets which
|
||||
want to typeset the title in full caps to do so.)
|
||||
|
||||
@<Also set a capitalized form@> =
|
||||
<<Also set a capitalized form>>=
|
||||
TEMPORARY_TEXT(recapped)
|
||||
Str::copy(recapped, val);
|
||||
LOOP_THROUGH_TEXT(P, recapped)
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
Manages the build metadata for an inweb project.
|
||||
|
||||
@h About build files.
|
||||
When we read a web, we look for a file in it called |build.txt|. If no such
|
||||
@ \section{About build files.}
|
||||
When we read a web, we look for a file in it called [[build.txt]]. If no such
|
||||
file exists, we look for the same thing in the current working directory.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
filename *BuildFiles::build_file_for_web(web_md *WS) {
|
||||
filename *F = Filenames::in(WS->path_to_web, I"build.txt");
|
||||
if (TextFiles::exists(F)) return F;
|
||||
|
@ -17,7 +17,7 @@ filename *BuildFiles::build_file_for_web(web_md *WS) {
|
|||
|
||||
@ The format of such a file is very simple: up to three text fields:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct build_file_data {
|
||||
struct text_stream *prerelease_text;
|
||||
struct text_stream *build_code;
|
||||
|
@ -26,7 +26,7 @@ typedef struct build_file_data {
|
|||
|
||||
@ Here's how to read in a build file:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
build_file_data BuildFiles::read(filename *F) {
|
||||
build_file_data bfd;
|
||||
bfd.prerelease_text = Str::new();
|
||||
|
@ -55,7 +55,7 @@ void BuildFiles::build_file_helper(text_stream *text, text_file_position *tfp, v
|
|||
|
||||
@ And here is how to write one:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void BuildFiles::write(build_file_data bfd, filename *F) {
|
||||
text_stream vr_stream;
|
||||
text_stream *OUT = &vr_stream;
|
||||
|
@ -69,11 +69,11 @@ void BuildFiles::write(build_file_data bfd, filename *F) {
|
|||
Streams::close(OUT);
|
||||
}
|
||||
|
||||
@h Bibliographic implications.
|
||||
@ \section{Bibliographic implications.}
|
||||
Whenever a web is read in by Inweb, its build file is looked at in order to
|
||||
set some bibliographic data.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void BuildFiles::set_bibliographic_data_for(web_md *WS) {
|
||||
filename *F = BuildFiles::build_file_for_web(WS);
|
||||
if (F) {
|
||||
|
@ -92,10 +92,10 @@ synthesize the semantic version number for the project. Note that this is
|
|||
called even if no build file had ever been found, so it's quite legal for
|
||||
the Contents page to specify all of this.
|
||||
|
||||
If no error occurs, then the expansion |[[Semantic Version Number]]| is
|
||||
If no error occurs, then the expansion [[[[Semantic Version Number]]]] is
|
||||
guaranteed to produce a semver-legal version number.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void BuildFiles::deduce_semver(web_md *WS) {
|
||||
TEMPORARY_TEXT(combined)
|
||||
text_stream *s = Bibliographic::get_datum(WS, I"Semantic Version Number");
|
||||
|
@ -121,11 +121,11 @@ void BuildFiles::deduce_semver(web_md *WS) {
|
|||
DISCARD_TEXT(combined)
|
||||
}
|
||||
|
||||
@h Advancing.
|
||||
@ \section{Advancing.}
|
||||
We update the build date to today and, if supplied, also increment the build
|
||||
number if we find that the date has changed.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void BuildFiles::advance_for_web(web_md *WS) {
|
||||
filename *F = BuildFiles::build_file_for_web(WS);
|
||||
if (F) BuildFiles::advance(F);
|
||||
|
@ -141,10 +141,10 @@ void BuildFiles::advance(filename *F) {
|
|||
}
|
||||
|
||||
@ The standard date format we use is "26 February 2018". If the contents of
|
||||
|dateline| match today's date in this format, we return |TRUE|; otherwise we
|
||||
rewrite |dateline| to today and return |FALSE|.
|
||||
[[dateline]] match today's date in this format, we return [[TRUE]]; otherwise we
|
||||
rewrite [[dateline]] to today and return [[FALSE]].
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int BuildFiles::dated_today(text_stream *dateline) {
|
||||
char *monthname[12] = { "January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December" };
|
||||
|
@ -161,16 +161,16 @@ int BuildFiles::dated_today(text_stream *dateline) {
|
|||
return rv;
|
||||
}
|
||||
|
||||
@ Traditional Inform build codes are four-character, e.g., |3Q27|. Here, we
|
||||
@ Traditional Inform build codes are four-character, e.g., [[3Q27]]. Here, we
|
||||
read such a code and increase it by one. The two-digit code at the back is
|
||||
incremented, but rolls around from |99| to |01|, in which case the letter is
|
||||
advanced, except that |I| and |O| are skipped, and if the letter passes |Z|
|
||||
then it rolls back around to |A| and the initial digit is incremented.
|
||||
incremented, but rolls around from [[99]] to [[01]], in which case the letter is
|
||||
advanced, except that [[I]] and [[O]] are skipped, and if the letter passes [[Z]]
|
||||
then it rolls back around to [[A]] and the initial digit is incremented.
|
||||
|
||||
This allows for 21384 distinct build codes, enough to use one each day for
|
||||
some 58 years.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void BuildFiles::increment(text_stream *T) {
|
||||
if (Str::len(T) != 4) Errors::with_text("build code malformed: %S", T);
|
||||
else {
|
||||
|
@ -178,8 +178,8 @@ void BuildFiles::increment(text_stream *T) {
|
|||
int L = Str::get_at(T, 1);
|
||||
int M1 = Str::get_at(T, 2) - '0';
|
||||
int M2 = Str::get_at(T, 3) - '0';
|
||||
if ((N < 0) || (N > 9) || (L < 'A') || (L > 'Z') ||
|
||||
(M1 < 0) || (M1 > 9) || (M2 < 0) || (M2 > 9)) {
|
||||
if ((N < 0) [[| (N > 9) || (L < 'A') || (L > 'Z') |]]
|
||||
(M1 < 0) [[| (M1 > 9) || (M2 < 0) |]] (M2 > 9)) {
|
||||
Errors::with_text("build code malformed: %S", T);
|
||||
} else {
|
||||
M2++;
|
|
@ -13,10 +13,10 @@ a program: something much simpler would surely be sufficient. And here it is.
|
|||
[1] Why might we have this? Because kits of Inter code take this form.
|
||||
|
||||
@ The simple tangler is controlled using a parcel of settings. Note also the
|
||||
|state|, which is not used by the reader itself, but instead allows the callback
|
||||
[[state]], which is not used by the reader itself, but instead allows the callback
|
||||
functions to have a shared state of their own.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct simple_tangle_docket {
|
||||
void (*raw_callback)(struct text_stream *, struct simple_tangle_docket *);
|
||||
void (*command_callback)(struct text_stream *, struct text_stream *,
|
||||
|
@ -27,7 +27,7 @@ typedef struct simple_tangle_docket {
|
|||
struct pathname *web_path;
|
||||
} simple_tangle_docket;
|
||||
|
||||
@ =
|
||||
<<*>>=
|
||||
simple_tangle_docket SimpleTangler::new_docket(
|
||||
void (*A)(struct text_stream *, struct simple_tangle_docket *),
|
||||
void (*B)(struct text_stream *, struct text_stream *,
|
||||
|
@ -49,7 +49,7 @@ simple_tangle_docket SimpleTangler::new_docket(
|
|||
should open), or a section (which the tangler should find and open), or a
|
||||
whole web of section files (ditto):
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void SimpleTangler::tangle_text(simple_tangle_docket *docket, text_stream *text) {
|
||||
SimpleTangler::tangle_L1(docket, text, NULL, NULL, FALSE);
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ void SimpleTangler::tangle_web(simple_tangle_docket *docket) {
|
|||
SimpleTangler::tangle_L1(docket, NULL, NULL, NULL, TRUE);
|
||||
}
|
||||
|
||||
@ =
|
||||
<<*>>=
|
||||
void SimpleTangler::tangle_L1(simple_tangle_docket *docket, text_stream *text,
|
||||
filename *F, text_stream *leafname, int whole_web) {
|
||||
TEMPORARY_TEXT(T)
|
||||
|
@ -77,7 +77,7 @@ void SimpleTangler::tangle_L1(simple_tangle_docket *docket, text_stream *text,
|
|||
|
||||
@ First, dispose of the "whole web" possibility.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void SimpleTangler::tangle_L2(OUTPUT_STREAM, text_stream *text, filename *F,
|
||||
text_stream *leafname, simple_tangle_docket *docket, int whole_web) {
|
||||
if (whole_web) {
|
||||
|
@ -95,26 +95,26 @@ void SimpleTangler::tangle_L2(OUTPUT_STREAM, text_stream *text, filename *F,
|
|||
}
|
||||
}
|
||||
|
||||
@ When tangling a file, we begin in |comment| mode; when tangling other matter,
|
||||
@ When tangling a file, we begin in [[comment]] mode; when tangling other matter,
|
||||
not so much.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void SimpleTangler::tangle_L3(OUTPUT_STREAM, text_stream *text,
|
||||
text_stream *leafname, simple_tangle_docket *docket, filename *F) {
|
||||
int comment = FALSE;
|
||||
FILE *Input_File = NULL;
|
||||
if ((Str::len(leafname) > 0) || (F)) {
|
||||
@<Open the file@>;
|
||||
<<Open the file>>;
|
||||
comment = TRUE;
|
||||
}
|
||||
@<Tangle the material@>;
|
||||
<<Tangle the material>>;
|
||||
if (Input_File) fclose(Input_File);
|
||||
}
|
||||
|
||||
@ Note that if we are looking for an explicit section -- say, |Juggling.i6t| --
|
||||
from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|.
|
||||
@ Note that if we are looking for an explicit section -- say, [[Juggling.i6t]] --
|
||||
from a web [[W]], we translate that into the path [[W/Sections/Juggling.i6t]].
|
||||
|
||||
@<Open the file@> =
|
||||
<<Open the file>>=
|
||||
if (F) {
|
||||
Input_File = Filenames::fopen(F, "r");
|
||||
} else if (Str::len(leafname) > 0) {
|
||||
|
@ -124,7 +124,7 @@ from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|.
|
|||
if (Input_File == NULL)
|
||||
(*(docket->error_callback))("unable to open the file '%S'", leafname);
|
||||
|
||||
@<Tangle the material@> =
|
||||
<<Tangle the material>>=
|
||||
TEMPORARY_TEXT(command)
|
||||
TEMPORARY_TEXT(argument)
|
||||
int skip_part = FALSE, extract = FALSE;
|
||||
|
@ -132,50 +132,51 @@ from a web |W|, we translate that into the path |W/Sections/Juggling.i6t|.
|
|||
do {
|
||||
Str::clear(command);
|
||||
Str::clear(argument);
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
NewCharacter: if (cr == EOF) break;
|
||||
if (((cr == '@') || (cr == '=')) && (col == 1)) {
|
||||
int inweb_syntax = -1;
|
||||
if (cr == '=') @<Read the rest of line as an equals-heading@>
|
||||
else @<Read the rest of line as an at-heading@>;
|
||||
@<Act on the heading, going in or out of comment mode as appropriate@>;
|
||||
if (cr == '=') <<Read the rest of line as an equals-heading>>
|
||||
else <<Read the rest of line as an at-heading>>;
|
||||
<<Act on the heading, going in or out of comment mode as appropriate>>;
|
||||
continue;
|
||||
}
|
||||
if (comment == FALSE) @<Deal with material which isn't commentary@>;
|
||||
if (comment == FALSE) <<Deal with material which isn't commentary>>;
|
||||
} while (cr != EOF);
|
||||
DISCARD_TEXT(command)
|
||||
DISCARD_TEXT(argument)
|
||||
|
||||
@ Our text files are encoded as ISO Latin-1, not as Unicode UTF-8, so ordinary
|
||||
|fgetc| is used, and no BOM marker is parsed. Lines are assumed to be terminated
|
||||
with either |0x0a| or |0x0d|. (Since blank lines are harmless, we take no
|
||||
trouble over |0a0d| or |0d0a| combinations.) The built-in template files, almost
|
||||
always the only ones used, are line terminated |0x0a| in Unix fashion.
|
||||
[[fgetc]] is used, and no BOM marker is parsed. Lines are assumed to be terminated
|
||||
with either [[0x0a]] or [[0x0d]]. (Since blank lines are harmless, we take no
|
||||
trouble over [[0a0d]] or [[0d0a]] combinations.) The built-in template files, almost
|
||||
always the only ones used, are line terminated [[0x0a]] in Unix fashion.
|
||||
|
||||
@<Read next character@> =
|
||||
<<Read next character>>=
|
||||
if (Input_File) cr = fgetc(Input_File);
|
||||
else if (text) {
|
||||
cr = Str::get_at(text, sfp); if (cr == 0) cr = EOF; else sfp++;
|
||||
} else cr = EOF;
|
||||
col++; if ((cr == 10) || (cr == 13)) col = 0;
|
||||
|
||||
@ Here we see the limited range of Inweb syntaxes allowed; but some |@| and |=|
|
||||
@ Here we see the limited range of Inweb syntaxes allowed; but some [[@]] and [[=]]
|
||||
commands can be used, at least.
|
||||
|
||||
@d INWEB_PARAGRAPH_SYNTAX 1
|
||||
@d INWEB_CODE_SYNTAX 2
|
||||
@d INWEB_DASH_SYNTAX 3
|
||||
@d INWEB_PURPOSE_SYNTAX 4
|
||||
@d INWEB_FIGURE_SYNTAX 5
|
||||
@d INWEB_EQUALS_SYNTAX 6
|
||||
@d INWEB_EXTRACT_SYNTAX 7
|
||||
<<*>>=
|
||||
#define INWEB_PARAGRAPH_SYNTAX 1
|
||||
#define INWEB_CODE_SYNTAX 2
|
||||
#define INWEB_DASH_SYNTAX 3
|
||||
#define INWEB_PURPOSE_SYNTAX 4
|
||||
#define INWEB_FIGURE_SYNTAX 5
|
||||
#define INWEB_EQUALS_SYNTAX 6
|
||||
#define INWEB_EXTRACT_SYNTAX 7
|
||||
|
||||
@<Read the rest of line as an at-heading@> =
|
||||
<<Read the rest of line as an at-heading>>=
|
||||
TEMPORARY_TEXT(at_cmd)
|
||||
int committed = FALSE, unacceptable_character = FALSE;
|
||||
while (TRUE) {
|
||||
@<Read next character@>;
|
||||
if ((committed == FALSE) && ((cr == 10) || (cr == 13) || (cr == ' '))) {
|
||||
<<Read next character>>;
|
||||
if ((committed == FALSE) && ((cr == 10) [[| (cr == 13) |]] (cr == ' '))) {
|
||||
if (Str::eq_wide_string(at_cmd, L"p"))
|
||||
inweb_syntax = INWEB_PARAGRAPH_SYNTAX;
|
||||
else if (Str::eq_wide_string(at_cmd, L"h"))
|
||||
|
@ -202,7 +203,7 @@ commands can be used, at least.
|
|||
}
|
||||
if (!(((cr >= 'A') && (cr <= 'Z')) || ((cr >= 'a') && (cr <= 'z'))
|
||||
|| ((cr >= '0') && (cr <= '9'))
|
||||
|| (cr == '-') || (cr == '>') || (cr == ':') || (cr == '_')))
|
||||
[[| (cr == '-') || (cr == '>') || (cr == ':') |]] (cr == '_')))
|
||||
unacceptable_character = TRUE;
|
||||
if ((cr == 10) || (cr == 13)) break;
|
||||
PUT_TO(at_cmd, cr);
|
||||
|
@ -210,10 +211,10 @@ commands can be used, at least.
|
|||
Str::copy(command, at_cmd);
|
||||
DISCARD_TEXT(at_cmd)
|
||||
|
||||
@<Read the rest of line as an equals-heading@> =
|
||||
<<Read the rest of line as an equals-heading>>=
|
||||
TEMPORARY_TEXT(equals_cmd)
|
||||
while (TRUE) {
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
if ((cr == 10) || (cr == 13)) break;
|
||||
PUT_TO(equals_cmd, cr);
|
||||
}
|
||||
|
@ -231,14 +232,14 @@ commands can be used, at least.
|
|||
Regexp::dispose_of(&mr);
|
||||
DISCARD_TEXT(equals_cmd)
|
||||
|
||||
@<Act on the heading, going in or out of comment mode as appropriate@> =
|
||||
<<Act on the heading, going in or out of comment mode as appropriate>>=
|
||||
switch (inweb_syntax) {
|
||||
case INWEB_PARAGRAPH_SYNTAX: {
|
||||
TEMPORARY_TEXT(heading_name)
|
||||
Str::copy_tail(heading_name, command, 2);
|
||||
int c;
|
||||
while (((c = Str::get_last_char(heading_name)) != 0) &&
|
||||
((c == ' ') || (c == '\t') || (c == '.')))
|
||||
((c == ' ') [[| (c == '\t') |]] (c == '.')))
|
||||
Str::delete_last_character(heading_name);
|
||||
if (Str::len(heading_name) == 0)
|
||||
(*(docket->error_callback))("Empty heading name", NULL);
|
||||
|
@ -266,11 +267,11 @@ commands can be used, at least.
|
|||
case INWEB_FIGURE_SYNTAX: break;
|
||||
}
|
||||
|
||||
@<Deal with material which isn't commentary@> =
|
||||
<<Deal with material which isn't commentary>>=
|
||||
if (cr == '{') {
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
if ((cr == '-') && (docket->command_callback)) {
|
||||
@<Read up to the next close brace as a braced command and argument@>;
|
||||
<<Read up to the next close brace as a braced command and argument>>;
|
||||
if (Str::get_first_char(command) == '!') continue;
|
||||
(*(docket->command_callback))(OUT, command, argument, docket);
|
||||
continue;
|
||||
|
@ -280,9 +281,9 @@ commands can be used, at least.
|
|||
}
|
||||
}
|
||||
if ((cr == '(') && (docket->bplus_callback)) {
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
if (cr == '+') {
|
||||
@<Read up to the next plus close-bracket as an I7 expression@>;
|
||||
<<Read up to the next plus close-bracket as an I7 expression>>;
|
||||
continue;
|
||||
} else { /* otherwise the open bracket was a literal */
|
||||
PUT_TO(OUT, '(');
|
||||
|
@ -291,29 +292,29 @@ commands can be used, at least.
|
|||
}
|
||||
PUT_TO(OUT, cr);
|
||||
|
||||
@ And here we read a normal command. The command name must not include |}|
|
||||
or |:|. If there is no |:| then the argument is left unset (so that it will
|
||||
be the empty string: see above). The argument must not include |}|.
|
||||
@ And here we read a normal command. The command name must not include [[}]]
|
||||
or [[:]]. If there is no [[:]] then the argument is left unset (so that it will
|
||||
be the empty string: see above). The argument must not include [[}]].
|
||||
|
||||
@<Read up to the next close brace as a braced command and argument@> =
|
||||
<<Read up to the next close brace as a braced command and argument>>=
|
||||
Str::clear(command);
|
||||
Str::clear(argument);
|
||||
int com_mode = TRUE;
|
||||
while (TRUE) {
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
if ((cr == '}') || (cr == EOF)) break;
|
||||
if ((cr == ':') && (com_mode)) { com_mode = FALSE; continue; }
|
||||
if (com_mode) PUT_TO(command, cr);
|
||||
else PUT_TO(argument, cr);
|
||||
}
|
||||
|
||||
@ And similarly, for the |(+| ... |+)| notation which was once used to mark
|
||||
@ And similarly, for the [[(+| ... |+)]] notation which was once used to mark
|
||||
I7 material within I6:
|
||||
|
||||
@<Read up to the next plus close-bracket as an I7 expression@> =
|
||||
<<Read up to the next plus close-bracket as an I7 expression>>=
|
||||
TEMPORARY_TEXT(material)
|
||||
while (TRUE) {
|
||||
@<Read next character@>;
|
||||
<<Read next character>>;
|
||||
if (cr == EOF) break;
|
||||
if ((cr == ')') && (Str::get_last_char(material) == '+')) {
|
||||
Str::delete_last_character(material); break; }
|
|
@ -2,32 +2,33 @@
|
|||
|
||||
To search for included modules, and track dependencies between them.
|
||||
|
||||
@h Creation.
|
||||
@ \section{Creation.}
|
||||
Each web of source material discovered by Inweb is given one of the following.
|
||||
Ordinarily these are found only when reading in a web for weaving, tangling
|
||||
and so on: in the vast majority of Inweb runs, all modules will have the
|
||||
"module origin marker" |READING_WEB_MOM|. But when Inweb is constructing a
|
||||
"module origin marker" [[READING_WEB_MOM]]. But when Inweb is constructing a
|
||||
makefile for a suite of tools, it can also discover multiple webs by other
|
||||
means.
|
||||
|
||||
@e READING_WEB_MOM from 0
|
||||
@e MAKEFILE_TOOL_MOM
|
||||
@e MAKEFILE_WEB_MOM
|
||||
@e MAKEFILE_MODULE_MOM
|
||||
<<*>>=
|
||||
enum READING_WEB_MOM from 0
|
||||
enum MAKEFILE_TOOL_MOM
|
||||
enum MAKEFILE_WEB_MOM
|
||||
enum MAKEFILE_MODULE_MOM
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct module {
|
||||
struct pathname *module_location;
|
||||
struct text_stream *module_name;
|
||||
struct linked_list *dependencies; /* of |module|: which other modules does this need? */
|
||||
struct linked_list *dependencies; /* of [[module]]: which other modules does this need? */
|
||||
struct text_stream *module_tag;
|
||||
int origin_marker; /* one of the |*_MOM| values above */
|
||||
struct linked_list *chapters_md; /* of |chapter_md|: just the ones in this module */
|
||||
struct linked_list *sections_md; /* of |section_md|: just the ones in this module */
|
||||
int origin_marker; /* one of the [[*_MOM]] values above */
|
||||
struct linked_list *chapters_md; /* of [[chapter_md]]: just the ones in this module */
|
||||
struct linked_list *sections_md; /* of [[section_md]]: just the ones in this module */
|
||||
CLASS_DEFINITION
|
||||
} module;
|
||||
|
||||
@ =
|
||||
<<*>>=
|
||||
module *WebModules::new(text_stream *name, pathname *at, int m) {
|
||||
module *M = CREATE(module);
|
||||
M->module_location = at;
|
||||
|
@ -44,48 +45,48 @@ module *WebModules::new(text_stream *name, pathname *at, int m) {
|
|||
contains a suite of utility routines, or a major component of a program, but
|
||||
which is not a program in its own right.
|
||||
|
||||
Internally, though, every web produces a |module| structure. The one for the
|
||||
Internally, though, every web produces a [[module]] structure. The one for the
|
||||
main web -- which can be tangled, and results in an actual program -- is
|
||||
internally named |"(main)"|, a name which the user will never see.
|
||||
internally named [["(main)"]], a name which the user will never see.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
module *WebModules::create_main_module(web_md *WS) {
|
||||
return WebModules::new(I"(main)", WS->path_to_web, READING_WEB_MOM);
|
||||
}
|
||||
|
||||
@h Dependencies.
|
||||
@ \section{Dependencies.}
|
||||
When web A imports module B, we will say that A is dependent on B. A web
|
||||
can import multiple modules, so there can a list of dependencies. These are
|
||||
needed when constructing makefiles, since the source code in B affects the
|
||||
program generated by A.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
void WebModules::dependency(module *A, module *B) {
|
||||
if ((A == NULL) || (B == NULL)) internal_error("no module");
|
||||
ADD_TO_LINKED_LIST(B, module, A->dependencies);
|
||||
}
|
||||
|
||||
@h Searching.
|
||||
@ \section{Searching.}
|
||||
The following abstracts the idea of a place where modules might be found.
|
||||
(At one time there was going to be a more elaborate search hierarchy.)
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
typedef struct module_search {
|
||||
struct pathname *path_to_search;
|
||||
CLASS_DEFINITION
|
||||
} module_search;
|
||||
|
||||
@ =
|
||||
<<*>>=
|
||||
module_search *WebModules::make_search_path(pathname *ext_path) {
|
||||
module_search *ms = CREATE(module_search);
|
||||
ms->path_to_search = ext_path;
|
||||
return ms;
|
||||
}
|
||||
|
||||
@ When a web's contents page says to |import Blah|, how do we find the module
|
||||
called |Blah| on disc? We try four possibilities in sequence:
|
||||
@ When a web's contents page says to [[import Blah]], how do we find the module
|
||||
called [[Blah]] on disc? We try four possibilities in sequence:
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
module *WebModules::find(web_md *WS, module_search *ms, text_stream *name, pathname *X) {
|
||||
TEMPORARY_TEXT(T)
|
||||
WRITE_TO(T, "%S-module", name);
|
||||
|
@ -97,16 +98,16 @@ module *WebModules::find(web_md *WS, module_search *ms, text_stream *name, pathn
|
|||
int N = 4;
|
||||
for (int i=0; i<N; i++) {
|
||||
pathname *P = Pathnames::from_text_relative(tries[i], T);
|
||||
if ((P) && (WebModules::exists(P))) @<Accept this directory as the module@>;
|
||||
if ((P) && (WebModules::exists(P))) <<Accept this directory as the module>>;
|
||||
}
|
||||
DISCARD_TEXT(T)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ When the module is found (if it is), a suitable module structure is made,
|
||||
and a dependency created from the web's |(main)| module to this one.
|
||||
and a dependency created from the web's [[(main)]] module to this one.
|
||||
|
||||
@<Accept this directory as the module@> =
|
||||
<<Accept this directory as the module>>=
|
||||
pathname *Q = Pathnames::from_text(name);
|
||||
module *M = WebModules::new(Pathnames::directory_name(Q), P, READING_WEB_MOM);
|
||||
WebModules::dependency(WS->as_module, M);
|
||||
|
@ -115,30 +116,30 @@ and a dependency created from the web's |(main)| module to this one.
|
|||
@ We accept that a plausibly-named directory is indeed the module being
|
||||
sought if it looks like a web.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int WebModules::exists(pathname *P) {
|
||||
return WebMetadata::directory_looks_like_a_web(P);
|
||||
}
|
||||
|
||||
@h Resolving cross-reference names.
|
||||
Suppose we are in module |from_M| and want to understand which section of
|
||||
a relevant web |text| might refer to. It could be the name of a module,
|
||||
@ \section{Resolving cross-reference names.}
|
||||
Suppose we are in module [[from_M]] and want to understand which section of
|
||||
a relevant web [[text]] might refer to. It could be the name of a module,
|
||||
either this one or one dependent on it; or the name of a chapter in one
|
||||
of those, or the shortened forms of those; or the name of a section. It
|
||||
may match multiple possibilities: we return how many, and if this is
|
||||
positive, we write the module in which the first find was made in |*return M|,
|
||||
the section in |*return_Sm|, and set the flag |*named_as_module| according
|
||||
positive, we write the module in which the first find was made in [[*return M]],
|
||||
the section in [[*return_Sm]], and set the flag [[*named_as_module]] according
|
||||
to whether the reference was a bare module name (say, "foundation") or not.
|
||||
|
||||
Note that we consider first the possibilities within |from_M|: we only
|
||||
Note that we consider first the possibilities within [[from_M]]: we only
|
||||
look at other modules if there are none. Thus, an unambiguous result in
|
||||
|from_M| is good enough, even if there are other possibilities elsewhere.
|
||||
[[from_M]] is good enough, even if there are other possibilities elsewhere.
|
||||
|
||||
A reference in the form |module: reference| is taken to be in the module
|
||||
of that name: for example, |"foundation: Web Modules"| would find the
|
||||
A reference in the form [[module: reference]] is taken to be in the module
|
||||
of that name: for example, [["foundation: Web Modules"]] would find the
|
||||
section of code you are now reading.
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
||||
int *named_as_module, text_stream *title, module *from_M, text_stream *text,
|
||||
int list, int sections_only) {
|
||||
|
@ -156,7 +157,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
|||
}
|
||||
LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies) {
|
||||
if (Str::eq_insensitive(M->module_name, seek_module)) {
|
||||
@<Look for references to chapters or sections in M@>;
|
||||
<<Look for references to chapters or sections in M>>;
|
||||
}
|
||||
}
|
||||
Regexp::dispose_of(&mr);
|
||||
|
@ -164,20 +165,20 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
|||
for (int stage = 1; ((finds == 0) && (stage <= 2)); stage++) {
|
||||
if (stage == 1) {
|
||||
M = from_M;
|
||||
@<Look for references to chapters or sections in M@>;
|
||||
<<Look for references to chapters or sections in M>>;
|
||||
}
|
||||
if (stage == 2) {
|
||||
LOOP_OVER_LINKED_LIST(M, module, from_M->dependencies)
|
||||
@<Look for references to chapters or sections in M@>;
|
||||
<<Look for references to chapters or sections in M>>;
|
||||
}
|
||||
}
|
||||
return finds;
|
||||
}
|
||||
|
||||
@<Look for references to chapters or sections in M@> =
|
||||
<<Look for references to chapters or sections in M>>=
|
||||
if (M == NULL) internal_error("no module");
|
||||
if (Str::eq_insensitive(M->module_name, seek))
|
||||
@<Found first section in module@>;
|
||||
<<Found first section in module>>;
|
||||
chapter_md *Cm;
|
||||
section_md *Sm;
|
||||
LOOP_OVER_LINKED_LIST(Cm, chapter_md, M->chapters_md) {
|
||||
|
@ -185,13 +186,13 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
|||
((Str::eq_insensitive(Cm->ch_title, seek)) ||
|
||||
(Str::eq_insensitive(Cm->ch_basic_title, seek)) ||
|
||||
(Str::eq_insensitive(Cm->ch_decorated_title, seek))))
|
||||
@<Found first section in chapter@>;
|
||||
<<Found first section in chapter>>;
|
||||
LOOP_OVER_LINKED_LIST(Sm, section_md, Cm->sections_md)
|
||||
if (Str::eq_insensitive(Sm->sect_title, seek))
|
||||
@<Found section by name@>;
|
||||
<<Found section by name>>;
|
||||
}
|
||||
|
||||
@<Found first section in module@> =
|
||||
<<Found first section in module>>=
|
||||
finds++;
|
||||
if (finds == 1) {
|
||||
*return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, M->sections_md);
|
||||
|
@ -200,7 +201,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
|||
}
|
||||
if (list) WRITE_TO(STDERR, "(%d) Module '%S'\n", finds, M->module_name);
|
||||
|
||||
@<Found first section in chapter@> =
|
||||
<<Found first section in chapter>>=
|
||||
finds++;
|
||||
if (finds == 1) {
|
||||
*return_M = M; *return_Sm = FIRST_IN_LINKED_LIST(section_md, Cm->sections_md);
|
||||
|
@ -209,7 +210,7 @@ int WebModules::named_reference(module **return_M, section_md **return_Sm,
|
|||
if (list) WRITE_TO(STDERR, "(%d) Chapter '%S' of module '%S'\n",
|
||||
finds, Cm->ch_title, M->module_name);
|
||||
|
||||
@<Found section by name@> =
|
||||
<<Found section by name>>=
|
||||
finds++;
|
||||
if (finds == 1) {
|
||||
*return_M = M; *return_Sm = Sm;
|
|
@ -3,7 +3,7 @@
|
|||
To read the structure of a literate programming web from a path in the file
|
||||
system.
|
||||
|
||||
@h Introduction.
|
||||
@ \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
|
||||
|
@ -13,21 +13,22 @@ 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.
|
||||
|
||||
@e V1_SYNTAX from 1
|
||||
@e V2_SYNTAX
|
||||
<<*>>=
|
||||
enum V1_SYNTAX from 1
|
||||
enum V2_SYNTAX
|
||||
|
||||
@h Web MD.
|
||||
@ \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|:
|
||||
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 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? */
|
||||
|
@ -35,20 +36,20 @@ typedef struct web_md {
|
|||
|
||||
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 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| */
|
||||
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:
|
||||
@ 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_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" */
|
||||
|
@ -58,13 +59,13 @@ typedef struct chapter_md {
|
|||
|
||||
int imported; /* from a different web? */
|
||||
|
||||
struct linked_list *sections_md; /* of |section_md| */
|
||||
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:
|
||||
@ 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" */
|
||||
|
@ -82,13 +83,13 @@ typedef struct section_md {
|
|||
CLASS_DEFINITION
|
||||
} section_md;
|
||||
|
||||
@h Reading from the file system.
|
||||
@ \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 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.
|
||||
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);
|
||||
}
|
||||
|
@ -97,20 +98,20 @@ 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@>;
|
||||
<<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@>;
|
||||
<<Consolidate the bibliographic data>>;
|
||||
<<Work out the section ranges>>;
|
||||
return Wm;
|
||||
}
|
||||
|
||||
@<Begin the bibliographic data@> =
|
||||
<<Begin the bibliographic data>>=
|
||||
Wm->bibliographic_data = NEW_LINKED_LIST(web_bibliographic_datum);
|
||||
Bibliographic::initialise_data(Wm);
|
||||
|
||||
@<Initialise the rest of the web MD@> =
|
||||
<<Initialise the rest of the web MD>>=
|
||||
if (P) {
|
||||
Wm->path_to_web = P;
|
||||
Wm->single_file = NULL;
|
||||
|
@ -130,14 +131,14 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
|
|||
Wm->header_filenames = NEW_LINKED_LIST(filename);
|
||||
Wm->as_module = WebModules::create_main_module(Wm);
|
||||
|
||||
@<Consolidate the bibliographic data@> =
|
||||
<<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@> =
|
||||
<<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;
|
||||
|
@ -147,12 +148,12 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
|
|||
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@>;
|
||||
<<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@> =
|
||||
<<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);
|
||||
|
@ -162,17 +163,17 @@ web_md *WebMetadata::get(pathname *P, filename *alt_F, int syntax_version,
|
|||
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@>;
|
||||
<<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@>;
|
||||
<<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@> =
|
||||
<<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;
|
||||
|
@ -197,7 +198,7 @@ would be "elctrcty", since we don't count "y" as a vowel here.
|
|||
|
||||
@ We never want two sections to have the same range.
|
||||
|
||||
@<Terminate with disambiguating numbers in case of collisions@> =
|
||||
<<Terminate with disambiguating numbers in case of collisions>>=
|
||||
TEMPORARY_TEXT(original_range)
|
||||
Str::copy(original_range, Sm->sect_range);
|
||||
int disnum = 0, collision = FALSE;
|
||||
|
@ -224,10 +225,10 @@ would be "elctrcty", since we don't count "y" as a vowel here.
|
|||
} while (collision);
|
||||
DISCARD_TEXT(original_range)
|
||||
|
||||
@h Reading the contents page.
|
||||
@ \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,
|
||||
[[.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".
|
||||
|
@ -242,7 +243,7 @@ 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;
|
||||
|
@ -268,7 +269,7 @@ 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@>;
|
||||
<<Initialise the reader state>>;
|
||||
|
||||
int cl = TextFiles::read(RS.contents_filename, FALSE, "can't open contents file",
|
||||
TRUE, WebMetadata::read_contents_line, NULL, &RS);
|
||||
|
@ -282,7 +283,7 @@ void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
|
|||
if (RS.section_count == 1) RS.last_section->is_a_singleton = TRUE;
|
||||
}
|
||||
|
||||
@<Initialise the reader state@> =
|
||||
<<Initialise the reader state>>=
|
||||
RS.Wm = Wm;
|
||||
RS.reading_from = of_module;
|
||||
RS.in_biblio = TRUE;
|
||||
|
@ -318,7 +319,7 @@ void WebMetadata::read_contents_page(web_md *Wm, module *of_module,
|
|||
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;
|
||||
|
@ -328,40 +329,40 @@ void WebMetadata::read_contents_line(text_stream *line, text_file_position *tfp,
|
|||
begins_with_white_space = TRUE;
|
||||
Str::trim_white_space(line);
|
||||
|
||||
@<Act immediately if the web syntax version is changed@>;
|
||||
<<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@>;
|
||||
<<Halt at this point in the single file, and make the rest of it a one-chapter section>>;
|
||||
|
||||
@<Read regular contents material@>;
|
||||
<<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@> =
|
||||
<<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.
|
||||
@ 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@> =
|
||||
<<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@>;
|
||||
<<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@>;
|
||||
<<Read about, and read in, a new section>>;
|
||||
return;
|
||||
|
||||
@ With those two complications out of the way, we now know that we're reading
|
||||
|
@ -369,21 +370,21 @@ 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@>;
|
||||
<<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@> =
|
||||
<<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@> =
|
||||
<<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+?) *")) {
|
||||
|
@ -391,7 +392,7 @@ variables with fixed names; a blank line ends the block.
|
|||
Str::copy(key, mr.exp[0]);
|
||||
TEMPORARY_TEXT(value)
|
||||
Str::copy(value, mr.exp[1]);
|
||||
@<Set bibliographic key-value pair@>;
|
||||
<<Set bibliographic key-value pair>>;
|
||||
DISCARD_TEXT(key)
|
||||
DISCARD_TEXT(value)
|
||||
} else {
|
||||
|
@ -403,7 +404,7 @@ variables with fixed names; a blank line ends the block.
|
|||
Regexp::dispose_of(&mr);
|
||||
}
|
||||
|
||||
@<Set bibliographic key-value pair@> =
|
||||
<<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"))) {
|
||||
|
@ -426,20 +427,20 @@ variables with fixed names; a blank line ends the block.
|
|||
@ 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@> =
|
||||
<<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@>;
|
||||
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@> =
|
||||
<<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;
|
||||
}
|
||||
|
@ -451,7 +452,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
|
|||
|
||||
@ The title tells us everything we need to know about a chapter:
|
||||
|
||||
@<Read about a new 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;
|
||||
|
@ -460,7 +461,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
|
|||
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@>;
|
||||
<<Mark this chapter as an independent tangle target>>;
|
||||
Str::copy(line, title_alone);
|
||||
}
|
||||
int this_is_a_chapter = TRUE;
|
||||
|
@ -540,7 +541,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
|
|||
DISCARD_TEXT(err)
|
||||
}
|
||||
|
||||
if (this_is_a_chapter) @<Create the new chapter with these details@>;
|
||||
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);
|
||||
|
@ -548,7 +549,7 @@ we like a spoonful of syntactic sugar on our porridge, that's why.
|
|||
@ 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@> =
|
||||
<<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");
|
||||
|
@ -556,7 +557,7 @@ with the same language as the main web unless stated otherwise.
|
|||
language_name = mr.exp[0];
|
||||
Regexp::dispose_of(&mr);
|
||||
|
||||
@<Create the new chapter with these details@> =
|
||||
<<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");
|
||||
|
@ -584,16 +585,16 @@ with the same language as the main web unless stated otherwise.
|
|||
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@> =
|
||||
<<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@>;
|
||||
<<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@>;
|
||||
<<Work out the filename of this section file>>;
|
||||
|
||||
@<Initialise the section structure@> =
|
||||
<<Initialise the section structure>>=
|
||||
Sm->source_file_for_section = filename_of_single_file_web;
|
||||
Sm->using_syntax = syntax;
|
||||
Sm->is_a_singleton = FALSE;
|
||||
|
@ -612,7 +613,7 @@ we also read in and process its file.
|
|||
Regexp::dispose_of(&mr);
|
||||
Sm->owning_module = RS->reading_from;
|
||||
|
||||
@<Add the section to the web and the current chapter@> =
|
||||
<<Add the section to the web and the current chapter>>=
|
||||
chapter_md *Cm = RS->chapter_being_scanned;
|
||||
RS->section_count++;
|
||||
RS->last_section = Sm;
|
||||
|
@ -620,28 +621,28 @@ we also read in and process its file.
|
|||
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@> =
|
||||
<<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@>;
|
||||
<<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@> =
|
||||
<<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.
|
||||
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@> =
|
||||
<<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;
|
||||
|
@ -656,9 +657,9 @@ the extension needs to be |.i6t|. We allow either.
|
|||
}
|
||||
DISCARD_TEXT(leafname_to_use)
|
||||
|
||||
@h Relative pathnames or filenames.
|
||||
@ \section{Relative pathnames or filenames.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int WebMetadata::directory_looks_like_a_web(pathname *P) {
|
||||
return TextFiles::exists(WebMetadata::contents_filename(P));
|
||||
}
|
||||
|
@ -667,9 +668,9 @@ filename *WebMetadata::contents_filename(pathname *P) {
|
|||
return Filenames::in(P, I"Contents.w");
|
||||
}
|
||||
|
||||
@h Statistics.
|
||||
@ \section{Statistics.}
|
||||
|
||||
=
|
||||
<<*>>=
|
||||
int WebMetadata::chapter_count(web_md *Wm) {
|
||||
int n = 0;
|
||||
chapter_md *Cm;
|
Loading…
Reference in a new issue