mirror of
https://github.com/ganelson/inform.git
synced 2024-06-26 04:00:43 +03:00
Moved extension census and scanning to inbuild
This commit is contained in:
parent
accbe7e16c
commit
ace0230321
|
@ -27,6 +27,7 @@ inbuild_nest *destination_nest = NULL;
|
|||
|
||||
int main(int argc, char **argv) {
|
||||
Foundation::start();
|
||||
WordsModule::start();
|
||||
InbuildModule::start();
|
||||
targets = NEW_LINKED_LIST(inbuild_copy);
|
||||
nest_list = NEW_LINKED_LIST(inbuild_nest);
|
||||
|
@ -67,6 +68,7 @@ int main(int argc, char **argv) {
|
|||
case SYNC_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, TRUE); break;
|
||||
}
|
||||
}
|
||||
WordsModule::end();
|
||||
InbuildModule::end();
|
||||
Foundation::end();
|
||||
return 0;
|
||||
|
@ -163,3 +165,58 @@ void Main::load_one(text_stream *arg, int throwing_error) {
|
|||
}
|
||||
ADD_TO_LINKED_LIST(C, inbuild_copy, targets);
|
||||
}
|
||||
|
||||
@ Since we want to include the words module, we have to define the following
|
||||
structure and initialiser:
|
||||
|
||||
@d VOCABULARY_MEANING_INITIALISER Main::ignore
|
||||
|
||||
=
|
||||
typedef struct vocabulary_meaning {
|
||||
int enigmatic_number;
|
||||
} vocabulary_meaning;
|
||||
|
||||
@
|
||||
|
||||
@d LEXER_PROBLEM_HANDLER Main::lexer_problem_handler
|
||||
|
||||
=
|
||||
vocabulary_meaning Main::ignore(vocabulary_entry *ve) {
|
||||
vocabulary_meaning vm;
|
||||
vm.enigmatic_number = 16339;
|
||||
return vm;
|
||||
}
|
||||
|
||||
void Main::lexer_problem_handler(int err, text_stream *problem_source_description, wchar_t *word) {
|
||||
if (err == MEMORY_OUT_LEXERERROR)
|
||||
Errors::fatal("Out of memory: unable to create lexer workspace");
|
||||
TEMPORARY_TEXT(word_t);
|
||||
if (word) WRITE_TO(word_t, "%w", word);
|
||||
switch (err) {
|
||||
case STRING_TOO_LONG_LEXERERROR:
|
||||
Errors::with_text("Too much text in quotation marks: %S", word_t);
|
||||
break;
|
||||
case WORD_TOO_LONG_LEXERERROR:
|
||||
Errors::with_text("Word too long: %S", word_t);
|
||||
break;
|
||||
case I6_TOO_LONG_LEXERERROR:
|
||||
Errors::with_text("I6 inclusion too long: %S", word_t);
|
||||
break;
|
||||
case STRING_NEVER_ENDS_LEXERERROR:
|
||||
Errors::with_text("Quoted text never ends: %S", problem_source_description);
|
||||
break;
|
||||
case COMMENT_NEVER_ENDS_LEXERERROR:
|
||||
Errors::with_text("Square-bracketed text never ends: %S", problem_source_description);
|
||||
break;
|
||||
case I6_NEVER_ENDS_LEXERERROR:
|
||||
Errors::with_text("I6 inclusion text never ends: %S", problem_source_description);
|
||||
break;
|
||||
default:
|
||||
internal_error("unknown lexer error");
|
||||
}
|
||||
DISCARD_TEXT(word_t);
|
||||
}
|
||||
|
||||
@
|
||||
|
||||
@d PREFORM_LANGUAGE_TYPE void
|
||||
|
|
|
@ -7,6 +7,7 @@ Version Number: 1
|
|||
Version Name: Avignon
|
||||
|
||||
Import: foundation
|
||||
Import: inform7/words
|
||||
Import: inbuild
|
||||
|
||||
Preliminaries
|
||||
|
|
|
@ -24,6 +24,8 @@ Setting up the use of this module.
|
|||
@e inbuild_nest_MT
|
||||
@e inbuild_search_result_MT
|
||||
@e inbuild_work_database_entry_array_MT
|
||||
@e extension_census_datum_MT
|
||||
@e extension_census_MT
|
||||
|
||||
=
|
||||
ALLOCATE_INDIVIDUALLY(inform_kit)
|
||||
|
@ -41,6 +43,8 @@ ALLOCATE_INDIVIDUALLY(build_script)
|
|||
ALLOCATE_INDIVIDUALLY(build_step)
|
||||
ALLOCATE_INDIVIDUALLY(inbuild_nest)
|
||||
ALLOCATE_INDIVIDUALLY(inbuild_search_result)
|
||||
ALLOCATE_INDIVIDUALLY(extension_census_datum)
|
||||
ALLOCATE_INDIVIDUALLY(extension_census)
|
||||
|
||||
ALLOCATE_IN_ARRAYS(inbuild_work_database_entry, 100)
|
||||
|
||||
|
@ -59,8 +63,10 @@ void InbuildModule::start(void) {
|
|||
|
||||
@
|
||||
|
||||
@e EXTENSION_DICTIONARY_MREASON
|
||||
|
||||
@<Register this module's memory allocation reasons@> =
|
||||
;
|
||||
Memory::reason_name(EXTENSION_DICTIONARY_MREASON, "extension dictionary");
|
||||
|
||||
@<Register this module's stream writers@> =
|
||||
Writers::register_writer('v', &VersionNumbers::writer);
|
||||
|
@ -68,8 +74,10 @@ void InbuildModule::start(void) {
|
|||
|
||||
@
|
||||
|
||||
@e EXTENSIONS_CENSUS_DA
|
||||
|
||||
@<Register this module's debugging log aspects@> =
|
||||
;
|
||||
Log::declare_aspect(EXTENSIONS_CENSUS_DA, L"extensions census", FALSE, FALSE);
|
||||
|
||||
@<Register this module's debugging log writers@> =
|
||||
;
|
||||
|
|
|
@ -158,6 +158,7 @@ inbuild_copy *Model::claim(text_stream *arg) {
|
|||
}
|
||||
inbuild_copy *C = NULL;
|
||||
if (C == NULL) C = Kits::claim(arg, ext, directory_status);
|
||||
if (C == NULL) C = Extensions::claim(arg, ext, directory_status);
|
||||
DISCARD_TEXT(ext);
|
||||
return C;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ Nests are repositories of Inform-related resources.
|
|||
typedef struct inbuild_nest {
|
||||
struct pathname *location;
|
||||
int read_only;
|
||||
int tag_value;
|
||||
MEMORY_MANAGEMENT
|
||||
} inbuild_nest;
|
||||
|
||||
|
@ -22,9 +23,20 @@ inbuild_nest *Nests::new(pathname *P) {
|
|||
inbuild_nest *N = CREATE(inbuild_nest);
|
||||
N->location = P;
|
||||
N->read_only = FALSE;
|
||||
N->tag_value = -1;
|
||||
return N;
|
||||
}
|
||||
|
||||
int Nests::get_tag(inbuild_nest *N) {
|
||||
if (N == NULL) return -1;
|
||||
return N->tag_value;
|
||||
}
|
||||
|
||||
void Nests::set_tag(inbuild_nest *N, int t) {
|
||||
if (N == NULL) internal_error("no nest");
|
||||
N->tag_value = t;
|
||||
}
|
||||
|
||||
void Nests::protect(inbuild_nest *N) {
|
||||
N->read_only = TRUE;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,41 @@
|
|||
To conduct a census of all the extensions installed (whether used
|
||||
on this run or not), and keep the documentation index for them up to date.
|
||||
|
||||
@h Definitions.
|
||||
@
|
||||
|
||||
=
|
||||
typedef struct extension_census {
|
||||
struct linked_list *search_list; /* of |inbuild_nest| */
|
||||
int built_in_tag;
|
||||
int materials_tag;
|
||||
int external_tag;
|
||||
MEMORY_MANAGEMENT
|
||||
} extension_census;
|
||||
|
||||
extension_census *Extensions::Census::new(linked_list *L) {
|
||||
extension_census *C = CREATE(extension_census);
|
||||
C->search_list = L;
|
||||
C->built_in_tag = -2;
|
||||
C->materials_tag = -2;
|
||||
C->external_tag = -2;
|
||||
return C;
|
||||
}
|
||||
|
||||
pathname *Extensions::Census::internal_path(extension_census *C) {
|
||||
inbuild_nest *N = NULL;
|
||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
||||
if (Nests::get_tag(N) == C->built_in_tag)
|
||||
return Extensions::path_within_nest(N);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pathname *Extensions::Census::external_path(extension_census *C) {
|
||||
inbuild_nest *N = NULL;
|
||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
||||
if (Nests::get_tag(N) == C->external_tag)
|
||||
return Extensions::path_within_nest(N);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ In addition to the extensions read in, there are the roads not taken: the
|
||||
ones which I7 has at its disposal, but which the source text never asks to
|
||||
|
@ -17,12 +51,12 @@ or ECD.
|
|||
=
|
||||
typedef struct extension_census_datum {
|
||||
struct inbuild_work *ecd_work; /* title, author, hash code */
|
||||
struct text_stream *version_text; /* such as |23| or |14/060527| */
|
||||
struct inbuild_version_number version; /* such as |2.1| or |14/060527| */
|
||||
struct text_stream *VM_requirement; /* such as "(for Z-machine only)" */
|
||||
int built_in; /* found in the Inform 7 application's private stock */
|
||||
int project_specific; /* found in the Materials folder for the current project */
|
||||
int overriding_a_built_in_extension; /* not built in, but overriding one which is */
|
||||
pathname *domain; /* pathname of the stock in which this was found */
|
||||
struct inbuild_nest *domain; /* pathname of the stock in which this was found */
|
||||
struct text_stream *rubric; /* brief description found in opening lines */
|
||||
struct extension_census_datum *next; /* next one in lexicographic order */
|
||||
MEMORY_MANAGEMENT
|
||||
|
@ -41,10 +75,11 @@ ever be reversed, a matching change would need to be made in the code which
|
|||
opens extension files in Read Source Text.)
|
||||
|
||||
=
|
||||
void Extensions::Census::perform(void) {
|
||||
void Extensions::Census::perform(extension_census *C) {
|
||||
Extensions::Census::begin_recording_census_errors();
|
||||
for (int area=0; area<NO_FS_AREAS; area++)
|
||||
Extensions::Census::take_census_of_domain(pathname_of_extensions[area], area+1);
|
||||
inbuild_nest *N;
|
||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
||||
Extensions::Census::take_census_of_domain(C, N, Nests::get_tag(N));
|
||||
Extensions::Census::end_recording_census_errors();
|
||||
}
|
||||
|
||||
|
@ -56,10 +91,11 @@ special (it is read-only, for one thing), but in principle we could have
|
|||
any number of other domains. The following code scans one.
|
||||
|
||||
=
|
||||
pathname *current_extension_domain = NULL;
|
||||
void Extensions::Census::take_census_of_domain(pathname *P, int origin) {
|
||||
current_extension_domain = P;
|
||||
Extensions::Census::census_from(P, TRUE, origin, NULL);
|
||||
inbuild_nest *current_extension_domain = NULL;
|
||||
void Extensions::Census::take_census_of_domain(extension_census *C, inbuild_nest *N, int origin) {
|
||||
current_extension_domain = N;
|
||||
pathname *P = Extensions::path_within_nest(N);
|
||||
Extensions::Census::census_from(C, N, P, TRUE, origin, NULL);
|
||||
}
|
||||
|
||||
@ The following routine is not as recursive as it looks, since it runs at
|
||||
|
@ -90,7 +126,7 @@ typedef struct census_state {
|
|||
text_stream *parent;
|
||||
} census_state;
|
||||
|
||||
void Extensions::Census::census_from(pathname *P, int top_level, int origin, text_stream *parent) {
|
||||
void Extensions::Census::census_from(extension_census *C, inbuild_nest *N, pathname *P, int top_level, int origin, text_stream *parent) {
|
||||
scan_directory *dir = Directories::open(P);
|
||||
if (dir) {
|
||||
census_state cs;
|
||||
|
@ -98,13 +134,13 @@ void Extensions::Census::census_from(pathname *P, int top_level, int origin, tex
|
|||
TEMPORARY_TEXT(item_name);
|
||||
int count = 0;
|
||||
while (Directories::next(dir, item_name))
|
||||
Extensions::Census::census_from_helper(item_name, &cs, ++count);
|
||||
Extensions::Census::census_from_helper(C, N, item_name, &cs, ++count);
|
||||
DISCARD_TEXT(item_name);
|
||||
Directories::close(dir);
|
||||
}
|
||||
}
|
||||
|
||||
void Extensions::Census::census_from_helper(text_stream *item_name, census_state *cs,
|
||||
void Extensions::Census::census_from_helper(extension_census *C, inbuild_nest *N, text_stream *item_name, census_state *cs,
|
||||
int count) {
|
||||
if (Str::len(item_name) == 0) return;
|
||||
LOGIF(EXTENSIONS_CENSUS, "%d: %S\n", count, item_name);
|
||||
|
@ -137,19 +173,19 @@ level to scan it.
|
|||
if (Str::eq(item_name, I"Reserved")) return;
|
||||
|
||||
if (Str::len(item_name) > MAX_EXTENSION_TITLE_LENGTH-1) {
|
||||
Extensions::Census::census_error("author name exceeds the maximum permitted length",
|
||||
Extensions::Census::census_error(I"author name exceeds the maximum permitted length",
|
||||
item_name, NULL, NULL, NULL); return;
|
||||
}
|
||||
if (Str::includes_character(item_name, '.')) {
|
||||
Extensions::Census::census_error("author name contains a full stop",
|
||||
Extensions::Census::census_error(I"author name contains a full stop",
|
||||
item_name, NULL, NULL, NULL); return;
|
||||
}
|
||||
Extensions::Census::census_from(
|
||||
Pathnames::subfolder(cs->P, item_name),
|
||||
C, N, Pathnames::subfolder(cs->P, item_name),
|
||||
FALSE, cs->origin, item_name);
|
||||
return;
|
||||
}
|
||||
Extensions::Census::census_error("non-folder found where author folders should be",
|
||||
Extensions::Census::census_error(I"non-folder found where author folders should be",
|
||||
item_name, NULL, NULL, NULL); return;
|
||||
|
||||
@ At the lower level, |.| files or folders are again skipped; any other
|
||||
|
@ -166,30 +202,28 @@ installed correctly on other Informs elsewhere (perhaps on other platforms).
|
|||
@d MAX_TITLING_LINE_LENGTH 501 /* lots, allowing for an improbably large number of virtual machine restrictions */
|
||||
|
||||
@<Take census from a possible extension@> =
|
||||
inbuild_version_number V = VersionNumbers::null();
|
||||
int overridden_by_an_extension_already_found = FALSE;
|
||||
TEMPORARY_TEXT(candidate_title);
|
||||
TEMPORARY_TEXT(raw_title);
|
||||
TEMPORARY_TEXT(candidate_author_name);
|
||||
TEMPORARY_TEXT(raw_author_name);
|
||||
TEMPORARY_TEXT(titling_line);
|
||||
TEMPORARY_TEXT(rubric_text);
|
||||
TEMPORARY_TEXT(version_text);
|
||||
TEMPORARY_TEXT(requirement_text);
|
||||
TEMPORARY_TEXT(claimed_author_name);
|
||||
TEMPORARY_TEXT(claimed_title);
|
||||
if (Str::get_last_char(item_name) == '.') return;
|
||||
if (Str::get_last_char(item_name) == FOLDER_SEPARATOR) {
|
||||
Extensions::Census::census_error("folder or application in author folder",
|
||||
Extensions::Census::census_error(I"folder or application in author folder",
|
||||
cs->parent, item_name, NULL, NULL); return;
|
||||
}
|
||||
if (Str::len(item_name) > MAX_EXTENSION_TITLE_LENGTH-1) {
|
||||
Extensions::Census::census_error("title exceeds the maximum permitted length",
|
||||
Extensions::Census::census_error(I"title exceeds the maximum permitted length",
|
||||
cs->parent, item_name, NULL, NULL); return;
|
||||
}
|
||||
|
||||
|
||||
@<Make candidate title and author name from normalised casing versions of filename and parent folder name@>;
|
||||
@<Extract the titling line and rubric, if any, from the extension file@>;
|
||||
@<Parse the version, title, author and VM requirements from the titling line@>;
|
||||
@<Scan the extension file@>;
|
||||
@<Check that the candidate name and title match those claimed in the titling line@>;
|
||||
@<See if we duplicate the title and author name of an extension already found in another domain@>;
|
||||
if (overridden_by_an_extension_already_found == FALSE) {
|
||||
|
@ -200,9 +234,7 @@ installed correctly on other Informs elsewhere (perhaps on other platforms).
|
|||
DISCARD_TEXT(raw_title);
|
||||
DISCARD_TEXT(candidate_author_name);
|
||||
DISCARD_TEXT(raw_author_name);
|
||||
DISCARD_TEXT(titling_line);
|
||||
DISCARD_TEXT(rubric_text);
|
||||
DISCARD_TEXT(version_text);
|
||||
DISCARD_TEXT(requirement_text);
|
||||
DISCARD_TEXT(claimed_author_name);
|
||||
DISCARD_TEXT(claimed_title);
|
||||
|
@ -238,7 +270,7 @@ stored have been waived.
|
|||
|
||||
if (Str::includes_character(candidate_title, '.')) {
|
||||
LOG("Title is <%S>\n", candidate_title);
|
||||
Extensions::Census::census_error("title contains a full stop",
|
||||
Extensions::Census::census_error(I"title contains a full stop",
|
||||
cs->parent, candidate_title, NULL, NULL); return;
|
||||
}
|
||||
|
||||
|
@ -260,127 +292,21 @@ extract its titling line and rubric, then close it again.
|
|||
@<Read the rubric text, if any is present@>;
|
||||
fclose(EXTF);
|
||||
|
||||
@ The following should only fail in the event of some peculiar filing system
|
||||
failure (or of some other process having moved or deleted the file since
|
||||
we scanned the folder only moments ago).
|
||||
@ The actual scanning is delegated to the extension-scanner already given.
|
||||
|
||||
@<Open the extension file for reading@> =
|
||||
@<Scan the extension file@> =
|
||||
filename *F =
|
||||
Locations::of_extension(current_extension_domain,
|
||||
item_name, cs->parent, FALSE);
|
||||
EXTF = Filenames::fopen_caseless(F, "r");
|
||||
if (EXTF == NULL) {
|
||||
Extensions::Census::census_error("file cannot be read",
|
||||
cs->parent, item_name, NULL, NULL); return;
|
||||
}
|
||||
|
||||
@ The actual maximum number of characters in the titling line is one less
|
||||
than |MAX_TITLING_LINE_LENGTH|, to allow for the null terminator. The titling
|
||||
line is terminated by any of |0A|, |0D|, |0A 0D| or |0D 0A|, or by the local
|
||||
|\n| for good measure.
|
||||
|
||||
@<Read the titling line of the extension and normalise its casing@> =
|
||||
int titling_chars_read = 0, c;
|
||||
while ((c = TextFiles::utf8_fgetc(EXTF, NULL, FALSE, NULL)) != EOF) {
|
||||
if (c == 0xFEFF) return; /* skip the optional Unicode BOM pseudo-character */
|
||||
if ((c == '\x0a') || (c == '\x0d') || (c == '\n')) break;
|
||||
if (titling_chars_read < MAX_TITLING_LINE_LENGTH - 1) PUT_TO(titling_line, c);
|
||||
}
|
||||
Works::normalise_casing(titling_line);
|
||||
|
||||
@ In the following, all possible newlines are converted to white space, and
|
||||
all white space before a quoted rubric text is ignored. We need to do this
|
||||
partly because users have probably keyed a double line break before the
|
||||
rubric, but also because we might have stopped reading the titling line
|
||||
halfway through a line division combination like |0A 0D|, so that the first
|
||||
thing we read here is a meaningless |0D|.
|
||||
|
||||
@<Read the rubric text, if any is present@> =
|
||||
int c, found_start = FALSE;
|
||||
while ((c = TextFiles::utf8_fgetc(EXTF, NULL, FALSE, NULL)) != EOF) {
|
||||
if ((c == '\x0a') || (c == '\x0d') || (c == '\n') || (c == '\t')) c = ' ';
|
||||
if ((c != ' ') && (found_start == FALSE)) {
|
||||
if (c == '"') found_start = TRUE;
|
||||
else break;
|
||||
} else {
|
||||
if (c == '"') break;
|
||||
if (found_start) PUT_TO(rubric_text, c);
|
||||
}
|
||||
}
|
||||
|
||||
@h Parsing the titling line.
|
||||
In general, once case-normalised, a titling line looks like this:
|
||||
|
||||
>> Version 2/070423 Of Going To The Zoo (For Glulx Only) By Cary Grant Begins Here.
|
||||
|
||||
and the version information, the VM restriction and the full stop are all
|
||||
optional, but the division word "of" and the concluding "begin[s] here"
|
||||
are not. We break it up into pieces, so that
|
||||
|
||||
|version_text = "2/070423"|
|
||||
|claimed_title = "Going To The Zoo"|
|
||||
|claimed_author_name = "Cary Grant"|
|
||||
|requirement_text = "(For Glulx Only)"|
|
||||
|
||||
It's tempting to do this by feeding it into the lexer and then reusing some
|
||||
of the code which parses these lines during sentence-breaking, but in fact
|
||||
we want to use the information rather differently, and besides: it seems
|
||||
useful to record some C code here which correctly parses a titling line,
|
||||
since this can easily be extracted and used in other utilities handling
|
||||
Inform extensions.
|
||||
|
||||
@<Parse the version, title, author and VM requirements from the titling line@> =
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Str::get_last_char(titling_line) == '.') Str::delete_last_character(titling_line);
|
||||
if ((Regexp::match(&mr, titling_line, L"(%c*) Begin Here")) ||
|
||||
(Regexp::match(&mr, titling_line, L"(%c*) Begins Here"))) {
|
||||
Str::copy(titling_line, mr.exp[0]);
|
||||
} else {
|
||||
LOG("Titling: %S\n", titling_line);
|
||||
Extensions::Census::census_error("appears not to be an extension (its first line does "
|
||||
"not end 'begin(s) here', as extension titling lines must)",
|
||||
cs->parent, item_name, NULL, NULL); return;
|
||||
}
|
||||
|
||||
@<Scan the version text, if any, and advance to the position past Version... Of@>;
|
||||
if (Regexp::match(&mr, titling_line, L"The (%c*)")) Str::copy(titling_line, mr.exp[0]);
|
||||
@<Divide the remaining text into a claimed author name and title, divided by By@>;
|
||||
@<Extract the VM requirements text, if any, from the claimed title@>;
|
||||
Regexp::dispose_of(&mr);
|
||||
|
||||
@ We make no attempt to check the version number for validity: the purpose
|
||||
of the census is to identify extensions and reject accidentally included
|
||||
other files, not to syntax-check all extensions to see if they would work
|
||||
if used.
|
||||
|
||||
@<Scan the version text, if any, and advance to the position past Version... Of@> =
|
||||
if (Regexp::match(&mr, titling_line, L"Version (%c*?) Of (%c*)")) {
|
||||
Str::copy(version_text, mr.exp[0]);
|
||||
Str::copy(titling_line, mr.exp[1]);
|
||||
}
|
||||
|
||||
@ The earliest "by" is the divider: note that extension titles are not
|
||||
allowed to contain this word, so "North By Northwest By Cary Grant" is
|
||||
not a situation we need to contend with.
|
||||
|
||||
@<Divide the remaining text into a claimed author name and title, divided by By@> =
|
||||
if (Regexp::match(&mr, titling_line, L"(%c*?) By (%c*)")) {
|
||||
Str::copy(claimed_title, mr.exp[0]);
|
||||
Str::copy(claimed_author_name, mr.exp[1]);
|
||||
} else {
|
||||
Extensions::Census::census_error("appears not to be an extension (the titling line does "
|
||||
"not give both author and title)",
|
||||
cs->parent, item_name, NULL, NULL); return;
|
||||
}
|
||||
|
||||
@ Similarly, extension titles are not allowed to contain parentheses, so
|
||||
this is unambiguous.
|
||||
|
||||
@<Extract the VM requirements text, if any, from the claimed title@> =
|
||||
if (Regexp::match(&mr, claimed_title, L"(%c*?) *(%(%c*%))")) {
|
||||
Str::copy(claimed_title, mr.exp[0]);
|
||||
Str::copy(requirement_text, mr.exp[1]);
|
||||
Extensions::filename_in_nest(current_extension_domain,
|
||||
item_name, cs->parent);
|
||||
TEMPORARY_TEXT(error_text);
|
||||
V = Extensions::scan_file(F, claimed_title, claimed_author_name,
|
||||
rubric_text, requirement_text, error_text);
|
||||
if (Str::len(error_text) > 0) {
|
||||
Extensions::Census::census_error(error_text,
|
||||
cs->parent, item_name, NULL, NULL);
|
||||
return;
|
||||
}
|
||||
DISCARD_TEXT(error_text);
|
||||
|
||||
@h Making sure this is the extension we expected to find.
|
||||
It's easier for these confusions to arise than might be thought. For instance,
|
||||
|
@ -396,18 +322,15 @@ to arise when users install extensions by hand. Still, it's prudent to check.
|
|||
if (Str::eq(claimed_author_name, candidate_author_name)) right_folder = TRUE;
|
||||
|
||||
if ((right_leafname == TRUE) && (right_folder == FALSE)) {
|
||||
Extensions::Census::census_error("an extension with the right filename but in the wrong "
|
||||
"author's folder",
|
||||
Extensions::Census::census_error(I"an extension with the right filename but in the wrong author's folder",
|
||||
cs->parent, item_name, claimed_author_name, claimed_title); return;
|
||||
}
|
||||
if ((right_leafname == FALSE) && (right_folder == TRUE)) {
|
||||
Extensions::Census::census_error("an extension stored in the correct author's folder, but "
|
||||
"with the wrong filename",
|
||||
Extensions::Census::census_error(I"an extension stored in the correct author's folder, but with the wrong filename",
|
||||
cs->parent, item_name, claimed_author_name, claimed_title); return;
|
||||
}
|
||||
if ((right_leafname == FALSE) && (right_folder == FALSE)) {
|
||||
Extensions::Census::census_error("an extension but with the wrong filename and put in the "
|
||||
"wrong author's folder",
|
||||
Extensions::Census::census_error(I"an extension but with the wrong filename and put in the wrong author's folder",
|
||||
cs->parent, item_name, claimed_author_name, claimed_title); return;
|
||||
}
|
||||
|
||||
|
@ -423,7 +346,7 @@ which the user had installed to override this built-in extension.
|
|||
LOOP_OVER(other, extension_census_datum)
|
||||
if ((Str::eq(candidate_author_name, other->ecd_work->author_name))
|
||||
&& (Str::eq(candidate_title, other->ecd_work->title))
|
||||
&& ((other->built_in) || (cs->origin == ORIGIN_WAS_BUILT_IN_EXTENSIONS_AREA))) {
|
||||
&& ((other->built_in) || (cs->origin == C->built_in_tag))) {
|
||||
other->overriding_a_built_in_extension = TRUE;
|
||||
overridden_by_an_extension_already_found = TRUE;
|
||||
}
|
||||
|
@ -439,14 +362,12 @@ truncate it.
|
|||
Works::add_to_database(ecd->ecd_work, INSTALLED_WDBC);
|
||||
Works::set_raw(ecd->ecd_work, raw_author_name, raw_title);
|
||||
ecd->VM_requirement = Str::duplicate(requirement_text);
|
||||
if (Str::len(version_text) > MAX_VERSION_NUMBER_LENGTH)
|
||||
Str::truncate(version_text, MAX_VERSION_NUMBER_LENGTH); /* truncate to maximum legal length */
|
||||
ecd->version_text = Str::duplicate(version_text);
|
||||
ecd->version = V;
|
||||
ecd->domain = current_extension_domain;
|
||||
ecd->built_in = FALSE;
|
||||
if (cs->origin == ORIGIN_WAS_BUILT_IN_EXTENSIONS_AREA) ecd->built_in = TRUE;
|
||||
if (cs->origin == C->built_in_tag) ecd->built_in = TRUE;
|
||||
ecd->project_specific = FALSE;
|
||||
if (cs->origin == ORIGIN_WAS_MATERIALS_EXTENSIONS_AREA) ecd->project_specific = TRUE;
|
||||
if (cs->origin == C->materials_tag) ecd->project_specific = TRUE;
|
||||
ecd->overriding_a_built_in_extension = FALSE;
|
||||
ecd->next = NULL;
|
||||
ecd->rubric = Str::duplicate(rubric_text);
|
||||
|
@ -484,18 +405,23 @@ void Extensions::Census::end_recording_census_errors(void) {
|
|||
@ When a census error arises, then, we write it as a line to the errors stream.
|
||||
|
||||
=
|
||||
void Extensions::Census::census_error(char *message, text_stream *auth, text_stream *title,
|
||||
void Extensions::Census::census_error(text_stream *message, text_stream *auth, text_stream *title,
|
||||
text_stream *claimed_author, text_stream *claimed_title) {
|
||||
text_stream *OUT = CENERR;
|
||||
no_census_errors++;
|
||||
#ifdef INDEX_MODULE
|
||||
HTMLFiles::open_para(OUT, 2, "hanging");
|
||||
#endif
|
||||
#ifndef INDEX_MODULE
|
||||
HTML_OPEN("p");
|
||||
#endif
|
||||
if (Str::len(claimed_author) > 0)
|
||||
WRITE("<b>%S by %S</b> - %s (the extension says it is '%S by %S')",
|
||||
WRITE("<b>%S by %S</b> - %S (the extension says it is '%S by %S')",
|
||||
title, auth, message, claimed_title, claimed_author);
|
||||
else if ((Str::len(auth) > 0) && (Str::len(title) > 0))
|
||||
WRITE("<b>%S by %S</b> - %s", title, auth, message);
|
||||
WRITE("<b>%S by %S</b> - %S", title, auth, message);
|
||||
else
|
||||
WRITE("<b>%S</b> - %s", auth, message);
|
||||
WRITE("<b>%S</b> - %S", auth, message);
|
||||
HTML_CLOSE("p");
|
||||
}
|
||||
|
||||
|
@ -557,7 +483,7 @@ any oddities found in the external extensions area.
|
|||
@d CE_BY_LENGTH 5
|
||||
|
||||
=
|
||||
void Extensions::Census::write_results(OUTPUT_STREAM) {
|
||||
void Extensions::Census::write_results(OUTPUT_STREAM, extension_census *C) {
|
||||
@<Display the location of installed extensions@>;
|
||||
Extensions::Census::warn_about_census_errors(OUT);
|
||||
HTML::end_html_row(OUT);
|
||||
|
@ -608,8 +534,10 @@ void Extensions::Census::write_results(OUTPUT_STREAM) {
|
|||
HTML_TAG_WITH("img", "src='inform:/doc_images/folder4.png' border=0");
|
||||
WRITE(" You have no other extensions installed at present.");
|
||||
} else {
|
||||
HTML::Javascript::open_file(OUT, pathname_of_extensions[EXTERNAL_FS_AREA], NULL,
|
||||
#ifdef INDEX_MODULE
|
||||
HTML::Javascript::open_file(OUT, Extensions::Census::external_path(C), NULL,
|
||||
"src='inform:/doc_images/folder4.png' border=0");
|
||||
#endif
|
||||
WRITE(" You have %d further extension%s installed. These are marked "
|
||||
"with a blue folder icon in the catalogue below. (Click it to see "
|
||||
"where the file is stored on your computer.) "
|
||||
|
@ -619,7 +547,9 @@ void Extensions::Census::write_results(OUTPUT_STREAM) {
|
|||
HTML_CLOSE("p");
|
||||
if (nps > 0) {
|
||||
HTML_OPEN("p");
|
||||
HTML::Javascript::open_file(OUT, pathname_of_extensions[INTERNAL_FS_AREA], NULL, PROJECT_SPECIFIC_SYMBOL);
|
||||
#ifdef INDEX_MODULE
|
||||
HTML::Javascript::open_file(OUT, Extensions::Census::internal_path(C), NULL, PROJECT_SPECIFIC_SYMBOL);
|
||||
#endif
|
||||
WRITE(" You have %d extension%s in the .materials folder for the "
|
||||
"current project. (Click the purple folder icon to show the "
|
||||
"location.) %s not available to other projects.",
|
||||
|
@ -632,9 +562,11 @@ extensions used in the current run; otherwise they wouldn't show in the
|
|||
documentation as used today until the next run, for obscure timing reasons.
|
||||
|
||||
@<Time stamp the extensions used on this run@> =
|
||||
extension_file *ef;
|
||||
LOOP_OVER(ef, extension_file)
|
||||
Extensions::Dictionary::time_stamp(ef);
|
||||
#ifdef CORE_MODULE
|
||||
inform_extension *E;
|
||||
LOOP_OVER(E, inform_extension)
|
||||
Extensions::Dictionary::time_stamp(E);
|
||||
#endif
|
||||
|
||||
@ I am the first to admit that this implementation is not inspired. There
|
||||
are five radio buttons, and number 2 is selected by default.
|
||||
|
@ -721,8 +653,10 @@ just a blank image used for horizontal spacing to keep margins straight.
|
|||
WRITE(" Your version overrides the one built in ");
|
||||
}
|
||||
if (key_vms) {
|
||||
#ifdef CORE_MODULE
|
||||
HTML_TAG("br");
|
||||
VirtualMachines::write_key(OUT);
|
||||
#endif
|
||||
}
|
||||
HTML_CLOSE("p");
|
||||
}
|
||||
|
@ -862,14 +796,14 @@ the usual ones seen in Mac OS X applications such as iTunes.
|
|||
case 0:
|
||||
WRITE("Supplied in the .materials folder ");
|
||||
HTML_OPEN_WITH("span", "class=\"smaller\"");
|
||||
WRITE("%p", pathname_of_extensions[INTERNAL_FS_AREA]);
|
||||
WRITE("%p", Extensions::Census::internal_path(C));
|
||||
HTML_CLOSE("span"); break;
|
||||
case 1: WRITE("Built in to Inform"); break;
|
||||
case 2: WRITE("User installed but overriding a built-in extension"); break;
|
||||
case 3:
|
||||
WRITE("User installed ");
|
||||
HTML_OPEN_WITH("span", "class=\"smaller\"");
|
||||
WRITE("%p", pathname_of_extensions[EXTERNAL_FS_AREA]);
|
||||
WRITE("%p", Extensions::Census::external_path(C));
|
||||
HTML_CLOSE("span"); break;
|
||||
}
|
||||
|
||||
|
@ -931,14 +865,19 @@ brackets, which the lexer will split off as distinct words, we can ignore
|
|||
the first and last word and just look at what is in between:
|
||||
|
||||
@<Append icons which signify the VM requirements of the extension@> =
|
||||
wording W = Feeds::feed_stream(ecd->VM_requirement);
|
||||
WRITE(" ");
|
||||
#ifdef CORE_MODULE
|
||||
wording W = Feeds::feed_stream(ecd->VM_requirement);
|
||||
VirtualMachines::write_icons(OUT, Wordings::trim_last_word(Wordings::trim_last_word(W)));
|
||||
#endif
|
||||
#ifndef CORE_MODULE
|
||||
WRITE("%S", ecd->VM_requirement);
|
||||
#endif
|
||||
|
||||
@<Print column 2 of the census line@> =
|
||||
HTML_OPEN_WITH("span", "class=\"smaller\"");
|
||||
if (Str::len(ecd->version_text) > 0)
|
||||
WRITE("v %S", ecd->version_text);
|
||||
if (VersionNumbers::is_null(ecd->version) == FALSE)
|
||||
WRITE("v %v", &(ecd->version));
|
||||
else
|
||||
WRITE("--");
|
||||
HTML_CLOSE("span");
|
||||
|
@ -951,17 +890,20 @@ the first and last word and just look at what is in between:
|
|||
|
||||
@<Print column 3 of the census line@> =
|
||||
char *opener = "src='inform:/doc_images/folder4.png' border=0";
|
||||
pathname *area = pathname_of_extensions[EXTERNAL_FS_AREA];
|
||||
if (ecd->built_in) { opener = BUILT_IN_SYMBOL; key_builtin = TRUE; }
|
||||
if (ecd->overriding_a_built_in_extension) {
|
||||
opener = OVERRIDING_SYMBOL; key_override = TRUE;
|
||||
}
|
||||
if (ecd->project_specific) {
|
||||
opener = PROJECT_SPECIFIC_SYMBOL; key_pspec = TRUE;
|
||||
area = pathname_of_extensions[MATERIALS_FS_AREA];
|
||||
}
|
||||
if (ecd->built_in) HTML_TAG_WITH("img", "%s", opener)
|
||||
else HTML::Javascript::open_file(OUT, area, ecd->ecd_work->raw_author_name, opener);
|
||||
else {
|
||||
#ifdef INDEX_MODULE
|
||||
pathname *area = Extensions::path_within_nest(ecd->domain);
|
||||
HTML::Javascript::open_file(OUT, area, ecd->ecd_work->raw_author_name, opener);
|
||||
#endif
|
||||
}
|
||||
|
||||
@<Print column 4 of the census line@> =
|
||||
HTML_OPEN_WITH("span", "class=\"smaller\"");
|
|
@ -7,6 +7,13 @@ An Inform 7 extension.
|
|||
= (early code)
|
||||
inbuild_genre *extension_genre = NULL;
|
||||
|
||||
@ An extension has a title and an author name, each of which is limited in
|
||||
length to one character less than the following constants:
|
||||
|
||||
@d MAX_EXTENSION_TITLE_LENGTH 51
|
||||
@d MAX_EXTENSION_AUTHOR_LENGTH 51
|
||||
@d MAX_VERSION_NUMBER_LENGTH 32 /* allows for |999/991231| and more besides */
|
||||
|
||||
@ =
|
||||
void Extensions::start(void) {
|
||||
extension_genre = Model::genre(I"extension");
|
||||
|
@ -18,10 +25,23 @@ void Extensions::start(void) {
|
|||
inbuild_copy *Extensions::claim(text_stream *arg, text_stream *ext, int directory_status) {
|
||||
if (directory_status == TRUE) return NULL;
|
||||
if (Str::eq_insensitive(ext, I"i7x")) {
|
||||
// eventually load into a copy here
|
||||
|
||||
// Works::add_to_database(...->work, CLAIMED_WDBC);
|
||||
return NULL;
|
||||
filename *F = Filenames::from_text(arg);
|
||||
TEMPORARY_TEXT(author);
|
||||
TEMPORARY_TEXT(title);
|
||||
TEMPORARY_TEXT(error_text);
|
||||
TEMPORARY_TEXT(rubric_text);
|
||||
TEMPORARY_TEXT(requirement_text);
|
||||
inbuild_version_number V = Extensions::scan_file(F, title, author, rubric_text, requirement_text, error_text);
|
||||
inform_extension *E = Extensions::load_at(title, author, F);
|
||||
if (Str::len(error_text) > 0) return NULL;
|
||||
Works::add_to_database(E->as_copy->edition->work, CLAIMED_WDBC);
|
||||
E->version_loaded = V; E->as_copy->edition->version = V;
|
||||
DISCARD_TEXT(author);
|
||||
DISCARD_TEXT(title);
|
||||
DISCARD_TEXT(error_text);
|
||||
DISCARD_TEXT(rubric_text);
|
||||
DISCARD_TEXT(requirement_text);
|
||||
return E->as_copy;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
@ -30,8 +50,169 @@ void Extensions::write_work(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *wor
|
|||
WRITE("%X", work);
|
||||
}
|
||||
|
||||
pathname *Extensions::path_within_nest(inbuild_nest *N) {
|
||||
if (N == NULL) internal_error("no nest");
|
||||
return Pathnames::subfolder(N->location, I"Extensions");
|
||||
}
|
||||
|
||||
filename *Extensions::filename_in_nest(inbuild_nest *N, text_stream *title, text_stream *author) {
|
||||
pathname *E = Extensions::path_within_nest(N);
|
||||
TEMPORARY_TEXT(leaf);
|
||||
WRITE_TO(leaf, "%S.i7x", title);
|
||||
filename *F = Filenames::in_folder(Pathnames::subfolder(E, author), leaf);
|
||||
DISCARD_TEXT(leaf);
|
||||
return F;
|
||||
}
|
||||
|
||||
inbuild_version_number Extensions::scan_file(filename *F,
|
||||
text_stream *claimed_title, text_stream *claimed_author_name,
|
||||
text_stream *rubric_text, text_stream *requirement_text, text_stream *error_text) {
|
||||
inbuild_version_number V = VersionNumbers::null();
|
||||
TEMPORARY_TEXT(titling_line);
|
||||
TEMPORARY_TEXT(version_text);
|
||||
FILE *EXTF = Filenames::fopen_caseless(F, "r");
|
||||
if (EXTF == NULL) {
|
||||
if (error_text) WRITE_TO(error_text, "file cannot be read");
|
||||
return V;
|
||||
}
|
||||
@<Read the titling line of the extension and normalise its casing@>;
|
||||
@<Read the rubric text, if any is present@>;
|
||||
@<Parse the version, title, author and VM requirements from the titling line@>;
|
||||
fclose(EXTF);
|
||||
if (Str::len(version_text) > 0) V = VersionNumbers::from_text(version_text);
|
||||
DISCARD_TEXT(titling_line);
|
||||
DISCARD_TEXT(version_text);
|
||||
return V;
|
||||
}
|
||||
|
||||
@ The actual maximum number of characters in the titling line is one less
|
||||
than |MAX_TITLING_LINE_LENGTH|, to allow for the null terminator. The titling
|
||||
line is terminated by any of |0A|, |0D|, |0A 0D| or |0D 0A|, or by the local
|
||||
|\n| for good measure.
|
||||
|
||||
@<Read the titling line of the extension and normalise its casing@> =
|
||||
int titling_chars_read = 0, c;
|
||||
while ((c = TextFiles::utf8_fgetc(EXTF, NULL, FALSE, NULL)) != EOF) {
|
||||
if (c == 0xFEFF) return V; /* skip the optional Unicode BOM pseudo-character */
|
||||
if ((c == '\x0a') || (c == '\x0d') || (c == '\n')) break;
|
||||
if (titling_chars_read < MAX_TITLING_LINE_LENGTH - 1) PUT_TO(titling_line, c);
|
||||
}
|
||||
Works::normalise_casing(titling_line);
|
||||
|
||||
@ In the following, all possible newlines are converted to white space, and
|
||||
all white space before a quoted rubric text is ignored. We need to do this
|
||||
partly because users have probably keyed a double line break before the
|
||||
rubric, but also because we might have stopped reading the titling line
|
||||
halfway through a line division combination like |0A 0D|, so that the first
|
||||
thing we read here is a meaningless |0D|.
|
||||
|
||||
@<Read the rubric text, if any is present@> =
|
||||
int c, found_start = FALSE;
|
||||
while ((c = TextFiles::utf8_fgetc(EXTF, NULL, FALSE, NULL)) != EOF) {
|
||||
if ((c == '\x0a') || (c == '\x0d') || (c == '\n') || (c == '\t')) c = ' ';
|
||||
if ((c != ' ') && (found_start == FALSE)) {
|
||||
if (c == '"') found_start = TRUE;
|
||||
else break;
|
||||
} else {
|
||||
if (c == '"') break;
|
||||
if (found_start) PUT_TO(rubric_text, c);
|
||||
}
|
||||
}
|
||||
|
||||
@h Parsing the titling line.
|
||||
In general, once case-normalised, a titling line looks like this:
|
||||
|
||||
>> Version 2/070423 Of Going To The Zoo (For Glulx Only) By Cary Grant Begins Here.
|
||||
|
||||
and the version information, the VM restriction and the full stop are all
|
||||
optional, but the division word "of" and the concluding "begin[s] here"
|
||||
are not. We break it up into pieces, so that
|
||||
|
||||
|version_text = "2/070423"|
|
||||
|claimed_title = "Going To The Zoo"|
|
||||
|claimed_author_name = "Cary Grant"|
|
||||
|requirement_text = "(For Glulx Only)"|
|
||||
|
||||
It's tempting to do this by feeding it into the lexer and then reusing some
|
||||
of the code which parses these lines during sentence-breaking, but in fact
|
||||
we want to use the information rather differently, and besides: it seems
|
||||
useful to record some C code here which correctly parses a titling line,
|
||||
since this can easily be extracted and used in other utilities handling
|
||||
Inform extensions.
|
||||
|
||||
@<Parse the version, title, author and VM requirements from the titling line@> =
|
||||
match_results mr = Regexp::create_mr();
|
||||
if (Str::get_last_char(titling_line) == '.') Str::delete_last_character(titling_line);
|
||||
if ((Regexp::match(&mr, titling_line, L"(%c*) Begin Here")) ||
|
||||
(Regexp::match(&mr, titling_line, L"(%c*) Begins Here"))) {
|
||||
Str::copy(titling_line, mr.exp[0]);
|
||||
} else {
|
||||
LOG("Titling: %S\n", titling_line);
|
||||
if (error_text) WRITE_TO(error_text,
|
||||
"appears not to be an extension (its first line does "
|
||||
"not end 'begin(s) here', as extension titling lines must)");
|
||||
return V;
|
||||
}
|
||||
|
||||
@<Scan the version text, if any, and advance to the position past Version... Of@>;
|
||||
if (Regexp::match(&mr, titling_line, L"The (%c*)")) Str::copy(titling_line, mr.exp[0]);
|
||||
@<Divide the remaining text into a claimed author name and title, divided by By@>;
|
||||
@<Extract the VM requirements text, if any, from the claimed title@>;
|
||||
Regexp::dispose_of(&mr);
|
||||
|
||||
@ We make no attempt to check the version number for validity: the purpose
|
||||
of the census is to identify extensions and reject accidentally included
|
||||
other files, not to syntax-check all extensions to see if they would work
|
||||
if used.
|
||||
|
||||
@<Scan the version text, if any, and advance to the position past Version... Of@> =
|
||||
if (Regexp::match(&mr, titling_line, L"Version (%c*?) Of (%c*)")) {
|
||||
Str::copy(version_text, mr.exp[0]);
|
||||
Str::copy(titling_line, mr.exp[1]);
|
||||
}
|
||||
|
||||
@ The earliest "by" is the divider: note that extension titles are not
|
||||
allowed to contain this word, so "North By Northwest By Cary Grant" is
|
||||
not a situation we need to contend with.
|
||||
|
||||
@<Divide the remaining text into a claimed author name and title, divided by By@> =
|
||||
if (Regexp::match(&mr, titling_line, L"(%c*?) By (%c*)")) {
|
||||
Str::copy(claimed_title, mr.exp[0]);
|
||||
Str::copy(claimed_author_name, mr.exp[1]);
|
||||
} else {
|
||||
if (error_text) WRITE_TO(error_text,
|
||||
"appears not to be an extension (the titling line does "
|
||||
"not give both author and title)");
|
||||
return V;
|
||||
}
|
||||
|
||||
@ Similarly, extension titles are not allowed to contain parentheses, so
|
||||
this is unambiguous.
|
||||
|
||||
@<Extract the VM requirements text, if any, from the claimed title@> =
|
||||
if (Regexp::match(&mr, claimed_title, L"(%c*?) *(%(%c*%))")) {
|
||||
Str::copy(claimed_title, mr.exp[0]);
|
||||
Str::copy(requirement_text, mr.exp[1]);
|
||||
}
|
||||
|
||||
@
|
||||
|
||||
=
|
||||
void Extensions::location_in_nest(inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results) {
|
||||
;
|
||||
pathname *P = Extensions::path_within_nest(N);
|
||||
for (int i7x_flag = 1; i7x_flag >= 0; i7x_flag--) {
|
||||
TEMPORARY_TEXT(leaf);
|
||||
if (i7x_flag) WRITE_TO(leaf, "%S.i7x", req->work->title);
|
||||
else WRITE_TO(leaf, "%S", req->work->title);
|
||||
filename *F = Filenames::in_folder(Pathnames::subfolder(P, req->work->author_name), leaf);
|
||||
if (TextFiles::exists(F)) {
|
||||
inform_extension *E = Extensions::load_at(req->work->title, req->work->author_name, F);
|
||||
if (Model::meets(E->version_loaded, req)) {
|
||||
Nests::add_search_result(search_results, N, E->as_copy);
|
||||
}
|
||||
}
|
||||
DISCARD_TEXT(leaf);
|
||||
}
|
||||
}
|
||||
|
||||
void Extensions::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing) {
|
||||
|
@ -72,6 +253,8 @@ inform_extension *Extensions::load_at(text_stream *title, text_stream *author, f
|
|||
E->rubric_as_lexed = NULL;
|
||||
E->extra_credit_as_lexed = NULL;
|
||||
#endif
|
||||
// build_graph *EV =
|
||||
Graphs::copy_vertex(E->as_copy);
|
||||
return E;
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,7 @@ void Kits::location_in_nest(inbuild_genre *gen, inbuild_nest *N, inbuild_require
|
|||
filename *canary = Filenames::in_folder(P, I"kit_metadata.txt");
|
||||
if (TextFiles::exists(canary)) {
|
||||
inform_kit *K = Kits::load_at(Pathnames::directory_name(P), P);
|
||||
if ((VersionNumbers::ge(K->version, req->min_version)) && (VersionNumbers::le(K->version, req->min_version))) {
|
||||
if (Model::meets(K->version, req)) {
|
||||
Nests::add_search_result(search_results, N, K->as_copy);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,3 +18,4 @@ Chapter 2: Conceptual Framework
|
|||
Chapter 3: The Genres
|
||||
Kits
|
||||
Extensions
|
||||
Extension Census
|
||||
|
|
|
@ -37,7 +37,6 @@ We need to itemise the structures we'll want to allocate:
|
|||
@e pcalc_prop_deferral_MT
|
||||
@e literal_pattern_MT
|
||||
@e generalisation_MT
|
||||
@e extension_census_datum_MT
|
||||
@e extension_dictionary_entry_MT
|
||||
@e known_extension_clash_MT
|
||||
@e i6_schema_array_MT
|
||||
|
@ -117,7 +116,6 @@ ALLOCATE_INDIVIDUALLY(dval_written)
|
|||
ALLOCATE_INDIVIDUALLY(equation_node)
|
||||
ALLOCATE_INDIVIDUALLY(equation_symbol)
|
||||
ALLOCATE_INDIVIDUALLY(equation)
|
||||
ALLOCATE_INDIVIDUALLY(extension_census_datum)
|
||||
ALLOCATE_INDIVIDUALLY(extension_dictionary_entry)
|
||||
ALLOCATE_INDIVIDUALLY(extension_file)
|
||||
ALLOCATE_INDIVIDUALLY(generalisation)
|
||||
|
@ -257,7 +255,6 @@ void CoreModule::start(void) {
|
|||
@ Not all of our memory will be claimed in the form of structures: now and then
|
||||
we need to use the equivalent of traditional |malloc| and |calloc| routines.
|
||||
|
||||
@e EXTENSION_DICTIONARY_MREASON
|
||||
@e INDEX_SORTING_MREASON
|
||||
@e INSTANCE_COUNTING_MREASON
|
||||
@e MAP_INDEX_MREASON
|
||||
|
@ -271,7 +268,6 @@ we need to use the equivalent of traditional |malloc| and |calloc| routines.
|
|||
@e EMIT_ARRAY_MREASON
|
||||
|
||||
@<Register this module's memory allocation reasons@> =
|
||||
Memory::reason_name(EXTENSION_DICTIONARY_MREASON, "extension dictionary");
|
||||
Memory::reason_name(INDEX_SORTING_MREASON, "index sorting");
|
||||
Memory::reason_name(INSTANCE_COUNTING_MREASON, "instance-of-kind counting");
|
||||
Memory::reason_name(MAP_INDEX_MREASON, "map in the World index");
|
||||
|
@ -301,7 +297,6 @@ we need to use the equivalent of traditional |malloc| and |calloc| routines.
|
|||
@e DEBUGGING_LOG_CONTENTS_DA
|
||||
@e DESCRIPTION_COMPILATION_DA
|
||||
@e EXPRESSIONS_DA
|
||||
@e EXTENSIONS_CENSUS_DA
|
||||
@e FIGURE_CREATIONS_DA
|
||||
@e HEADINGS_DA
|
||||
@e IMPLICATIONS_DA
|
||||
|
@ -347,7 +342,6 @@ we need to use the equivalent of traditional |malloc| and |calloc| routines.
|
|||
Log::declare_aspect(DEBUGGING_LOG_CONTENTS_DA, L"debugging log contents", TRUE, FALSE);
|
||||
Log::declare_aspect(DESCRIPTION_COMPILATION_DA, L"description compilation", FALSE, FALSE);
|
||||
Log::declare_aspect(EXPRESSIONS_DA, L"expressions", FALSE, FALSE);
|
||||
Log::declare_aspect(EXTENSIONS_CENSUS_DA, L"extensions census", FALSE, FALSE);
|
||||
Log::declare_aspect(FIGURE_CREATIONS_DA, L"figure creations", FALSE, FALSE);
|
||||
Log::declare_aspect(HEADINGS_DA, L"headings", FALSE, FALSE);
|
||||
Log::declare_aspect(IMPLICATIONS_DA, L"implications", FALSE, TRUE);
|
||||
|
|
|
@ -23,7 +23,6 @@ char *AREA_NAME[3] = { "from .materials", "installed", "built in" };
|
|||
= (early code)
|
||||
linked_list *I7_nest_list = NULL;
|
||||
pathname *pathname_of_area[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||
pathname *pathname_of_extensions[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||
pathname *pathname_of_inter_resources[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||
pathname *pathname_of_languages[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||
pathname *pathname_of_website_templates[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||
|
@ -135,12 +134,21 @@ int Locations::set_defaults(int census_mode) {
|
|||
if ((census_mode) && (filename_of_i7_source))
|
||||
Problems::Fatal::issue("In census mode, no source text may be supplied");
|
||||
I7_nest_list = NEW_LINKED_LIST(inbuild_nest);
|
||||
if (pathname_of_area[MATERIALS_FS_AREA])
|
||||
ADD_TO_LINKED_LIST(Nests::new(pathname_of_area[MATERIALS_FS_AREA]), inbuild_nest, I7_nest_list);
|
||||
if (pathname_of_area[EXTERNAL_FS_AREA])
|
||||
ADD_TO_LINKED_LIST(Nests::new(pathname_of_area[EXTERNAL_FS_AREA]), inbuild_nest, I7_nest_list);
|
||||
if (pathname_of_area[INTERNAL_FS_AREA])
|
||||
ADD_TO_LINKED_LIST(Nests::new(pathname_of_area[INTERNAL_FS_AREA]), inbuild_nest, I7_nest_list);
|
||||
if (pathname_of_area[MATERIALS_FS_AREA]) {
|
||||
inbuild_nest *nest = Nests::new(pathname_of_area[MATERIALS_FS_AREA]);
|
||||
Nests::set_tag(nest, ORIGIN_WAS_MATERIALS_EXTENSIONS_AREA);
|
||||
ADD_TO_LINKED_LIST(nest, inbuild_nest, I7_nest_list);
|
||||
}
|
||||
if (pathname_of_area[EXTERNAL_FS_AREA]) {
|
||||
inbuild_nest *nest = Nests::new(pathname_of_area[EXTERNAL_FS_AREA]);
|
||||
Nests::set_tag(nest, ORIGIN_WAS_USER_EXTENSIONS_AREA);
|
||||
ADD_TO_LINKED_LIST(nest, inbuild_nest, I7_nest_list);
|
||||
}
|
||||
if (pathname_of_area[INTERNAL_FS_AREA]) {
|
||||
inbuild_nest *nest = Nests::new(pathname_of_area[INTERNAL_FS_AREA]);
|
||||
Nests::set_tag(nest, ORIGIN_WAS_BUILT_IN_EXTENSIONS_AREA);
|
||||
ADD_TO_LINKED_LIST(nest, inbuild_nest, I7_nest_list);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -476,7 +484,6 @@ template files, language definitions and website templates.
|
|||
|
||||
=
|
||||
void Locations::EILT_at(int area, pathname *P) {
|
||||
pathname_of_extensions[area] = Pathnames::subfolder(P, I"Extensions");
|
||||
pathname_of_languages[area] = Pathnames::subfolder(P, I"Languages");
|
||||
pathname_of_website_templates[area] = Pathnames::subfolder(P, I"Templates");
|
||||
pathname_of_inter_resources[area] = Pathnames::subfolder(P, I"Inter");
|
||||
|
|
|
@ -82,11 +82,11 @@ int SourceFiles::read_file(filename *F, text_stream *synopsis, extension_file *E
|
|||
int area = -1;
|
||||
if (EF)
|
||||
area = SourceFiles::read_file_inner(F, synopsis,
|
||||
pathname_of_extensions, NO_FS_AREAS + 1, documentation_only, &sf,
|
||||
I7_nest_list, documentation_only, &sf,
|
||||
STORE_POINTER_extension_file(EF), FALSE, EF);
|
||||
else
|
||||
area = SourceFiles::read_file_inner(F, synopsis,
|
||||
NULL, 0, documentation_only, &sf,
|
||||
NULL, documentation_only, &sf,
|
||||
STORE_POINTER_extension_file(NULL), TRUE, NULL);
|
||||
if (area == -1) {
|
||||
if (EF) {
|
||||
|
@ -136,7 +136,7 @@ application.
|
|||
|
||||
@ =
|
||||
int SourceFiles::read_file_inner(filename *F, text_stream *synopsis,
|
||||
pathname **list, int list_len, int documentation_only, source_file **S,
|
||||
linked_list *search_list, int documentation_only, source_file **S,
|
||||
general_pointer ref, int primary, extension_file *EF) {
|
||||
int origin_tried = 1;
|
||||
|
||||
|
@ -165,24 +165,20 @@ application to communicate the problem badly.
|
|||
|
||||
@<Set pathname and filename, and open file@> =
|
||||
handle = NULL;
|
||||
if (list) {
|
||||
TEMPORARY_TEXT(author_name);
|
||||
TEMPORARY_TEXT(title);
|
||||
WRITE_TO(author_name, "%W", EF->author_text);
|
||||
WRITE_TO(title, "%W", EF->title_text);
|
||||
for (int area=0; area<list_len; area++)
|
||||
if (handle == NULL) {
|
||||
pathname *P = list[area];
|
||||
origin_tried = area + 1;
|
||||
eventual = Locations::of_extension(P, title, author_name, TRUE);
|
||||
handle = Filenames::fopen_caseless(eventual, "r");
|
||||
if (handle == NULL) {
|
||||
eventual = Locations::of_extension(P, title, author_name, FALSE);
|
||||
handle = Filenames::fopen_caseless(eventual, "r");
|
||||
}
|
||||
}
|
||||
DISCARD_TEXT(author_name);
|
||||
DISCARD_TEXT(title);
|
||||
if (search_list) {
|
||||
text_stream *author_name = EF->ef_req->work->author_name;
|
||||
text_stream *title = EF->ef_req->work->title;
|
||||
inbuild_work *work = Works::new(extension_genre, title, author_name);
|
||||
inbuild_requirement *req = Model::requirement(work, VersionNumbers::null(), VersionNumbers::null());
|
||||
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
|
||||
Nests::locate(req, search_list, L);
|
||||
inbuild_search_result *search_result;
|
||||
LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) {
|
||||
eventual = search_result->copy->location_if_file;
|
||||
handle = Filenames::fopen_caseless(eventual, "r");
|
||||
origin_tried = Nests::get_tag(search_result->nest);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
handle = Filenames::fopen(F, "r");
|
||||
}
|
||||
|
|
|
@ -167,8 +167,8 @@ void Extensions::Dictionary::new_entry(text_stream *category, extension_file *ef
|
|||
}
|
||||
}
|
||||
|
||||
void Extensions::Dictionary::new_entry_from_stream(text_stream *category, extension_file *ef, text_stream *headword) {
|
||||
Extensions::Dictionary::new_dictionary_entry_raw(category, ef->ef_req->work->author_name, ef->ef_req->work->title, headword);
|
||||
void Extensions::Dictionary::new_entry_from_stream(text_stream *category, inform_extension *E, text_stream *headword) {
|
||||
Extensions::Dictionary::new_dictionary_entry_raw(category, E->as_copy->edition->work->author_name, E->as_copy->edition->work->title, headword);
|
||||
}
|
||||
|
||||
void Extensions::Dictionary::new_dictionary_entry_raw(text_stream *category,
|
||||
|
@ -280,7 +280,7 @@ void Extensions::Dictionary::load_helper(text_stream *line_entry,
|
|||
@h Time stamping.
|
||||
|
||||
=
|
||||
void Extensions::Dictionary::time_stamp(extension_file *ef) {
|
||||
void Extensions::Dictionary::time_stamp(inform_extension *E) {
|
||||
TEMPORARY_TEXT(dbuff);
|
||||
char *ascday[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
|
||||
char *ascmon[] = { "January", "February", "March", "April", "May", "June",
|
||||
|
@ -288,12 +288,11 @@ void Extensions::Dictionary::time_stamp(extension_file *ef) {
|
|||
WRITE_TO(dbuff, "%04d%02d%02d%02d%02d%02d/%d:%s %d %s %d %02d:%02d",
|
||||
the_present->tm_year+1900, the_present->tm_mon + 1, the_present->tm_mday,
|
||||
the_present->tm_hour, the_present->tm_min, the_present->tm_sec,
|
||||
TextFromFiles::total_word_count(
|
||||
Extensions::Files::source(ef)),
|
||||
TextFromFiles::total_word_count(E->read_into_file),
|
||||
ascday[the_present->tm_wday], the_present->tm_mday,
|
||||
ascmon[the_present->tm_mon], the_present->tm_year+1900,
|
||||
the_present->tm_hour, the_present->tm_min);
|
||||
Extensions::Dictionary::new_entry_from_stream(I"indexing", ef, dbuff);
|
||||
Extensions::Dictionary::new_entry_from_stream(I"indexing", E, dbuff);
|
||||
DISCARD_TEXT(dbuff);
|
||||
}
|
||||
|
||||
|
|
|
@ -245,7 +245,8 @@ documentation page could be forgiven for thinking it a miscellany.
|
|||
|
||||
@<Document and dictionary the definitions made in extension file ef@> =
|
||||
Extensions::Dictionary::erase_entries(ef);
|
||||
Extensions::Dictionary::time_stamp(ef);
|
||||
inform_extension *E = Extensions::Files::find(ef);
|
||||
if (E) Extensions::Dictionary::time_stamp(E);
|
||||
|
||||
@<Document and dictionary the kinds made in extension@>;
|
||||
@<Document and dictionary the objects made in extension@>;
|
||||
|
|
|
@ -24,12 +24,6 @@ because the application passes this as |-rules| on the command line, and it
|
|||
knows the location of the latter because this is standard and fixed for
|
||||
each platform: see Platform-Specific Definitions.
|
||||
|
||||
@ An extension has a title and an author name, each of which is limited in
|
||||
length to one character less than the following constants:
|
||||
|
||||
@d MAX_EXTENSION_TITLE_LENGTH 51
|
||||
@d MAX_EXTENSION_AUTHOR_LENGTH 51
|
||||
|
||||
@h How the application should install extensions.
|
||||
When the Inform 7 application looks at a file chosen by the user to
|
||||
be installed, it should look at the first line. (Note that this might have
|
||||
|
@ -577,9 +571,13 @@ two alternatives are expressed here:
|
|||
=
|
||||
void Extensions::Files::handle_census_mode(void) {
|
||||
if (census_mode) {
|
||||
extension_census *C = Extensions::Census::new(I7_nest_list);
|
||||
C->built_in_tag = ORIGIN_WAS_BUILT_IN_EXTENSIONS_AREA;
|
||||
C->materials_tag = ORIGIN_WAS_MATERIALS_EXTENSIONS_AREA;
|
||||
C->external_tag = ORIGIN_WAS_USER_EXTENSIONS_AREA;
|
||||
Extensions::Dictionary::load();
|
||||
Extensions::Census::perform();
|
||||
Extensions::Files::write_top_level_of_extensions_documentation();
|
||||
Extensions::Census::perform(C);
|
||||
Extensions::Files::write_top_level_of_extensions_documentation(C);
|
||||
Extensions::Files::write_sketchy_documentation_for_extensions_found();
|
||||
}
|
||||
}
|
||||
|
@ -587,8 +585,12 @@ void Extensions::Files::handle_census_mode(void) {
|
|||
void Extensions::Files::update_census(void) {
|
||||
extension_file *ef;
|
||||
Extensions::Dictionary::load();
|
||||
Extensions::Census::perform();
|
||||
Extensions::Files::write_top_level_of_extensions_documentation();
|
||||
extension_census *C = Extensions::Census::new(I7_nest_list);
|
||||
C->built_in_tag = ORIGIN_WAS_BUILT_IN_EXTENSIONS_AREA;
|
||||
C->materials_tag = ORIGIN_WAS_MATERIALS_EXTENSIONS_AREA;
|
||||
C->external_tag = ORIGIN_WAS_USER_EXTENSIONS_AREA;
|
||||
Extensions::Census::perform(C);
|
||||
Extensions::Files::write_top_level_of_extensions_documentation(C);
|
||||
LOOP_OVER(ef, extension_file) Extensions::Documentation::write_detailed(ef);
|
||||
Extensions::Files::write_sketchy_documentation_for_extensions_found();
|
||||
Extensions::Dictionary::write_back();
|
||||
|
@ -635,13 +637,13 @@ platform, for reasons as always to do with the vagaries of Internet
|
|||
Explorer 7 for Windows.
|
||||
|
||||
=
|
||||
void Extensions::Files::write_top_level_of_extensions_documentation(void) {
|
||||
Extensions::Files::write_top_level_extensions_page(I"Extensions.html", 1);
|
||||
Extensions::Files::write_top_level_extensions_page(I"ExtIndex.html", 2);
|
||||
void Extensions::Files::write_top_level_of_extensions_documentation(extension_census *C) {
|
||||
Extensions::Files::write_top_level_extensions_page(I"Extensions.html", 1, C);
|
||||
Extensions::Files::write_top_level_extensions_page(I"ExtIndex.html", 2, NULL);
|
||||
}
|
||||
|
||||
@ =
|
||||
void Extensions::Files::write_top_level_extensions_page(text_stream *leaf, int content) {
|
||||
void Extensions::Files::write_top_level_extensions_page(text_stream *leaf, int content, extension_census *C) {
|
||||
text_stream HOMEPAGE_struct;
|
||||
text_stream *OUT = &HOMEPAGE_struct;
|
||||
filename *F = Filenames::in_folder(pathname_of_extension_docs, leaf);
|
||||
|
@ -674,7 +676,7 @@ void Extensions::Files::write_top_level_extensions_page(text_stream *leaf, int c
|
|||
HTML_CLOSE("div");
|
||||
|
||||
switch (content) {
|
||||
case 1: Extensions::Census::write_results(OUT); break;
|
||||
case 1: Extensions::Census::write_results(OUT, C); break;
|
||||
case 2: Extensions::Dictionary::write_to_HTML(OUT); break;
|
||||
}
|
||||
|
||||
|
|
|
@ -263,8 +263,6 @@ In order that the numerical form of a version number should be a signed
|
|||
|N| be at most 999. It could in fact rise to 2146 without incident, but
|
||||
it seems cleaner to constrain the number of digits than the value.
|
||||
|
||||
@d MAX_VERSION_NUMBER_LENGTH 10 /* for |999/991231| */
|
||||
|
||||
=
|
||||
inbuild_version_number Extensions::Inclusion::parse_version(int vwn) {
|
||||
int i, slashes = 0, digits = 0, slash_at = 0;
|
||||
|
|
|
@ -69,7 +69,6 @@ which, we also handle extension installation, uninstallation and
|
|||
documentation here."
|
||||
Extension Files
|
||||
Including Extensions
|
||||
Extension Census
|
||||
Extension Dictionary
|
||||
Extension Documentation
|
||||
|
||||
|
|
|
@ -485,7 +485,8 @@ void Index::Lexicon::list_verbs_in_file(OUTPUT_STREAM, source_file *sf, extensio
|
|||
if (verb_count++ == 0) { HTML_OPEN("p"); WRITE("Verbs: "); } else WRITE(", ");
|
||||
if (lex->part_of_speech == VERB_LEXE) WRITE("to <b>%S</b>", entry_text);
|
||||
else WRITE("to be able to <b>%S</b>", entry_text);
|
||||
Extensions::Dictionary::new_entry_from_stream(I"verb", ef, entry_text);
|
||||
inform_extension *E = Extensions::Files::find(ef);
|
||||
Extensions::Dictionary::new_entry_from_stream(I"verb", E, entry_text);
|
||||
DISCARD_TEXT(entry_text);
|
||||
}
|
||||
if (verb_count > 0) HTML_CLOSE("p");
|
||||
|
|
|
@ -100,6 +100,7 @@ INBUILDX = inbuild/Tangled/inbuild
|
|||
|
||||
{tool} INBUILDTOOL inbuild inbuild
|
||||
{dep} INBUILDTOOL on FOUNDATION
|
||||
{dep} INBUILDTOOL on WORDS
|
||||
{dep} INBUILDTOOL on INBUILD
|
||||
|
||||
{tool} INTERTOOL inter inter
|
||||
|
|
Loading…
Reference in a new issue