inweb-bootstrap/Chapter_1/Patterns.nw

287 lines
11 KiB
Text

[Patterns::] Patterns.
Managing weave patterns, which are bundled configuration settings for weaving.
@ \section{Reading in.}
Patterns are stored as directories in the file system, and are identified by
names such as [[HTML]]. On request, we need to find the directory corresponding
to such a name, and to read it in. This structure holds the result:
<<*>>=
typedef struct weave_pattern {
struct text_stream *pattern_name; /* such as [[HTML]] */
struct pathname *pattern_location; /* the directory */
struct weave_pattern *based_on; /* inherit from which other pattern? */
struct weave_format *pattern_format; /* such as [[DVI]]: the desired final format */
struct linked_list *plugins; /* of [[weave_plugin]]: any extras needed */
struct linked_list *colour_schemes; /* of [[colour_scheme]]: any extras needed */
struct text_stream *mathematics_plugin; /* name only, not a [[weave_pattern *]] */
struct text_stream *footnotes_plugin; /* name only, not a [[weave_pattern *]] */
struct text_stream *initial_extension; /* filename extension, that is */
struct linked_list *post_commands; /* of [[text_stream]] */
struct linked_list *blocked_templates; /* of [[text_stream]] */
struct linked_list *asset_rules; /* of [[asset_rule]] */
int show_abbrevs; /* show section range abbreviations in the weave? */
int number_sections; /* insert section numbers into the weave? */
struct text_stream *default_range; /* for example, [[sections]] */
struct web *patterned_for; /* the web which caused this to be read in */
int commands;
int name_command_given;
CLASS_DEFINITION
} weave_pattern;
@ When a given web needs a pattern with a given name, this is where it comes.
<<*>>=
weave_pattern *Patterns::find(web *W, text_stream *name) {
filename *pattern_file = NULL;
weave_pattern *wp = CREATE(weave_pattern);
<<Initialise the pattern structure>>;
<<Locate the pattern directory>>;
<<Read in the pattern.txt file>>;
return wp;
}
<<Initialise the pattern structure>>=
wp->pattern_name = Str::duplicate(name);
wp->pattern_location = NULL;
wp->plugins = NEW_LINKED_LIST(weave_plugin);
wp->colour_schemes = NEW_LINKED_LIST(colour_scheme);
wp->based_on = NULL;
wp->asset_rules = Assets::new_asset_rules_list();
wp->patterned_for = W;
wp->number_sections = FALSE;
wp->footnotes_plugin = NULL;
wp->mathematics_plugin = NULL;
wp->default_range = Str::duplicate(I"0");
wp->initial_extension = NULL;
wp->post_commands = NEW_LINKED_LIST(text_stream);
wp->blocked_templates = NEW_LINKED_LIST(text_stream);
wp->commands = 0;
wp->name_command_given = FALSE;
<<Locate the pattern directory>>=
wp->pattern_location = NULL;
pathname *CP = Colonies::patterns_path();
if (CP) {
wp->pattern_location = Pathnames::down(CP, name);
pattern_file = Filenames::in(wp->pattern_location, I"pattern.txt");
if (TextFiles::exists(pattern_file) == FALSE) wp->pattern_location = NULL;
}
if (wp->pattern_location == NULL) {
wp->pattern_location = Pathnames::down(
Pathnames::down(W->md->path_to_web, I"Patterns"), name);
pattern_file = Filenames::in(wp->pattern_location, I"pattern.txt");
if (TextFiles::exists(pattern_file) == FALSE) wp->pattern_location = NULL;
}
if (wp->pattern_location == NULL) {
wp->pattern_location = Pathnames::down(
path_to_inweb_patterns, name);
pattern_file = Filenames::in(wp->pattern_location, I"pattern.txt");
if (TextFiles::exists(pattern_file) == FALSE) wp->pattern_location = NULL;
}
if (wp->pattern_location == NULL)
Errors::fatal_with_text("no such weave pattern as '%S'", name);
<<Read in the pattern.txt file>>=
if (pattern_file)
TextFiles::read(pattern_file, FALSE, "can't open pattern.txt file",
TRUE, Patterns::scan_pattern_line, NULL, wp);
if (wp->pattern_format == NULL)
Errors::fatal_with_text("pattern did not specify a format", name);
if (wp->name_command_given == FALSE)
Errors::fatal_with_text("pattern did not name itself at the top", name);
@ The Foundation module provides a standard way to scan text files line by
line, and this is used to send each line in the [[pattern.txt]] file to the
following routine:
<<*>>=
void Patterns::scan_pattern_line(text_stream *line, text_file_position *tfp, void *X) {
weave_pattern *wp = (weave_pattern *) X;
Str::trim_white_space(line); /* ignore trailing space */
if (Str::len(line) == 0) return; /* ignore blank lines */
if (Str::get_first_char(line) == '#') return; /* lines opening with [[#]] are comments */
wp->commands++;
match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, line, L"(%c+) *: *(%c+?)")) {
text_stream *key = mr.exp[0], *value = Str::duplicate(mr.exp[1]);
if ((Str::eq_insensitive(key, I"name")) && (wp->commands == 1)) {
match_results mr2 = Regexp::create_mr();
if (Regexp::match(&mr2, value, L"(%c+?) based on (%c+)")) {
if (Str::ne_insensitive(mr2.exp[0], wp->pattern_name)) {
Errors::in_text_file("wrong pattern name", tfp);
}
wp->based_on = Patterns::find(wp->patterned_for, mr2.exp[1]);
wp->pattern_format = wp->based_on->pattern_format;
wp->number_sections = wp->based_on->number_sections;
wp->default_range = Str::duplicate(wp->based_on->default_range);
wp->mathematics_plugin = Str::duplicate(wp->based_on->mathematics_plugin);
wp->footnotes_plugin = Str::duplicate(wp->based_on->footnotes_plugin);
} else {
if (Str::ne_insensitive(value, wp->pattern_name)) {
Errors::in_text_file("wrong pattern name", tfp);
}
}
Regexp::dispose_of(&mr2);
wp->name_command_given = TRUE;
} else if (Str::eq_insensitive(key, I"plugin")) {
text_stream *name = Patterns::plugin_name(value, tfp);
if (Str::len(name) > 0) {
weave_plugin *plugin = Assets::new(name);
ADD_TO_LINKED_LIST(plugin, weave_plugin, wp->plugins);
}
} else if (Str::eq_insensitive(key, I"format")) {
wp->pattern_format = Formats::find_by_name(value);
} else if (Str::eq_insensitive(key, I"number sections")) {
wp->number_sections = Patterns::yes_or_no(value, tfp);
} else if (Str::eq_insensitive(key, I"default range")) {
wp->default_range = Str::duplicate(value);
} else if (Str::eq_insensitive(key, I"initial extension")) {
wp->initial_extension = Str::duplicate(value);
} else if (Str::eq_insensitive(key, I"mathematics plugin")) {
wp->mathematics_plugin = Patterns::plugin_name(value, tfp);
} else if (Str::eq_insensitive(key, I"footnotes plugin")) {
wp->footnotes_plugin = Patterns::plugin_name(value, tfp);
} else if (Str::eq_insensitive(key, I"block template")) {
ADD_TO_LINKED_LIST(Str::duplicate(value), text_stream, wp->blocked_templates);
} else if (Str::eq_insensitive(key, I"command")) {
ADD_TO_LINKED_LIST(Str::duplicate(value), text_stream, wp->post_commands);
} else if (Str::eq_insensitive(key, I"bibliographic data")) {
match_results mr2 = Regexp::create_mr();
if (Regexp::match(&mr2, value, L"(%c+?) = (%c+)")) {
Bibliographic::set_datum(wp->patterned_for->md, mr2.exp[0], mr2.exp[1]);
} else {
Errors::in_text_file("syntax is 'bibliographic data: X = Y'", tfp);
}
Regexp::dispose_of(&mr2);
} else if (Str::eq_insensitive(key, I"assets")) {
match_results mr2 = Regexp::create_mr();
if (Regexp::match(&mr2, value, L"(.%C+?) (%c+)")) {
Assets::add_asset_rule(wp->asset_rules, mr2.exp[0], mr2.exp[1], tfp);
} else {
Errors::in_text_file("syntax is 'assets: .EXT COMMAND'", tfp);
}
Regexp::dispose_of(&mr2);
} else {
Errors::in_text_file("unrecognised pattern command", tfp);
}
} else {
Errors::in_text_file("unrecognised pattern command", tfp);
}
Regexp::dispose_of(&mr);
}
<<*>>=
int Patterns::yes_or_no(text_stream *arg, text_file_position *tfp) {
if (Str::eq(arg, I"yes")) return TRUE;
if (Str::eq(arg, I"no")) return FALSE;
Errors::in_text_file("setting must be 'yes' or 'no'", tfp);
return FALSE;
}
text_stream *Patterns::plugin_name(text_stream *arg, text_file_position *tfp) {
match_results mr = Regexp::create_mr();
if (Regexp::match(&mr, arg, L"(%i+)")) {
if (Str::eq_insensitive(arg, I"none")) return NULL;
} else {
Errors::in_text_file("plugin names must be single alphanumeric words", tfp);
arg = NULL;
}
Regexp::dispose_of(&mr);
return Str::duplicate(arg);
}
@ \section{Post-processing.}
In effect, a pattern can hold a shell script to run after each weave (subset)
completes.
<<*>>=
void Patterns::post_process(weave_pattern *pattern, weave_order *wv) {
text_stream *T;
LOOP_OVER_LINKED_LIST(T, text_stream, pattern->post_commands) {
filename *last_F = NULL;
TEMPORARY_TEXT(cmd)
for (int i=0; i<Str::len(T); i++) {
if (Str::includes_at(T, i, I"WOVENPATH")) {
Shell::quote_path(cmd, Filenames::up(wv->weave_to));
i += 8;
} else if (Str::includes_at(T, i, I"WOVEN")) {
filename *W = wv->weave_to;
i += 5;
if (Str::get_at(T, i) == '.') {
i++;
TEMPORARY_TEXT(ext)
while (Characters::isalpha(Str::get_at(T, i)))
PUT_TO(ext,Str::get_at(T, i++));
W = Filenames::set_extension(W, ext);
DISCARD_TEXT(ext)
}
Shell::quote_file(cmd, W);
last_F = W;
i--;
} else PUT_TO(cmd, Str::get_at(T, i));
}
if ((Str::includes_at(cmd, 0, I"PROCESS ")) && (last_F)) {
TeXUtilities::post_process_weave(wv, last_F);
} else {
if (verbose_mode) PRINT("(%S)\n", cmd);
int rv = Shell::run(cmd);
if (rv != 0) Errors::fatal("post-processing command failed");
}
DISCARD_TEXT(cmd)
}
}
@ \section{Obtaining files.}
Patterns provide place template files, such as [[template-body.html]], in
their root directories.
Note that if you're rash enough to set up a cycle of patterns inheriting
from each other then this routine will lock up into an infinite loop.
<<*>>=
filename *Patterns::find_template(weave_pattern *pattern, text_stream *leafname) {
for (weave_pattern *wp = pattern; wp; wp = wp->based_on) {
text_stream *T;
LOOP_OVER_LINKED_LIST(T, text_stream, pattern->blocked_templates)
if (Str::eq_insensitive(T, leafname))
return NULL;
filename *F = Filenames::in(wp->pattern_location, leafname);
if (TextFiles::exists(F)) return F;
}
return NULL;
}
@ Similarly, but looking in an intermediate directory:
<<*>>=
filename *Patterns::find_file_in_subdirectory(weave_pattern *pattern,
text_stream *dirname, text_stream *leafname) {
for (weave_pattern *wp = pattern; wp; wp = wp->based_on) {
pathname *P = Pathnames::down(wp->pattern_location, dirname);
filename *F = Filenames::in(P, leafname);
if (TextFiles::exists(F)) return F;
}
return NULL;
}
<<*>>=
void Patterns::include_plugins(OUTPUT_STREAM, web *W, weave_pattern *pattern, filename *from) {
for (weave_pattern *p = pattern; p; p = p->based_on) {
weave_plugin *wp;
LOOP_OVER_LINKED_LIST(wp, weave_plugin, p->plugins)
Assets::include_plugin(OUT, W, wp, pattern, from);
colour_scheme *cs;
LOOP_OVER_LINKED_LIST(cs, colour_scheme, p->colour_schemes)
Assets::include_colour_scheme(OUT, W, cs, pattern, from);
}
}