mirror of
https://github.com/ganelson/inform.git
synced 2024-07-01 06:24:58 +03:00
Refactored inbuild
This commit is contained in:
parent
bee49ecc3d
commit
e412a7b601
|
@ -50,7 +50,7 @@ int main(int argc, char **argv) {
|
||||||
Errors::with_text("requirement malformed: %S", errors);
|
Errors::with_text("requirement malformed: %S", errors);
|
||||||
} else {
|
} else {
|
||||||
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
|
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
|
||||||
Nests::locate(req, nest_list, L);
|
Nests::search_for(req, nest_list, L);
|
||||||
inbuild_search_result *R;
|
inbuild_search_result *R;
|
||||||
LOOP_OVER_LINKED_LIST(R, inbuild_search_result, L) {
|
LOOP_OVER_LINKED_LIST(R, inbuild_search_result, L) {
|
||||||
ADD_TO_LINKED_LIST(R->copy, inbuild_copy, targets);
|
ADD_TO_LINKED_LIST(R->copy, inbuild_copy, targets);
|
||||||
|
@ -76,8 +76,8 @@ int main(int argc, char **argv) {
|
||||||
case GRAPH_TTASK: Graphs::describe(STDOUT, C->graph, TRUE); break;
|
case GRAPH_TTASK: Graphs::describe(STDOUT, C->graph, TRUE); break;
|
||||||
case BUILD_TTASK: Graphs::build(C->graph, BM); break;
|
case BUILD_TTASK: Graphs::build(C->graph, BM); break;
|
||||||
case REBUILD_TTASK: Graphs::rebuild(C->graph, BM); break;
|
case REBUILD_TTASK: Graphs::rebuild(C->graph, BM); break;
|
||||||
case COPY_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, FALSE); break;
|
case COPY_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, FALSE, BM); break;
|
||||||
case SYNC_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, TRUE); break;
|
case SYNC_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, TRUE, BM); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WordsModule::end();
|
WordsModule::end();
|
||||||
|
|
|
@ -57,8 +57,8 @@ void InbuildModule::start(void) {
|
||||||
@<Register this module's debugging log aspects@>;
|
@<Register this module's debugging log aspects@>;
|
||||||
@<Register this module's debugging log writers@>;
|
@<Register this module's debugging log writers@>;
|
||||||
@<Register this module's command line switches@>;
|
@<Register this module's command line switches@>;
|
||||||
Kits::start();
|
KitManager::start();
|
||||||
Extensions::start();
|
ExtensionManager::start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@
|
@
|
||||||
|
|
|
@ -103,17 +103,11 @@ build_methodology *BuildSteps::methodology(pathname *tools_path, int dev) {
|
||||||
void BuildSteps::execute(build_script *BS, build_methodology *meth) {
|
void BuildSteps::execute(build_script *BS, build_methodology *meth) {
|
||||||
build_step *S;
|
build_step *S;
|
||||||
LOOP_OVER_LINKED_LIST(S, build_step, BS->steps) {
|
LOOP_OVER_LINKED_LIST(S, build_step, BS->steps) {
|
||||||
switch (meth->methodology) {
|
|
||||||
case DRY_RUN_METHODOLOGY:
|
|
||||||
case SHELL_METHODOLOGY: {
|
|
||||||
TEMPORARY_TEXT(command);
|
TEMPORARY_TEXT(command);
|
||||||
@<Write a shell command for the step@>;
|
@<Write a shell command for the step@>;
|
||||||
WRITE_TO(STDOUT, "%S\n", command);
|
BuildSteps::shell(command, meth);
|
||||||
if (meth->methodology == SHELL_METHODOLOGY) Shell::run(command);
|
|
||||||
DISCARD_TEXT(command);
|
DISCARD_TEXT(command);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@<Write a shell command for the step@> =
|
@<Write a shell command for the step@> =
|
||||||
|
@ -125,3 +119,14 @@ void BuildSteps::execute(build_script *BS, build_methodology *meth) {
|
||||||
break;
|
break;
|
||||||
default: internal_error("unimplemented step");
|
default: internal_error("unimplemented step");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ =
|
||||||
|
void BuildSteps::shell(text_stream *command, build_methodology *meth) {
|
||||||
|
switch (meth->methodology) {
|
||||||
|
case DRY_RUN_METHODOLOGY:
|
||||||
|
case SHELL_METHODOLOGY: {
|
||||||
|
WRITE_TO(STDOUT, "%S\n", command);
|
||||||
|
if (meth->methodology == SHELL_METHODOLOGY) Shell::run(command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,8 @@ For example, "kit" and "extension" will both be both genres. There will be
|
||||||
few of these.
|
few of these.
|
||||||
|
|
||||||
@e GENRE_WRITE_WORK_MTID
|
@e GENRE_WRITE_WORK_MTID
|
||||||
@e GENRE_LOCATION_IN_NEST_MTID
|
@e GENRE_CLAIM_AS_COPY_MTID
|
||||||
|
@e GENRE_SEARCH_NEST_FOR_MTID
|
||||||
@e GENRE_COPY_TO_NEST_MTID
|
@e GENRE_COPY_TO_NEST_MTID
|
||||||
|
|
||||||
=
|
=
|
||||||
|
@ -18,8 +19,9 @@ typedef struct inbuild_genre {
|
||||||
} inbuild_genre;
|
} inbuild_genre;
|
||||||
|
|
||||||
VMETHOD_TYPE(GENRE_WRITE_WORK_MTID, inbuild_genre *gen, text_stream *OUT, inbuild_work *work)
|
VMETHOD_TYPE(GENRE_WRITE_WORK_MTID, inbuild_genre *gen, text_stream *OUT, inbuild_work *work)
|
||||||
VMETHOD_TYPE(GENRE_LOCATION_IN_NEST_MTID, inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results)
|
VMETHOD_TYPE(GENRE_CLAIM_AS_COPY_MTID, inbuild_genre *gen, inbuild_copy **C, text_stream *arg, text_stream *ext, int directory_status)
|
||||||
VMETHOD_TYPE(GENRE_COPY_TO_NEST_MTID, inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing)
|
VMETHOD_TYPE(GENRE_SEARCH_NEST_FOR_MTID, inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results)
|
||||||
|
VMETHOD_TYPE(GENRE_COPY_TO_NEST_MTID, inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing, build_methodology *meth)
|
||||||
|
|
||||||
@ =
|
@ =
|
||||||
inbuild_genre *Model::genre(text_stream *name) {
|
inbuild_genre *Model::genre(text_stream *name) {
|
||||||
|
@ -116,8 +118,10 @@ inbuild_copy *Model::claim(text_stream *arg) {
|
||||||
directory_status = TRUE;
|
directory_status = TRUE;
|
||||||
}
|
}
|
||||||
inbuild_copy *C = NULL;
|
inbuild_copy *C = NULL;
|
||||||
if (C == NULL) C = Kits::claim(arg, ext, directory_status);
|
inbuild_genre *G;
|
||||||
if (C == NULL) C = Extensions::claim(arg, ext, directory_status);
|
LOOP_OVER(G, inbuild_genre)
|
||||||
|
if (C == NULL)
|
||||||
|
VMETHOD_CALL(G, GENRE_CLAIM_AS_COPY_MTID, &C, arg, ext, directory_status);
|
||||||
DISCARD_TEXT(ext);
|
DISCARD_TEXT(ext);
|
||||||
return C;
|
return C;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,15 +64,22 @@ void Nests::add_to_search_sequence(linked_list *search_list, inbuild_nest *N) {
|
||||||
ADD_TO_LINKED_LIST(N, inbuild_nest, search_list);
|
ADD_TO_LINKED_LIST(N, inbuild_nest, search_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nests::locate(inbuild_requirement *req, linked_list *search_list, linked_list *results) {
|
void Nests::search_for(inbuild_requirement *req, linked_list *search_list, linked_list *results) {
|
||||||
inbuild_nest *N;
|
inbuild_nest *N;
|
||||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, search_list) {
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, search_list) {
|
||||||
inbuild_genre *G;
|
inbuild_genre *G;
|
||||||
LOOP_OVER(G, inbuild_genre)
|
LOOP_OVER(G, inbuild_genre)
|
||||||
VMETHOD_CALL(G, GENRE_LOCATION_IN_NEST_MTID, N, req, results);
|
VMETHOD_CALL(G, GENRE_SEARCH_NEST_FOR_MTID, N, req, results);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Nests::copy_to(inbuild_copy *C, inbuild_nest *destination_nest, int syncing) {
|
void Nests::copy_to(inbuild_copy *C, inbuild_nest *destination_nest, int syncing,
|
||||||
VMETHOD_CALL(C->edition->work->genre, GENRE_COPY_TO_NEST_MTID, C, destination_nest, syncing);
|
build_methodology *meth) {
|
||||||
|
VMETHOD_CALL(C->edition->work->genre, GENRE_COPY_TO_NEST_MTID, C, destination_nest, syncing, meth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Nests::overwrite_error(inbuild_nest *N, inbuild_copy *C) {
|
||||||
|
text_stream *ext = Str::new();
|
||||||
|
WRITE_TO(ext, "%X", C->edition->work);
|
||||||
|
Errors::with_text("already present (to overwrite, use -sync-to not -copy-to): '%S'", ext);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ typedef struct inbuild_requirement {
|
||||||
struct inbuild_work *work;
|
struct inbuild_work *work;
|
||||||
struct inbuild_version_number min_version;
|
struct inbuild_version_number min_version;
|
||||||
struct inbuild_version_number max_version;
|
struct inbuild_version_number max_version;
|
||||||
|
int allow_malformed;
|
||||||
MEMORY_MANAGEMENT
|
MEMORY_MANAGEMENT
|
||||||
} inbuild_requirement;
|
} inbuild_requirement;
|
||||||
|
|
||||||
|
@ -19,6 +20,7 @@ inbuild_requirement *Requirements::new(inbuild_work *work,
|
||||||
req->work = work;
|
req->work = work;
|
||||||
req->min_version = min;
|
req->min_version = min;
|
||||||
req->max_version = max;
|
req->max_version = max;
|
||||||
|
req->allow_malformed = FALSE;
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +76,18 @@ void Requirements::impose_clause(inbuild_requirement *req, text_stream *T, text_
|
||||||
Str::trim_white_space(value);
|
Str::trim_white_space(value);
|
||||||
|
|
||||||
if ((Str::len(clause) > 0) && (Str::len(value) > 0)) {
|
if ((Str::len(clause) > 0) && (Str::len(value) > 0)) {
|
||||||
if (Str::eq(clause, I"title")) Str::copy(req->work->title, value);
|
if (Str::eq(clause, I"genre")) {
|
||||||
|
inbuild_genre *G;
|
||||||
|
LOOP_OVER(G, inbuild_genre)
|
||||||
|
if (Str::eq_insensitive(G->genre_name, value)) {
|
||||||
|
req->work->genre = G;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (req->work->genre == NULL) {
|
||||||
|
if (Str::len(errors) == 0)
|
||||||
|
WRITE_TO(errors, "not a valid genre: '%S'", value);
|
||||||
|
}
|
||||||
|
} else if (Str::eq(clause, I"title")) Str::copy(req->work->title, value);
|
||||||
else if (Str::eq(clause, I"author")) Str::copy(req->work->author_name, value);
|
else if (Str::eq(clause, I"author")) Str::copy(req->work->author_name, value);
|
||||||
else if (Str::eq(clause, I"version")) {
|
else if (Str::eq(clause, I"version")) {
|
||||||
inbuild_version_number V = VersionNumbers::from_text(value);
|
inbuild_version_number V = VersionNumbers::from_text(value);
|
||||||
|
@ -118,6 +131,7 @@ int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) {
|
||||||
if (req->work->genre != edition->work->genre)
|
if (req->work->genre != edition->work->genre)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
if ((req->allow_malformed) && (Str::len(edition->work->title) == 0)) return TRUE;
|
||||||
if (Str::len(req->work->title) > 0) {
|
if (Str::len(req->work->title) > 0) {
|
||||||
if (Str::ne_insensitive(req->work->title, edition->work->title))
|
if (Str::ne_insensitive(req->work->title, edition->work->title))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
352
inbuild/inbuild-module/Chapter 3/Extension Manager.w
Normal file
352
inbuild/inbuild-module/Chapter 3/Extension Manager.w
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
[ExtensionManager::] Extension Manager.
|
||||||
|
|
||||||
|
An Inform 7 extension.
|
||||||
|
|
||||||
|
@h Genre definition.
|
||||||
|
|
||||||
|
= (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
|
||||||
|
|
||||||
|
@ =
|
||||||
|
void ExtensionManager::start(void) {
|
||||||
|
extension_genre = Model::genre(I"extension");
|
||||||
|
METHOD_ADD(extension_genre, GENRE_WRITE_WORK_MTID, ExtensionManager::write_work);
|
||||||
|
METHOD_ADD(extension_genre, GENRE_CLAIM_AS_COPY_MTID, ExtensionManager::claim_as_copy);
|
||||||
|
METHOD_ADD(extension_genre, GENRE_SEARCH_NEST_FOR_MTID, ExtensionManager::search_nest_for);
|
||||||
|
METHOD_ADD(extension_genre, GENRE_COPY_TO_NEST_MTID, ExtensionManager::copy_to_nest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionManager::write_work(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) {
|
||||||
|
WRITE("%X", work);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ Extensions live in their namesake subdirectory of a nest:
|
||||||
|
|
||||||
|
=
|
||||||
|
pathname *ExtensionManager::path_within_nest(inbuild_nest *N) {
|
||||||
|
if (N == NULL) internal_error("no nest");
|
||||||
|
return Pathnames::subfolder(N->location, I"Extensions");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ Extension copies are annotated with a structure called an |inform_extension|,
|
||||||
|
which stores data about extensions used by the Inform compiler.
|
||||||
|
|
||||||
|
=
|
||||||
|
inform_extension *ExtensionManager::from_copy(inbuild_copy *C) {
|
||||||
|
if ((C) && (C->edition->work->genre == extension_genre)) {
|
||||||
|
return RETRIEVE_POINTER_inform_extension(C->content);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuild_copy *ExtensionManager::new_copy(inbuild_edition *edition, filename *F) {
|
||||||
|
inform_extension *E = Extensions::new_ie();
|
||||||
|
inbuild_copy *C = Model::copy_in_file(edition, F, STORE_POINTER_inform_extension(E));
|
||||||
|
E->as_copy = C;
|
||||||
|
return C;
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Claiming.
|
||||||
|
Here |arg| is a textual form of a filename or pathname, such as may have been
|
||||||
|
supplied at the command line; |ext| is a substring of it, and is its extension
|
||||||
|
(e.g., |jpg| if |arg| is |Geraniums.jpg|), or is empty if there isn't one;
|
||||||
|
|directory_status| is true if we know for some reason that this is a directory
|
||||||
|
not a file, false if we know the reverse, and otherwise not applicable.
|
||||||
|
|
||||||
|
An extension, for us, needs to be a file with extension |i7x|, but it needs
|
||||||
|
also to scan properly -- which means the top line of the file has to be right.
|
||||||
|
So we'll open it and look.
|
||||||
|
|
||||||
|
=
|
||||||
|
void ExtensionManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C,
|
||||||
|
text_stream *arg, text_stream *ext, int directory_status) {
|
||||||
|
if (directory_status == TRUE) return;
|
||||||
|
if (Str::eq_insensitive(ext, I"i7x")) {
|
||||||
|
filename *F = Filenames::from_text(arg);
|
||||||
|
*C = ExtensionManager::claim_file_as_copy(F, NULL, FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuild_copy *ExtensionManager::claim_file_as_copy(filename *F, text_stream *error_text,
|
||||||
|
int allow_malformed) {
|
||||||
|
if ((allow_malformed) && (TextFiles::exists(F) == FALSE)) return NULL;
|
||||||
|
TEMPORARY_TEXT(author);
|
||||||
|
TEMPORARY_TEXT(title);
|
||||||
|
TEMPORARY_TEXT(rubric_text);
|
||||||
|
TEMPORARY_TEXT(requirement_text);
|
||||||
|
inbuild_version_number V =
|
||||||
|
ExtensionManager::scan_file(F, title, author, rubric_text, requirement_text, error_text);
|
||||||
|
inbuild_copy *C = ExtensionManager::new_copy(
|
||||||
|
Model::edition(Works::new(extension_genre, title, author), V), F);
|
||||||
|
if ((allow_malformed) || (Str::len(error_text) == 0)) {
|
||||||
|
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||||
|
ExtensionManager::build_graph(C);
|
||||||
|
} else {
|
||||||
|
C = NULL;
|
||||||
|
}
|
||||||
|
DISCARD_TEXT(author);
|
||||||
|
DISCARD_TEXT(title);
|
||||||
|
DISCARD_TEXT(rubric_text);
|
||||||
|
DISCARD_TEXT(requirement_text);
|
||||||
|
return C;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ The following scans a potential extension file. If it seems malformed, a
|
||||||
|
suitable error is written to the stream |error_text|. If not, this is left
|
||||||
|
alone, and the version number is returned.
|
||||||
|
|
||||||
|
=
|
||||||
|
inbuild_version_number ExtensionManager::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, "this 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ 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 Inform 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 {
|
||||||
|
if (error_text) WRITE_TO(error_text,
|
||||||
|
"the first line of this file does not end 'begin(s) here'");
|
||||||
|
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,
|
||||||
|
"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]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Searching.
|
||||||
|
Here we look through a nest to find all extensions matching the supplied
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
For efficiency's sake, since the nest could contain many hundreds of
|
||||||
|
extensions, we narrow down to the author's subfolder if a specific
|
||||||
|
author is required, and to the specific extension file if title is
|
||||||
|
also known. (In particular, this happens when the Inform compiler is
|
||||||
|
using us to search for, say, Locksmith by Emily Short.)
|
||||||
|
|
||||||
|
Nobody should any longer be storing extension files without the file
|
||||||
|
extension |.i7x|, but this was allowed in the early days of Inform 7,
|
||||||
|
so we'll quietly allow for it.
|
||||||
|
|
||||||
|
=
|
||||||
|
void ExtensionManager::search_nest_for(inbuild_genre *gen, inbuild_nest *N,
|
||||||
|
inbuild_requirement *req, linked_list *search_results) {
|
||||||
|
pathname *P = ExtensionManager::path_within_nest(N);
|
||||||
|
if (Str::len(req->work->author_name) > 0) {
|
||||||
|
pathname *Q = Pathnames::subfolder(P, req->work->author_name);
|
||||||
|
if (Str::len(req->work->title) > 0) {
|
||||||
|
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(Q, leaf);
|
||||||
|
ExtensionManager::search_nest_for_single_file(F, N, req, search_results);
|
||||||
|
DISCARD_TEXT(leaf);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ExtensionManager::search_nest_for_r(Q, N, req, search_results);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ExtensionManager::search_nest_for_r(P, N, req, search_results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionManager::search_nest_for_r(pathname *P, inbuild_nest *N,
|
||||||
|
inbuild_requirement *req, linked_list *search_results) {
|
||||||
|
scan_directory *D = Directories::open(P);
|
||||||
|
if (D) {
|
||||||
|
TEMPORARY_TEXT(LEAFNAME);
|
||||||
|
while (Directories::next(D, LEAFNAME)) {
|
||||||
|
if (Str::get_last_char(LEAFNAME) == FOLDER_SEPARATOR) {
|
||||||
|
Str::delete_last_character(LEAFNAME);
|
||||||
|
pathname *Q = Pathnames::subfolder(P, LEAFNAME);
|
||||||
|
ExtensionManager::search_nest_for_r(Q, N, req, search_results);
|
||||||
|
} else {
|
||||||
|
filename *F = Filenames::in_folder(P, LEAFNAME);
|
||||||
|
ExtensionManager::search_nest_for_single_file(F, N, req, search_results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DISCARD_TEXT(LEAFNAME);
|
||||||
|
Directories::close(D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionManager::search_nest_for_single_file(filename *F, inbuild_nest *N,
|
||||||
|
inbuild_requirement *req, linked_list *search_results) {
|
||||||
|
inbuild_copy *C = ExtensionManager::claim_file_as_copy(F, NULL, req->allow_malformed);
|
||||||
|
if ((C) && (Requirements::meets(C->edition, req))) {
|
||||||
|
Nests::add_search_result(search_results, N, C);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Copying.
|
||||||
|
Now the task is to copy an extension into place in a nest. This is easy,
|
||||||
|
since an extension is a single file; to sync, we just overwrite.
|
||||||
|
|
||||||
|
=
|
||||||
|
filename *ExtensionManager::filename_in_nest(inbuild_nest *N,
|
||||||
|
text_stream *title, text_stream *author) {
|
||||||
|
pathname *E = ExtensionManager::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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExtensionManager::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N,
|
||||||
|
int syncing, build_methodology *meth) {
|
||||||
|
pathname *E = ExtensionManager::path_within_nest(N);
|
||||||
|
TEMPORARY_TEXT(leaf);
|
||||||
|
WRITE_TO(leaf, "%S.i7x", C->edition->work->title);
|
||||||
|
filename *F = Filenames::in_folder(
|
||||||
|
Pathnames::subfolder(E, C->edition->work->author_name), leaf);
|
||||||
|
DISCARD_TEXT(leaf);
|
||||||
|
|
||||||
|
if (TextFiles::exists(F)) {
|
||||||
|
if (syncing == FALSE) { Nests::overwrite_error(N, C); return; }
|
||||||
|
} else {
|
||||||
|
if (meth->methodology == DRY_RUN_METHODOLOGY) {
|
||||||
|
TEMPORARY_TEXT(command);
|
||||||
|
WRITE_TO(command, "mkdir -p ");
|
||||||
|
Shell::quote_path(command, Filenames::get_path_to(F));
|
||||||
|
WRITE_TO(STDOUT, "%S\n", command);
|
||||||
|
DISCARD_TEXT(command);
|
||||||
|
} else {
|
||||||
|
Pathnames::create_in_file_system(N->location);
|
||||||
|
Pathnames::create_in_file_system(Pathnames::subfolder(N->location, I"Extensions"));
|
||||||
|
Pathnames::create_in_file_system(Filenames::get_path_to(F));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEMPORARY_TEXT(command);
|
||||||
|
WRITE_TO(command, "cp -f ");
|
||||||
|
Shell::quote_file(command, C->location_if_file);
|
||||||
|
Shell::quote_file(command, F);
|
||||||
|
BuildSteps::shell(command, meth);
|
||||||
|
DISCARD_TEXT(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Build graph.
|
||||||
|
The build graph for an extension is just a single node: you don't need to
|
||||||
|
build an extension at all.
|
||||||
|
|
||||||
|
=
|
||||||
|
void ExtensionManager::build_graph(inbuild_copy *C) {
|
||||||
|
Graphs::copy_vertex(C);
|
||||||
|
}
|
|
@ -1,297 +0,0 @@
|
||||||
[Extensions::] Extensions.
|
|
||||||
|
|
||||||
An Inform 7 extension.
|
|
||||||
|
|
||||||
@h Kits.
|
|
||||||
|
|
||||||
= (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");
|
|
||||||
METHOD_ADD(extension_genre, GENRE_WRITE_WORK_MTID, Extensions::write_work);
|
|
||||||
METHOD_ADD(extension_genre, GENRE_LOCATION_IN_NEST_MTID, Extensions::location_in_nest);
|
|
||||||
METHOD_ADD(extension_genre, GENRE_COPY_TO_NEST_MTID, Extensions::copy_to_nest);
|
|
||||||
}
|
|
||||||
|
|
||||||
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")) {
|
|
||||||
filename *F = Filenames::from_text(arg);
|
|
||||||
return Extensions::claim_file(F);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
inbuild_copy *Extensions::claim_file(filename *F) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Extensions::write_work(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) {
|
|
||||||
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);
|
|
||||||
if ((Str::len(req->work->title) > 0) && (Str::len(req->work->author_name) > 0)) {
|
|
||||||
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 (Requirements::meets(E->as_copy->edition, req)) {
|
|
||||||
Nests::add_search_result(search_results, N, E->as_copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DISCARD_TEXT(leaf);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Extensions::location_recursively(P, N, req, search_results);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Extensions::location_recursively(pathname *P, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results) {
|
|
||||||
scan_directory *D = Directories::open(P);
|
|
||||||
if (D) {
|
|
||||||
TEMPORARY_TEXT(LEAFNAME);
|
|
||||||
while (Directories::next(D, LEAFNAME)) {
|
|
||||||
if (Str::get_last_char(LEAFNAME) == FOLDER_SEPARATOR) {
|
|
||||||
pathname *Q = Pathnames::subfolder(P, LEAFNAME);
|
|
||||||
Extensions::location_recursively(Q, N, req, search_results);
|
|
||||||
} else {
|
|
||||||
if (Str::suffix_eq(LEAFNAME, I".i7x", 4)) {
|
|
||||||
filename *F = Filenames::in_folder(P, LEAFNAME);
|
|
||||||
inbuild_copy *C = Extensions::claim_file(F);
|
|
||||||
if (Requirements::meets(C->edition, req)) {
|
|
||||||
Nests::add_search_result(search_results, N, C);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DISCARD_TEXT(LEAFNAME);
|
|
||||||
Directories::close(D);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Extensions::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing) {
|
|
||||||
internal_error("unimplemented");
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct inform_extension {
|
|
||||||
struct inbuild_copy *as_copy;
|
|
||||||
struct inbuild_version_number version_loaded; /* As actually loaded */
|
|
||||||
#ifdef CORE_MODULE
|
|
||||||
struct wording body_text; /* Body of source text supplied in extension, if any */
|
|
||||||
int body_text_unbroken; /* Does this contain text waiting to be sentence-broken? */
|
|
||||||
struct wording documentation_text; /* Documentation supplied in extension, if any */
|
|
||||||
int loaded_from_built_in_area; /* Located within Inform application */
|
|
||||||
int authorial_modesty; /* Do not credit in the compiled game */
|
|
||||||
struct source_file *read_into_file; /* Which source file loaded this */
|
|
||||||
struct text_stream *rubric_as_lexed;
|
|
||||||
struct text_stream *extra_credit_as_lexed;
|
|
||||||
#endif
|
|
||||||
MEMORY_MANAGEMENT
|
|
||||||
} inform_extension;
|
|
||||||
|
|
||||||
inform_extension *Extensions::load_at(text_stream *title, text_stream *author, filename *F) {
|
|
||||||
inform_extension *E = CREATE(inform_extension);
|
|
||||||
|
|
||||||
inbuild_work *work = Works::new(extension_genre, title, author);
|
|
||||||
inbuild_edition *edition = Model::edition(work, VersionNumbers::null());
|
|
||||||
E->as_copy = Model::copy_in_file(edition, F, STORE_POINTER_inform_extension(E));
|
|
||||||
|
|
||||||
E->version_loaded = VersionNumbers::null();
|
|
||||||
|
|
||||||
#ifdef CORE_MODULE
|
|
||||||
E->body_text = EMPTY_WORDING;
|
|
||||||
E->body_text_unbroken = FALSE;
|
|
||||||
E->documentation_text = EMPTY_WORDING;
|
|
||||||
E->loaded_from_built_in_area = FALSE;
|
|
||||||
E->authorial_modesty = FALSE;
|
|
||||||
E->rubric_as_lexed = NULL;
|
|
||||||
E->extra_credit_as_lexed = NULL;
|
|
||||||
#endif
|
|
||||||
// build_graph *EV =
|
|
||||||
Graphs::copy_vertex(E->as_copy);
|
|
||||||
return E;
|
|
||||||
}
|
|
||||||
|
|
||||||
inform_extension *Extensions::from_copy(inbuild_copy *C) {
|
|
||||||
if ((C) && (C->edition->work->genre == extension_genre)) {
|
|
||||||
return RETRIEVE_POINTER_inform_extension(C->content);
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
201
inbuild/inbuild-module/Chapter 3/Kit Manager.w
Normal file
201
inbuild/inbuild-module/Chapter 3/Kit Manager.w
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
[KitManager::] Kit Manager.
|
||||||
|
|
||||||
|
A kit is a combination of Inter code with an Inform 7 extension.
|
||||||
|
|
||||||
|
@h Genre definition.
|
||||||
|
|
||||||
|
=
|
||||||
|
inbuild_genre *kit_genre = NULL;
|
||||||
|
void KitManager::start(void) {
|
||||||
|
kit_genre = Model::genre(I"kit");
|
||||||
|
METHOD_ADD(kit_genre, GENRE_WRITE_WORK_MTID, KitManager::write_work);
|
||||||
|
METHOD_ADD(kit_genre, GENRE_CLAIM_AS_COPY_MTID, KitManager::claim_as_copy);
|
||||||
|
METHOD_ADD(kit_genre, GENRE_SEARCH_NEST_FOR_MTID, KitManager::search_nest_for);
|
||||||
|
METHOD_ADD(kit_genre, GENRE_COPY_TO_NEST_MTID, KitManager::copy_to_nest);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KitManager::write_work(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) {
|
||||||
|
WRITE("%S", work->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ Kits live in the |Inter| subdirectory of a nest:
|
||||||
|
|
||||||
|
=
|
||||||
|
pathname *KitManager::path_within_nest(inbuild_nest *N) {
|
||||||
|
if (N == NULL) internal_error("no nest");
|
||||||
|
return Pathnames::subfolder(N->location, I"Inter");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ Kit copies are annotated with a structure called an |inform_kit|,
|
||||||
|
which stores data about extensions used by the Inform compiler.
|
||||||
|
|
||||||
|
=
|
||||||
|
inform_kit *KitManager::from_copy(inbuild_copy *C) {
|
||||||
|
if ((C) && (C->edition->work->genre == kit_genre)) {
|
||||||
|
return RETRIEVE_POINTER_inform_kit(C->content);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuild_copy *KitManager::new_copy(text_stream *name, pathname *P) {
|
||||||
|
inform_kit *K = Kits::new_ik(name, P);
|
||||||
|
inbuild_work *work = Works::new(kit_genre, Str::duplicate(name), NULL);
|
||||||
|
inbuild_edition *edition = Model::edition(work, K->version);
|
||||||
|
K->as_copy = Model::copy_in_directory(edition, P, STORE_POINTER_inform_kit(K));
|
||||||
|
return K->as_copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Claiming.
|
||||||
|
Here |arg| is a textual form of a filename or pathname, such as may have been
|
||||||
|
supplied at the command line; |ext| is a substring of it, and is its extension
|
||||||
|
(e.g., |jpg| if |arg| is |Geraniums.jpg|), or is empty if there isn't one;
|
||||||
|
|directory_status| is true if we know for some reason that this is a directory
|
||||||
|
not a file, false if we know the reverse, and otherwise not applicable.
|
||||||
|
|
||||||
|
A kit needs to be a directory whose name ends in |Kit|, and which contains
|
||||||
|
a valid metadata file.
|
||||||
|
|
||||||
|
=
|
||||||
|
void KitManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C,
|
||||||
|
text_stream *arg, text_stream *ext, int directory_status) {
|
||||||
|
if (directory_status == FALSE) return;
|
||||||
|
int kitpos = Str::len(arg) - 3;
|
||||||
|
if ((kitpos >= 0) && (Str::get_at(arg, kitpos) == 'K') &&
|
||||||
|
(Str::get_at(arg, kitpos+1) == 'i') &&
|
||||||
|
(Str::get_at(arg, kitpos+2) == 't')) {
|
||||||
|
pathname *P = Pathnames::from_text(arg);
|
||||||
|
*C = KitManager::claim_folder_as_copy(P);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inbuild_copy *KitManager::claim_folder_as_copy(pathname *P) {
|
||||||
|
filename *canary = Filenames::in_folder(P, I"kit_metadata.txt");
|
||||||
|
if (TextFiles::exists(canary)) {
|
||||||
|
inbuild_copy *C = KitManager::new_copy(Pathnames::directory_name(P), P);
|
||||||
|
KitManager::build_graph(C);
|
||||||
|
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||||
|
return C;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Searching.
|
||||||
|
Here we look through a nest to find all kits matching the supplied
|
||||||
|
requirements.
|
||||||
|
|
||||||
|
=
|
||||||
|
void KitManager::search_nest_for(inbuild_genre *gen, inbuild_nest *N,
|
||||||
|
inbuild_requirement *req, linked_list *search_results) {
|
||||||
|
pathname *P = KitManager::path_within_nest(N);
|
||||||
|
scan_directory *D = Directories::open(P);
|
||||||
|
if (D) {
|
||||||
|
TEMPORARY_TEXT(LEAFNAME);
|
||||||
|
while (Directories::next(D, LEAFNAME)) {
|
||||||
|
if (Str::get_last_char(LEAFNAME) == FOLDER_SEPARATOR) {
|
||||||
|
Str::delete_last_character(LEAFNAME);
|
||||||
|
pathname *Q = Pathnames::subfolder(P, LEAFNAME);
|
||||||
|
inbuild_copy *C = KitManager::claim_folder_as_copy(Q);
|
||||||
|
if ((C) && (Requirements::meets(C->edition, req))) {
|
||||||
|
Nests::add_search_result(search_results, N, C);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DISCARD_TEXT(LEAFNAME);
|
||||||
|
Directories::close(D);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Copying.
|
||||||
|
Now the task is to copy a kit into place in a nest. Since a kit is a folder,
|
||||||
|
we need to |rsync| it.
|
||||||
|
|
||||||
|
=
|
||||||
|
pathname *KitManager::pathname_in_nest(inbuild_nest *N, inbuild_work *W) {
|
||||||
|
return Pathnames::subfolder(KitManager::path_within_nest(N), W->title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KitManager::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N,
|
||||||
|
int syncing, build_methodology *meth) {
|
||||||
|
pathname *dest_kit = KitManager::pathname_in_nest(N, C->edition->work);
|
||||||
|
filename *dest_kit_metadata = Filenames::in_folder(dest_kit, I"kit_metadata.txt");
|
||||||
|
if (TextFiles::exists(dest_kit_metadata)) {
|
||||||
|
if (syncing == FALSE) { Nests::overwrite_error(N, C); return; }
|
||||||
|
} else {
|
||||||
|
if (meth->methodology == DRY_RUN_METHODOLOGY) {
|
||||||
|
TEMPORARY_TEXT(command);
|
||||||
|
WRITE_TO(command, "mkdir -p ");
|
||||||
|
Shell::quote_path(command, dest_kit);
|
||||||
|
WRITE_TO(STDOUT, "%S\n", command);
|
||||||
|
DISCARD_TEXT(command);
|
||||||
|
} else {
|
||||||
|
Pathnames::create_in_file_system(N->location);
|
||||||
|
Pathnames::create_in_file_system(KitManager::path_within_nest(N));
|
||||||
|
Pathnames::create_in_file_system(dest_kit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (meth->methodology == DRY_RUN_METHODOLOGY) {
|
||||||
|
TEMPORARY_TEXT(command);
|
||||||
|
WRITE_TO(command, "rsync -a --delete ");
|
||||||
|
Shell::quote_path(command, C->location_if_path);
|
||||||
|
Shell::quote_path(command, dest_kit);
|
||||||
|
WRITE_TO(STDOUT, "%S\n", command);
|
||||||
|
DISCARD_TEXT(command);
|
||||||
|
} else {
|
||||||
|
Pathnames::rsync(C->location_if_path, dest_kit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@h Build graph.
|
||||||
|
The build graph for a kit is quite extensive, since a kit contains Inter
|
||||||
|
binaries for four different architectures; and each of those has a
|
||||||
|
dependency on every section file of the web of Inform 6 source for the kit.
|
||||||
|
If there are $S$ sections then the graph has $S+5$ vertices and $4(S+1)$ edges.
|
||||||
|
|
||||||
|
=
|
||||||
|
void KitManager::build_graph(inbuild_copy *C) {
|
||||||
|
pathname *P = C->location_if_path;
|
||||||
|
build_graph *KV = Graphs::copy_vertex(C);
|
||||||
|
text_stream *archs[4] = { I"16", I"32", I"16d", I"32d" };
|
||||||
|
text_stream *binaries[4] = { I"arch-16.interb", I"arch-32.interb", I"arch-16d.interb", I"arch-32d.interb" };
|
||||||
|
build_graph *BV[4];
|
||||||
|
for (int i=0; i<4; i++) {
|
||||||
|
filename *FV = Filenames::in_folder(P, binaries[i]);
|
||||||
|
BV[i] = Graphs::internal_vertex(FV);
|
||||||
|
Graphs::arrow(KV, BV[i]);
|
||||||
|
build_step *BS = BuildSteps::new_step(ASSIMILATE_BSTEP, P, archs[i]);
|
||||||
|
BuildSteps::add_step(BV[i]->script, BS);
|
||||||
|
}
|
||||||
|
|
||||||
|
filename *contents_page = Filenames::in_folder(C->location_if_path, I"Contents.w");
|
||||||
|
build_graph *CV = Graphs::internal_vertex(contents_page);
|
||||||
|
for (int i=0; i<4; i++) Graphs::arrow(BV[i], CV);
|
||||||
|
|
||||||
|
kit_contents_section_state CSS;
|
||||||
|
CSS.active = FALSE;
|
||||||
|
CSS.sects = NEW_LINKED_LIST(text_stream);
|
||||||
|
TextFiles::read(contents_page, FALSE, NULL, FALSE, KitManager::read_contents, NULL, (void *) &CSS);
|
||||||
|
text_stream *segment;
|
||||||
|
LOOP_OVER_LINKED_LIST(segment, text_stream, CSS.sects) {
|
||||||
|
filename *SF = Filenames::in_folder(
|
||||||
|
Pathnames::subfolder(C->location_if_path, I"Sections"), segment);
|
||||||
|
build_graph *SV = Graphs::internal_vertex(SF);
|
||||||
|
for (int i=0; i<4; i++) Graphs::arrow(BV[i], SV);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct kit_contents_section_state {
|
||||||
|
struct linked_list *sects; /* of |text_stream| */
|
||||||
|
int active;
|
||||||
|
} kit_contents_section_state;
|
||||||
|
|
||||||
|
void KitManager::read_contents(text_stream *text, text_file_position *tfp, void *state) {
|
||||||
|
kit_contents_section_state *CSS = (kit_contents_section_state *) state;
|
||||||
|
match_results mr = Regexp::create_mr();
|
||||||
|
if (Regexp::match(&mr, text, L"Sections"))
|
||||||
|
CSS->active = TRUE;
|
||||||
|
if ((Regexp::match(&mr, text, L" (%c+)")) && (CSS->active)) {
|
||||||
|
WRITE_TO(mr.exp[0], ".i6t");
|
||||||
|
ADD_TO_LINKED_LIST(Str::duplicate(mr.exp[0]), text_stream, CSS->sects);
|
||||||
|
}
|
||||||
|
Regexp::dispose_of(&mr);
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ pathname *Extensions::Census::internal_path(extension_census *C) {
|
||||||
inbuild_nest *N = NULL;
|
inbuild_nest *N = NULL;
|
||||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
||||||
if (Nests::get_tag(N) == C->built_in_tag)
|
if (Nests::get_tag(N) == C->built_in_tag)
|
||||||
return Extensions::path_within_nest(N);
|
return ExtensionManager::path_within_nest(N);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ pathname *Extensions::Census::external_path(extension_census *C) {
|
||||||
inbuild_nest *N = NULL;
|
inbuild_nest *N = NULL;
|
||||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, C->search_list)
|
||||||
if (Nests::get_tag(N) == C->external_tag)
|
if (Nests::get_tag(N) == C->external_tag)
|
||||||
return Extensions::path_within_nest(N);
|
return ExtensionManager::path_within_nest(N);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ any number of other domains. The following code scans one.
|
||||||
inbuild_nest *current_extension_domain = NULL;
|
inbuild_nest *current_extension_domain = NULL;
|
||||||
void Extensions::Census::take_census_of_domain(extension_census *C, inbuild_nest *N, int origin) {
|
void Extensions::Census::take_census_of_domain(extension_census *C, inbuild_nest *N, int origin) {
|
||||||
current_extension_domain = N;
|
current_extension_domain = N;
|
||||||
pathname *P = Extensions::path_within_nest(N);
|
pathname *P = ExtensionManager::path_within_nest(N);
|
||||||
Extensions::Census::census_from(C, N, P, TRUE, origin, NULL);
|
Extensions::Census::census_from(C, N, P, TRUE, origin, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,10 +296,10 @@ extract its titling line and rubric, then close it again.
|
||||||
|
|
||||||
@<Scan the extension file@> =
|
@<Scan the extension file@> =
|
||||||
filename *F =
|
filename *F =
|
||||||
Extensions::filename_in_nest(current_extension_domain,
|
ExtensionManager::filename_in_nest(current_extension_domain,
|
||||||
item_name, cs->parent);
|
item_name, cs->parent);
|
||||||
TEMPORARY_TEXT(error_text);
|
TEMPORARY_TEXT(error_text);
|
||||||
V = Extensions::scan_file(F, claimed_title, claimed_author_name,
|
V = ExtensionManager::scan_file(F, claimed_title, claimed_author_name,
|
||||||
rubric_text, requirement_text, error_text);
|
rubric_text, requirement_text, error_text);
|
||||||
if (Str::len(error_text) > 0) {
|
if (Str::len(error_text) > 0) {
|
||||||
Extensions::Census::census_error(error_text,
|
Extensions::Census::census_error(error_text,
|
||||||
|
@ -900,7 +900,7 @@ the first and last word and just look at what is in between:
|
||||||
if (ecd->built_in) HTML_TAG_WITH("img", "%s", opener)
|
if (ecd->built_in) HTML_TAG_WITH("img", "%s", opener)
|
||||||
else {
|
else {
|
||||||
#ifdef INDEX_MODULE
|
#ifdef INDEX_MODULE
|
||||||
pathname *area = Extensions::path_within_nest(ecd->domain);
|
pathname *area = ExtensionManager::path_within_nest(ecd->domain);
|
||||||
HTML::Javascript::open_file(OUT, area, ecd->ecd_work->raw_author_name, opener);
|
HTML::Javascript::open_file(OUT, area, ecd->ecd_work->raw_author_name, opener);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
35
inbuild/inbuild-module/Chapter 4/Extension Services.w
Normal file
35
inbuild/inbuild-module/Chapter 4/Extension Services.w
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
[Extensions::] Extensions.
|
||||||
|
|
||||||
|
An Inform 7 extension.
|
||||||
|
|
||||||
|
@ =
|
||||||
|
typedef struct inform_extension {
|
||||||
|
struct inbuild_copy *as_copy;
|
||||||
|
#ifdef CORE_MODULE
|
||||||
|
struct wording body_text; /* Body of source text supplied in extension, if any */
|
||||||
|
int body_text_unbroken; /* Does this contain text waiting to be sentence-broken? */
|
||||||
|
struct wording documentation_text; /* Documentation supplied in extension, if any */
|
||||||
|
int loaded_from_built_in_area; /* Located within Inform application */
|
||||||
|
int authorial_modesty; /* Do not credit in the compiled game */
|
||||||
|
struct source_file *read_into_file; /* Which source file loaded this */
|
||||||
|
struct text_stream *rubric_as_lexed;
|
||||||
|
struct text_stream *extra_credit_as_lexed;
|
||||||
|
#endif
|
||||||
|
MEMORY_MANAGEMENT
|
||||||
|
} inform_extension;
|
||||||
|
|
||||||
|
inform_extension *Extensions::new_ie(void) {
|
||||||
|
inform_extension *E = CREATE(inform_extension);
|
||||||
|
E->as_copy = NULL;
|
||||||
|
#ifdef CORE_MODULE
|
||||||
|
E->body_text = EMPTY_WORDING;
|
||||||
|
E->body_text_unbroken = FALSE;
|
||||||
|
E->documentation_text = EMPTY_WORDING;
|
||||||
|
E->loaded_from_built_in_area = FALSE;
|
||||||
|
E->authorial_modesty = FALSE;
|
||||||
|
E->rubric_as_lexed = NULL;
|
||||||
|
E->extra_credit_as_lexed = NULL;
|
||||||
|
E->read_into_file = NULL;
|
||||||
|
#endif
|
||||||
|
return E;
|
||||||
|
}
|
|
@ -2,78 +2,9 @@
|
||||||
|
|
||||||
A kit is a combination of Inter code with an Inform 7 extension.
|
A kit is a combination of Inter code with an Inform 7 extension.
|
||||||
|
|
||||||
@h Kits.
|
@h Genre definition.
|
||||||
|
|
||||||
=
|
=
|
||||||
inbuild_genre *kit_genre = NULL;
|
|
||||||
void Kits::start(void) {
|
|
||||||
kit_genre = Model::genre(I"kit");
|
|
||||||
METHOD_ADD(kit_genre, GENRE_WRITE_WORK_MTID, Kits::write_copy);
|
|
||||||
METHOD_ADD(kit_genre, GENRE_LOCATION_IN_NEST_MTID, Kits::location_in_nest);
|
|
||||||
METHOD_ADD(kit_genre, GENRE_COPY_TO_NEST_MTID, Kits::copy_to_nest);
|
|
||||||
}
|
|
||||||
|
|
||||||
inbuild_copy *Kits::claim(text_stream *arg, text_stream *ext, int directory_status) {
|
|
||||||
if (directory_status == FALSE) return NULL;
|
|
||||||
int kitpos = Str::len(arg) - 3;
|
|
||||||
if ((kitpos >= 0) && (Str::get_at(arg, kitpos) == 'K') &&
|
|
||||||
(Str::get_at(arg, kitpos+1) == 'i') &&
|
|
||||||
(Str::get_at(arg, kitpos+2) == 't')) {
|
|
||||||
pathname *P = Pathnames::from_text(arg);
|
|
||||||
inform_kit *K = Kits::load_at(Pathnames::directory_name(P), P);
|
|
||||||
Works::add_to_database(K->as_copy->edition->work, CLAIMED_WDBC);
|
|
||||||
return K->as_copy;
|
|
||||||
}
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Kits::write_copy(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) {
|
|
||||||
WRITE("%S", work->title);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Kits::location_in_nest(inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results) {
|
|
||||||
pathname *P = Pathnames::subfolder(N->location, I"Inter");
|
|
||||||
scan_directory *D = Directories::open(P);
|
|
||||||
if (D) {
|
|
||||||
TEMPORARY_TEXT(LEAFNAME);
|
|
||||||
while (Directories::next(D, LEAFNAME)) {
|
|
||||||
if (Str::get_last_char(LEAFNAME) == FOLDER_SEPARATOR) {
|
|
||||||
Str::delete_last_character(LEAFNAME);
|
|
||||||
pathname *Q = Pathnames::subfolder(P, LEAFNAME);
|
|
||||||
filename *canary = Filenames::in_folder(Q, I"kit_metadata.txt");
|
|
||||||
if (TextFiles::exists(canary)) {
|
|
||||||
inform_kit *K = Kits::load_at(Pathnames::directory_name(Q), Q);
|
|
||||||
if (Requirements::meets(K->as_copy->edition, req)) {
|
|
||||||
Nests::add_search_result(search_results, N, K->as_copy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
DISCARD_TEXT(LEAFNAME);
|
|
||||||
Directories::close(D);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Kits::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing) {
|
|
||||||
// Model::write_copy(STDOUT, C); PRINT(" --> %p %S\n", N->location, syncing?I"syncing":I"copying");
|
|
||||||
pathname *dest_kit = Pathnames::subfolder(N->location, I"Inter");
|
|
||||||
dest_kit = Pathnames::subfolder(dest_kit, C->edition->work->title);
|
|
||||||
|
|
||||||
filename *dest_kit_metadata = Filenames::in_folder(dest_kit, I"kit_metadata.txt");
|
|
||||||
if (TextFiles::exists(dest_kit_metadata)) {
|
|
||||||
if (syncing == FALSE) {
|
|
||||||
Errors::with_text("already present in nest (use -sync-to not -copy-to to overwrite)",
|
|
||||||
C->edition->work->title);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Pathnames::create_in_file_system(N->location);
|
|
||||||
Pathnames::create_in_file_system(Pathnames::subfolder(N->location, I"Inter"));
|
|
||||||
Pathnames::create_in_file_system(dest_kit);
|
|
||||||
}
|
|
||||||
Pathnames::rsync(C->location_if_path, dest_kit);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct inform_kit {
|
typedef struct inform_kit {
|
||||||
struct inbuild_copy *as_copy;
|
struct inbuild_copy *as_copy;
|
||||||
struct text_stream *name;
|
struct text_stream *name;
|
||||||
|
@ -107,7 +38,7 @@ typedef struct element_activation {
|
||||||
pathname *Kits::find(text_stream *name, linked_list *nest_list) {
|
pathname *Kits::find(text_stream *name, linked_list *nest_list) {
|
||||||
inbuild_nest *N;
|
inbuild_nest *N;
|
||||||
LOOP_OVER_LINKED_LIST(N, inbuild_nest, nest_list) {
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, nest_list) {
|
||||||
pathname *P = Pathnames::subfolder(N->location, I"Inter");
|
pathname *P = KitManager::path_within_nest(N);
|
||||||
P = Pathnames::subfolder(P, name);
|
P = Pathnames::subfolder(P, name);
|
||||||
filename *F = Filenames::in_folder(P, I"kit_metadata.txt");
|
filename *F = Filenames::in_folder(P, I"kit_metadata.txt");
|
||||||
if (TextFiles::exists(F)) return P;
|
if (TextFiles::exists(F)) return P;
|
||||||
|
@ -115,8 +46,9 @@ pathname *Kits::find(text_stream *name, linked_list *nest_list) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
inform_kit *Kits::load_at(text_stream *name, pathname *P) {
|
inform_kit *Kits::new_ik(text_stream *name, pathname *P) {
|
||||||
inform_kit *K = CREATE(inform_kit);
|
inform_kit *K = CREATE(inform_kit);
|
||||||
|
K->as_copy = NULL;
|
||||||
K->name = Str::duplicate(name);
|
K->name = Str::duplicate(name);
|
||||||
K->attachment_point = Str::new();
|
K->attachment_point = Str::new();
|
||||||
WRITE_TO(K->attachment_point, "/main/%S", name);
|
WRITE_TO(K->attachment_point, "/main/%S", name);
|
||||||
|
@ -134,63 +66,15 @@ inform_kit *Kits::load_at(text_stream *name, pathname *P) {
|
||||||
filename *F = Filenames::in_folder(P, I"kit_metadata.txt");
|
filename *F = Filenames::in_folder(P, I"kit_metadata.txt");
|
||||||
TextFiles::read(F, FALSE,
|
TextFiles::read(F, FALSE,
|
||||||
NULL, FALSE, Kits::read_metadata, NULL, (void *) K);
|
NULL, FALSE, Kits::read_metadata, NULL, (void *) K);
|
||||||
|
|
||||||
inbuild_work *work = Works::new(kit_genre, name, NULL);
|
|
||||||
work->title = Str::duplicate(name);
|
|
||||||
inbuild_edition *edition = Model::edition(work, K->version);
|
|
||||||
K->as_copy = Model::copy_in_directory(edition, P, STORE_POINTER_inform_kit(K));
|
|
||||||
|
|
||||||
build_graph *KV = Graphs::copy_vertex(K->as_copy);
|
|
||||||
text_stream *archs[4] = { I"16", I"32", I"16d", I"32d" };
|
|
||||||
text_stream *binaries[4] = { I"arch-16.interb", I"arch-32.interb", I"arch-16d.interb", I"arch-32d.interb" };
|
|
||||||
build_graph *BV[4];
|
|
||||||
for (int i=0; i<4; i++) {
|
|
||||||
filename *FV = Filenames::in_folder(P, binaries[i]);
|
|
||||||
BV[i] = Graphs::internal_vertex(FV);
|
|
||||||
Graphs::arrow(KV, BV[i]);
|
|
||||||
build_step *BS = BuildSteps::new_step(ASSIMILATE_BSTEP, P, archs[i]);
|
|
||||||
BuildSteps::add_step(BV[i]->script, BS);
|
|
||||||
}
|
|
||||||
|
|
||||||
filename *contents_page = Filenames::in_folder(K->as_copy->location_if_path, I"Contents.w");
|
|
||||||
build_graph *CV = Graphs::internal_vertex(contents_page);
|
|
||||||
for (int i=0; i<4; i++) Graphs::arrow(BV[i], CV);
|
|
||||||
|
|
||||||
kit_contents_section_state CSS;
|
|
||||||
CSS.active = FALSE;
|
|
||||||
CSS.sects = NEW_LINKED_LIST(text_stream);
|
|
||||||
TextFiles::read(contents_page, FALSE, NULL, FALSE, Kits::read_contents, NULL, (void *) &CSS);
|
|
||||||
text_stream *segment;
|
|
||||||
LOOP_OVER_LINKED_LIST(segment, text_stream, CSS.sects) {
|
|
||||||
filename *SF = Filenames::in_folder(
|
|
||||||
Pathnames::subfolder(K->as_copy->location_if_path, I"Sections"), segment);
|
|
||||||
build_graph *SV = Graphs::internal_vertex(SF);
|
|
||||||
for (int i=0; i<4; i++) Graphs::arrow(BV[i], SV);
|
|
||||||
}
|
|
||||||
return K;
|
return K;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef struct kit_contents_section_state {
|
|
||||||
struct linked_list *sects; /* of |text_stream| */
|
|
||||||
int active;
|
|
||||||
} kit_contents_section_state;
|
|
||||||
|
|
||||||
void Kits::read_contents(text_stream *text, text_file_position *tfp, void *state) {
|
|
||||||
kit_contents_section_state *CSS = (kit_contents_section_state *) state;
|
|
||||||
match_results mr = Regexp::create_mr();
|
|
||||||
if (Regexp::match(&mr, text, L"Sections"))
|
|
||||||
CSS->active = TRUE;
|
|
||||||
if ((Regexp::match(&mr, text, L" (%c+)")) && (CSS->active)) {
|
|
||||||
WRITE_TO(mr.exp[0], ".i6t");
|
|
||||||
ADD_TO_LINKED_LIST(Str::duplicate(mr.exp[0]), text_stream, CSS->sects);
|
|
||||||
}
|
|
||||||
Regexp::dispose_of(&mr);
|
|
||||||
}
|
|
||||||
|
|
||||||
inform_kit *Kits::load(text_stream *name, linked_list *nest_list) {
|
inform_kit *Kits::load(text_stream *name, linked_list *nest_list) {
|
||||||
pathname *P = Kits::find(name, nest_list);
|
pathname *P = Kits::find(name, nest_list);
|
||||||
if (P == NULL) Errors::fatal_with_text("cannot find kit", name);
|
if (P == NULL) Errors::fatal_with_text("cannot find kit", name);
|
||||||
return Kits::load_at(name, P);
|
inbuild_copy *C = KitManager::new_copy(name, P);
|
||||||
|
KitManager::build_graph(C);
|
||||||
|
return KitManager::from_copy(C);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Kits::dependency(inform_kit *K, text_stream *if_text, int inc, text_stream *then_text) {
|
void Kits::dependency(inform_kit *K, text_stream *if_text, int inc, text_stream *then_text) {
|
|
@ -16,7 +16,11 @@ Chapter 2: Conceptual Framework
|
||||||
Build Steps
|
Build Steps
|
||||||
Nests
|
Nests
|
||||||
|
|
||||||
Chapter 3: The Genres
|
Chapter 3: Managing Genres of Work
|
||||||
Kits
|
Kit Manager
|
||||||
Extensions
|
Extension Manager
|
||||||
|
|
||||||
|
Chapter 4: Services for the Inform Compiler
|
||||||
|
Kit Services
|
||||||
|
Extension Services
|
||||||
Extension Census
|
Extension Census
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
Include Extendswithoutbegins Extension by Araminta Intest.
|
|
||||||
|
|
||||||
Xenon is a room.
|
|
|
@ -1,3 +0,0 @@
|
||||||
Include Extmisidentified Extension by Araminta Intest.
|
|
||||||
|
|
||||||
Xenon is a room.
|
|
|
@ -1,3 +0,0 @@
|
||||||
Include Extnobeginshere Extension by Araminta Intest.
|
|
||||||
|
|
||||||
Xenon is a room.
|
|
|
@ -1,10 +0,0 @@
|
||||||
Inform 7 build 6L26 has started.
|
|
||||||
I've now read your source text, which is 10 words long.
|
|
||||||
I've also read Standard Rules by Graham Nelson, which is 42597 words long.
|
|
||||||
I've also read English Language by Graham Nelson, which is 2288 words long.
|
|
||||||
I've also read Extendswithoutbegins Extension by Araminta Intest, which is 19 words long.
|
|
||||||
Problem__ PM_ExtEndsWithoutBegins
|
|
||||||
>--> The extension Extendswithoutbegins Extension by Araminta Intest, which
|
|
||||||
your source text makes use of, has an 'ends here' with nothing having
|
|
||||||
begun.
|
|
||||||
Inform 7 has finished: 8 centiseconds used.
|
|
|
@ -1,11 +0,0 @@
|
||||||
Inform 7 build 6L26 has started.
|
|
||||||
I've now read your source text, which is 10 words long.
|
|
||||||
I've also read Standard Rules by Graham Nelson, which is 42597 words long.
|
|
||||||
I've also read English Language by Graham Nelson, which is 2288 words long.
|
|
||||||
I've also read Extmisidentified Extension by Araminta Intest, which is 15 words long.
|
|
||||||
Problem__ PM_ExtMisidentified
|
|
||||||
>--> The extension Extmisidentified Extension by Araminta Intest, which your
|
|
||||||
source text makes use of, seems to be misidentified: its 'begins here'
|
|
||||||
sentence declares it as 'Soggy Dishcloths by Jereboam Nuce'. (Perhaps it
|
|
||||||
was wrongly installed?)
|
|
||||||
Inform 7 has finished: 8 centiseconds used.
|
|
|
@ -1,12 +1,12 @@
|
||||||
Inform 7 build 6L26 has started.
|
Inform 7.10.1 build 6Q21 has started.
|
||||||
I've now read your source text, which is 10 words long.
|
I've now read your source text, which is 10 words long.
|
||||||
I've also read Standard Rules by Graham Nelson, which is 42597 words long.
|
I've also read Basic Inform by Graham Nelson, which is 7645 words long.
|
||||||
I've also read English Language by Graham Nelson, which is 2288 words long.
|
I've also read English Language by Graham Nelson, which is 2328 words long.
|
||||||
I've also read Extmiswordedbeginshere Extension by Araminta Intest, which is 12 words long.
|
I've also read Standard Rules by Graham Nelson, which is 32123 words long.
|
||||||
Problem__ PM_ExtMiswordedBeginsHere
|
Problem__ PM_ExtMiswordedBeginsHere
|
||||||
>--> has a misworded 'begins here' sentence ('Extmiswordedbeginshere
|
>--> The extension Extmiswordedbeginshere Extension by Araminta Intest, which
|
||||||
Extension'), which contains no 'by'. Recall that every extension should
|
your source text makes use of, seems to be damaged or incorrect: its
|
||||||
begin with a sentence such as 'Quantum Mechanics by Max Planck begins
|
identifying opening line is wrong. Specifically, the titling line does not
|
||||||
here.', and end with a matching 'Quantum Mechanics ends here.', perhaps
|
give both author and title.
|
||||||
with documentation to follow.
|
I've also read Extmiswordedbeginshere Extension by Araminta Intest, which is 12 words long.
|
||||||
Inform 7 has finished: 8 centiseconds used.
|
Inform 7 has finished: 11 centiseconds used.
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
Inform 7 build 6L26 has started.
|
|
||||||
I've now read your source text, which is 10 words long.
|
|
||||||
I've also read Standard Rules by Graham Nelson, which is 42597 words long.
|
|
||||||
I've also read English Language by Graham Nelson, which is 2288 words long.
|
|
||||||
I've also read Extnobeginshere Extension by Araminta Intest, which is 4 words long.
|
|
||||||
Problem__ PM_ExtNoBeginsHere
|
|
||||||
>--> The extension Extnobeginshere Extension by Araminta Intest, which your
|
|
||||||
source text makes use of, has no 'begins here' sentence.
|
|
||||||
Inform 7 has finished: 8 centiseconds used.
|
|
|
@ -435,6 +435,13 @@ with "Output.i6t".
|
||||||
@<Ensure inter pipeline variables dictionary@>;
|
@<Ensure inter pipeline variables dictionary@>;
|
||||||
Str::copy(Dictionaries::create_text(pipeline_vars, I"*in"), I"*memory");
|
Str::copy(Dictionaries::create_text(pipeline_vars, I"*in"), I"*memory");
|
||||||
Str::copy(Dictionaries::create_text(pipeline_vars, I"*out"), Filenames::get_leafname(filename_of_compiled_i6_code));
|
Str::copy(Dictionaries::create_text(pipeline_vars, I"*out"), Filenames::get_leafname(filename_of_compiled_i6_code));
|
||||||
|
pathname *inter_subnests[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||||
|
int s = 0;
|
||||||
|
inbuild_nest *N;
|
||||||
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, I7_nest_list) {
|
||||||
|
if (s >= NO_FS_AREAS) break;
|
||||||
|
inter_subnests[s++] = KitManager::path_within_nest(N);
|
||||||
|
}
|
||||||
codegen_pipeline *SS = NULL;
|
codegen_pipeline *SS = NULL;
|
||||||
if (inter_processing_file)
|
if (inter_processing_file)
|
||||||
SS = CodeGen::Pipeline::parse_from_file(Filenames::from_text(inter_processing_file), pipeline_vars);
|
SS = CodeGen::Pipeline::parse_from_file(Filenames::from_text(inter_processing_file), pipeline_vars);
|
||||||
|
@ -442,7 +449,7 @@ with "Output.i6t".
|
||||||
SS = CodeGen::Pipeline::parse(inter_processing_pipeline, pipeline_vars);
|
SS = CodeGen::Pipeline::parse(inter_processing_pipeline, pipeline_vars);
|
||||||
else {
|
else {
|
||||||
for (int area=0; area<NO_FS_AREAS; area++) {
|
for (int area=0; area<NO_FS_AREAS; area++) {
|
||||||
pathname *P = pathname_of_inter_resources[area];
|
pathname *P = inter_subnests[area];
|
||||||
filename *F = Filenames::in_folder(P, I"default.interpipeline");
|
filename *F = Filenames::in_folder(P, I"default.interpipeline");
|
||||||
if (TextFiles::exists(F)) {
|
if (TextFiles::exists(F)) {
|
||||||
SS = CodeGen::Pipeline::parse_from_file(F, pipeline_vars);
|
SS = CodeGen::Pipeline::parse_from_file(F, pipeline_vars);
|
||||||
|
@ -454,7 +461,7 @@ with "Output.i6t".
|
||||||
Problems::Fatal::issue("The Inter pipeline description contained errors");
|
Problems::Fatal::issue("The Inter pipeline description contained errors");
|
||||||
CodeGen::Pipeline::set_repository(SS, Emit::tree());
|
CodeGen::Pipeline::set_repository(SS, Emit::tree());
|
||||||
CodeGen::Pipeline::run(Filenames::get_path_to(filename_of_compiled_i6_code),
|
CodeGen::Pipeline::run(Filenames::get_path_to(filename_of_compiled_i6_code),
|
||||||
SS, NO_FS_AREAS, pathname_of_inter_resources, Kits::list_of_inter_libraries());
|
SS, NO_FS_AREAS, inter_subnests, Kits::list_of_inter_libraries());
|
||||||
}
|
}
|
||||||
LOG("Back end elapsed time: %dcs\n", ((int) (clock() - front_end)) / (CLOCKS_PER_SEC/100));
|
LOG("Back end elapsed time: %dcs\n", ((int) (clock() - front_end)) / (CLOCKS_PER_SEC/100));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ char *AREA_NAME[3] = { "from .materials", "installed", "built in" };
|
||||||
= (early code)
|
= (early code)
|
||||||
linked_list *I7_nest_list = NULL;
|
linked_list *I7_nest_list = NULL;
|
||||||
pathname *pathname_of_area[NO_FS_AREAS] = { NULL, NULL, NULL };
|
pathname *pathname_of_area[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_languages[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||||
pathname *pathname_of_website_templates[NO_FS_AREAS] = { NULL, NULL, NULL };
|
pathname *pathname_of_website_templates[NO_FS_AREAS] = { NULL, NULL, NULL };
|
||||||
|
|
||||||
|
@ -486,7 +485,6 @@ template files, language definitions and website templates.
|
||||||
void Locations::EILT_at(int area, pathname *P) {
|
void Locations::EILT_at(int area, pathname *P) {
|
||||||
pathname_of_languages[area] = Pathnames::subfolder(P, I"Languages");
|
pathname_of_languages[area] = Pathnames::subfolder(P, I"Languages");
|
||||||
pathname_of_website_templates[area] = Pathnames::subfolder(P, I"Templates");
|
pathname_of_website_templates[area] = Pathnames::subfolder(P, I"Templates");
|
||||||
pathname_of_inter_resources[area] = Pathnames::subfolder(P, I"Inter");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@h Location of extensions.
|
@h Location of extensions.
|
||||||
|
|
|
@ -128,7 +128,11 @@ file.
|
||||||
|
|
||||||
@<Open a file for input, if necessary@> =
|
@<Open a file for input, if necessary@> =
|
||||||
if (Str::len(segment_name) > 0) {
|
if (Str::len(segment_name) > 0) {
|
||||||
@<Open the I6 template file@>;
|
Input_File = NULL;
|
||||||
|
WRITE_TO(STDERR, "inform: Unable to open segment <%S>\n", segment_name);
|
||||||
|
Problems::Issue::unlocated_problem(_p_(BelievedImpossible), /* or anyway not usefully testable */
|
||||||
|
"I couldn't open a requested I6T segment: see the console "
|
||||||
|
"output for details.");
|
||||||
} else if (index_template) {
|
} else if (index_template) {
|
||||||
Input_File = Filenames::fopen(index_template, "r");
|
Input_File = Filenames::fopen(index_template, "r");
|
||||||
if (Input_File == NULL) {
|
if (Input_File == NULL) {
|
||||||
|
@ -138,29 +142,6 @@ file.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ We look for the |.i6t| files first in the materials folder, then in the
|
|
||||||
installed area and lastly (but almost always) in the built-in resources.
|
|
||||||
Within those, we look inside |Inter/kinds| for |*.kindt| files,
|
|
||||||
and |Inter/miscellaneous| for |*.i6t| files (though at present Inform makes
|
|
||||||
no use of this ability).
|
|
||||||
|
|
||||||
@<Open the I6 template file@> =
|
|
||||||
Input_File = NULL;
|
|
||||||
for (int area=0; area<NO_FS_AREAS; area++)
|
|
||||||
if (Input_File == NULL) {
|
|
||||||
pathname *P = pathname_of_inter_resources[area];
|
|
||||||
if (int_mode == KINDT_MODE) P = Pathnames::subfolder(P, I"kinds");
|
|
||||||
else P = Pathnames::subfolder(P, I"miscellaneous");
|
|
||||||
Input_File = Filenames::fopen(
|
|
||||||
Filenames::in_folder(P, segment_name), "r");
|
|
||||||
}
|
|
||||||
if (Input_File == NULL) {
|
|
||||||
WRITE_TO(STDERR, "inform: Unable to open segment <%S>\n", segment_name);
|
|
||||||
Problems::Issue::unlocated_problem(_p_(BelievedImpossible), /* or anyway not usefully testable */
|
|
||||||
"I couldn't open a requested I6T segment: see the console "
|
|
||||||
"output for details.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@ I6 template files are encoded as ISO Latin-1, not as Unicode UTF-8, so
|
@ I6 template 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
|
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
|
to be terminated with either |0x0a| or |0x0d|. (Since blank lines are
|
||||||
|
|
|
@ -170,8 +170,9 @@ application to communicate the problem badly.
|
||||||
text_stream *title = EF->ef_req->work->title;
|
text_stream *title = EF->ef_req->work->title;
|
||||||
inbuild_work *work = Works::new(extension_genre, title, author_name);
|
inbuild_work *work = Works::new(extension_genre, title, author_name);
|
||||||
inbuild_requirement *req = Requirements::any_version_of(work);
|
inbuild_requirement *req = Requirements::any_version_of(work);
|
||||||
|
req->allow_malformed = TRUE;
|
||||||
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
|
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
|
||||||
Nests::locate(req, search_list, L);
|
Nests::search_for(req, search_list, L);
|
||||||
inbuild_search_result *search_result;
|
inbuild_search_result *search_result;
|
||||||
LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) {
|
LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) {
|
||||||
eventual = search_result->copy->location_if_file;
|
eventual = search_result->copy->location_if_file;
|
||||||
|
|
|
@ -139,7 +139,7 @@ source text.
|
||||||
|
|
||||||
@<Check we can end an extension here@> =
|
@<Check we can end an extension here@> =
|
||||||
switch (sfsm_extension_position) {
|
switch (sfsm_extension_position) {
|
||||||
case 1: Problems::Issue::extension_problem(_p_(PM_ExtEndsWithoutBegins),
|
case 1: Problems::Issue::extension_problem(_p_(BelievedImpossible),
|
||||||
sfsm_extension, "has an 'ends here' with nothing having begun"); break;
|
sfsm_extension, "has an 'ends here' with nothing having begun"); break;
|
||||||
case 2: sfsm_extension_position++; break;
|
case 2: sfsm_extension_position++; break;
|
||||||
case 3: Problems::Issue::extension_problem(_p_(PM_ExtMultipleEndsHere),
|
case 3: Problems::Issue::extension_problem(_p_(PM_ExtMultipleEndsHere),
|
||||||
|
|
|
@ -288,7 +288,7 @@ void Extensions::Dictionary::time_stamp(inform_extension *E) {
|
||||||
WRITE_TO(dbuff, "%04d%02d%02d%02d%02d%02d/%d:%s %d %s %d %02d:%02d",
|
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_year+1900, the_present->tm_mon + 1, the_present->tm_mday,
|
||||||
the_present->tm_hour, the_present->tm_min, the_present->tm_sec,
|
the_present->tm_hour, the_present->tm_min, the_present->tm_sec,
|
||||||
TextFromFiles::total_word_count(E->read_into_file),
|
(E->read_into_file)?(TextFromFiles::total_word_count(E->read_into_file)):0,
|
||||||
ascday[the_present->tm_wday], the_present->tm_mday,
|
ascday[the_present->tm_wday], the_present->tm_mday,
|
||||||
ascmon[the_present->tm_mon], the_present->tm_year+1900,
|
ascmon[the_present->tm_mon], the_present->tm_year+1900,
|
||||||
the_present->tm_hour, the_present->tm_min);
|
the_present->tm_hour, the_present->tm_min);
|
||||||
|
|
|
@ -171,7 +171,7 @@ extension_file *Extensions::Files::new(wording AW, wording NW, wording VMW, int
|
||||||
inform_extension *Extensions::Files::find(extension_file *ef) {
|
inform_extension *Extensions::Files::find(extension_file *ef) {
|
||||||
if (ef == NULL) return NULL;
|
if (ef == NULL) return NULL;
|
||||||
if (ef->found == NULL) return NULL;
|
if (ef->found == NULL) return NULL;
|
||||||
return Extensions::from_copy(ef->found);
|
return ExtensionManager::from_copy(ef->found);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ We protect ourselves a little against absurdly long requested author or
|
@ We protect ourselves a little against absurdly long requested author or
|
||||||
|
@ -243,10 +243,23 @@ it up with the corresponding source file structure.
|
||||||
|
|
||||||
=
|
=
|
||||||
void Extensions::Files::set_corresponding_source_file(extension_file *ef, source_file *sf) {
|
void Extensions::Files::set_corresponding_source_file(extension_file *ef, source_file *sf) {
|
||||||
inform_extension *E = Extensions::load_at(
|
TEMPORARY_TEXT(error_text);
|
||||||
ef->ef_req->work->title, ef->ef_req->work->author_name, sf->name);
|
inbuild_copy *C = ExtensionManager::claim_file_as_copy(sf->name, error_text, TRUE);
|
||||||
ef->found = E->as_copy;
|
if (Str::len(error_text) > 0) {
|
||||||
|
Problems::quote_extension(1, ef);
|
||||||
|
Problems::quote_stream(2, error_text);
|
||||||
|
Problems::Issue::handmade_problem(_p_(PM_ExtMiswordedBeginsHere));
|
||||||
|
Problems::issue_problem_segment(
|
||||||
|
"The extension %1, which your source text makes use of, seems to be "
|
||||||
|
"damaged or incorrect: its identifying opening line is wrong. "
|
||||||
|
"Specifically, %2.");
|
||||||
|
Problems::issue_problem_end();
|
||||||
|
} else {
|
||||||
|
ef->found = C;
|
||||||
|
inform_extension *E = ExtensionManager::from_copy(C);
|
||||||
E->read_into_file = sf;
|
E->read_into_file = sf;
|
||||||
|
}
|
||||||
|
DISCARD_TEXT(error_text);
|
||||||
}
|
}
|
||||||
|
|
||||||
source_file *Extensions::Files::source(extension_file *ef) {
|
source_file *Extensions::Files::source(extension_file *ef) {
|
||||||
|
@ -268,7 +281,7 @@ inbuild_work *Extensions::Files::get_work(extension_file *ef) {
|
||||||
inbuild_version_number Extensions::Files::get_version(extension_file *ef) {
|
inbuild_version_number Extensions::Files::get_version(extension_file *ef) {
|
||||||
inform_extension *E = Extensions::Files::find(ef);
|
inform_extension *E = Extensions::Files::find(ef);
|
||||||
if (E == NULL) return VersionNumbers::null();
|
if (E == NULL) return VersionNumbers::null();
|
||||||
return E->version_loaded;
|
return E->as_copy->edition->version;
|
||||||
}
|
}
|
||||||
|
|
||||||
inbuild_edition *Extensions::Files::get_edition(extension_file *ef) {
|
inbuild_edition *Extensions::Files::get_edition(extension_file *ef) {
|
||||||
|
@ -280,7 +293,7 @@ inbuild_edition *Extensions::Files::get_edition(extension_file *ef) {
|
||||||
void Extensions::Files::set_version(extension_file *ef, inbuild_version_number V) {
|
void Extensions::Files::set_version(extension_file *ef, inbuild_version_number V) {
|
||||||
inform_extension *E = Extensions::Files::find(ef);
|
inform_extension *E = Extensions::Files::find(ef);
|
||||||
if (E == NULL) internal_error("no E found");
|
if (E == NULL) internal_error("no E found");
|
||||||
E->version_loaded = V;
|
E->as_copy->edition->version = V;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ The use option "authorial modesty" is unusual in applying to the extension
|
@ The use option "authorial modesty" is unusual in applying to the extension
|
||||||
|
|
|
@ -334,11 +334,11 @@ problem messages if it is malformed.
|
||||||
=
|
=
|
||||||
<begins-here-sentence-subject> ::=
|
<begins-here-sentence-subject> ::=
|
||||||
<extension-title-and-version> by ... | ==> R[1]; <<auth1>> = Wordings::first_wn(WR[1]); <<auth2>> = Wordings::last_wn(WR[1]);
|
<extension-title-and-version> by ... | ==> R[1]; <<auth1>> = Wordings::first_wn(WR[1]); <<auth2>> = Wordings::last_wn(WR[1]);
|
||||||
... ==> @<Issue PM_ExtMiswordedBeginsHere problem@>
|
... ==> @<Issue problem@>
|
||||||
|
|
||||||
@<Issue PM_ExtMiswordedBeginsHere problem@> =
|
@<Issue problem@> =
|
||||||
<<auth1>> = -1; <<auth2>> = -1;
|
<<auth1>> = -1; <<auth2>> = -1;
|
||||||
Problems::Issue::handmade_problem(_p_(PM_ExtMiswordedBeginsHere));
|
Problems::Issue::handmade_problem(_p_(BelievedImpossible)); // since inbuild's scan catches this first
|
||||||
Problems::issue_problem_segment(
|
Problems::issue_problem_segment(
|
||||||
"has a misworded 'begins here' sentence ('%2'), which contains "
|
"has a misworded 'begins here' sentence ('%2'), which contains "
|
||||||
"no 'by'. Recall that every extension should begin with a "
|
"no 'by'. Recall that every extension should begin with a "
|
||||||
|
@ -385,12 +385,14 @@ with our VM de jour.
|
||||||
file called Onion Cookery in the Delia Smith folder of the (probably external)
|
file called Onion Cookery in the Delia Smith folder of the (probably external)
|
||||||
extensions area: but suppose that file turns out instead to be French Cuisine
|
extensions area: but suppose that file turns out instead to be French Cuisine
|
||||||
by Elizabeth David, according to its "begins here" sentence? Then the
|
by Elizabeth David, according to its "begins here" sentence? Then the
|
||||||
following problem message is produced:
|
following problem message is produced. (In fact it's now hard to get this
|
||||||
|
problem to arise, because inbuild searches for extensions much more carefully
|
||||||
|
than Inform 7 used to.)
|
||||||
|
|
||||||
@<Issue a problem message pointing out that name and author do not agree with filename@> =
|
@<Issue a problem message pointing out that name and author do not agree with filename@> =
|
||||||
Problems::quote_extension(1, ef);
|
Problems::quote_extension(1, ef);
|
||||||
Problems::quote_wording(2, ParseTree::get_text(PN));
|
Problems::quote_wording(2, ParseTree::get_text(PN));
|
||||||
Problems::Issue::handmade_problem(_p_(PM_ExtMisidentified));
|
Problems::Issue::handmade_problem(_p_(BelievedImpossible));
|
||||||
Problems::issue_problem_segment(
|
Problems::issue_problem_segment(
|
||||||
"The extension %1, which your source text makes use of, seems to be "
|
"The extension %1, which your source text makes use of, seems to be "
|
||||||
"misidentified: its 'begins here' sentence declares it as '%2'. "
|
"misidentified: its 'begins here' sentence declares it as '%2'. "
|
||||||
|
|
|
@ -152,13 +152,13 @@ void Main::act(void) {
|
||||||
if ((pipeline_as_file) || (pipeline_as_text)) {
|
if ((pipeline_as_file) || (pipeline_as_text)) {
|
||||||
if (NUMBER_CREATED(inter_file) > 0)
|
if (NUMBER_CREATED(inter_file) > 0)
|
||||||
Errors::fatal("-pipeline and -pipeline-file cannot be combined with inter file parameters");
|
Errors::fatal("-pipeline and -pipeline-file cannot be combined with inter file parameters");
|
||||||
int NO_FS_AREAS = 0;
|
int no_subnests = 0;
|
||||||
pathname *pathname_of_inter_resources[1];
|
pathname *subnests[1];
|
||||||
if (template_path) { NO_FS_AREAS = 1; pathname_of_inter_resources[0] = template_path; }
|
if (template_path) { no_subnests = 1; subnests[0] = template_path; }
|
||||||
codegen_pipeline *SS;
|
codegen_pipeline *SS;
|
||||||
if (pipeline_as_file) SS = CodeGen::Pipeline::parse_from_file(pipeline_as_file, pipeline_vars);
|
if (pipeline_as_file) SS = CodeGen::Pipeline::parse_from_file(pipeline_as_file, pipeline_vars);
|
||||||
else SS = CodeGen::Pipeline::parse(pipeline_as_text, pipeline_vars);
|
else SS = CodeGen::Pipeline::parse(pipeline_as_text, pipeline_vars);
|
||||||
if (SS) CodeGen::Pipeline::run(domain_path, SS, NO_FS_AREAS, pathname_of_inter_resources, requirements_list);
|
if (SS) CodeGen::Pipeline::run(domain_path, SS, no_subnests, subnests, requirements_list);
|
||||||
else Errors::fatal("pipeline could not be parsed");
|
else Errors::fatal("pipeline could not be parsed");
|
||||||
} else if (unit_test_file) {
|
} else if (unit_test_file) {
|
||||||
UnitTests::run(unit_test_file);
|
UnitTests::run(unit_test_file);
|
||||||
|
|
Loading…
Reference in a new issue