1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-26 04:00:43 +03:00

Refactored inbuild

This commit is contained in:
Graham Nelson 2020-02-08 10:34:58 +00:00
parent bee49ecc3d
commit e412a7b601
29 changed files with 722 additions and 550 deletions

View file

@ -50,7 +50,7 @@ int main(int argc, char **argv) {
Errors::with_text("requirement malformed: %S", errors);
} else {
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;
LOOP_OVER_LINKED_LIST(R, inbuild_search_result, L) {
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 BUILD_TTASK: Graphs::build(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 SYNC_TO_TTASK: if (destination_nest) Nests::copy_to(C, destination_nest, TRUE); 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, BM); break;
}
}
WordsModule::end();

View file

@ -57,8 +57,8 @@ void InbuildModule::start(void) {
@<Register this module's debugging log aspects@>;
@<Register this module's debugging log writers@>;
@<Register this module's command line switches@>;
Kits::start();
Extensions::start();
KitManager::start();
ExtensionManager::start();
}
@

View file

@ -103,16 +103,10 @@ build_methodology *BuildSteps::methodology(pathname *tools_path, int dev) {
void BuildSteps::execute(build_script *BS, build_methodology *meth) {
build_step *S;
LOOP_OVER_LINKED_LIST(S, build_step, BS->steps) {
switch (meth->methodology) {
case DRY_RUN_METHODOLOGY:
case SHELL_METHODOLOGY: {
TEMPORARY_TEXT(command);
@<Write a shell command for the step@>;
WRITE_TO(STDOUT, "%S\n", command);
if (meth->methodology == SHELL_METHODOLOGY) Shell::run(command);
DISCARD_TEXT(command);
}
}
TEMPORARY_TEXT(command);
@<Write a shell command for the step@>;
BuildSteps::shell(command, meth);
DISCARD_TEXT(command);
}
}
@ -125,3 +119,14 @@ void BuildSteps::execute(build_script *BS, build_methodology *meth) {
break;
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);
}
}
}

View file

@ -7,7 +7,8 @@ For example, "kit" and "extension" will both be both genres. There will be
few of these.
@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
=
@ -18,8 +19,9 @@ typedef struct inbuild_genre {
} inbuild_genre;
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_COPY_TO_NEST_MTID, inbuild_genre *gen, inbuild_copy *C, inbuild_nest *N, int syncing)
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_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) {
@ -116,8 +118,10 @@ inbuild_copy *Model::claim(text_stream *arg) {
directory_status = TRUE;
}
inbuild_copy *C = NULL;
if (C == NULL) C = Kits::claim(arg, ext, directory_status);
if (C == NULL) C = Extensions::claim(arg, ext, directory_status);
inbuild_genre *G;
LOOP_OVER(G, inbuild_genre)
if (C == NULL)
VMETHOD_CALL(G, GENRE_CLAIM_AS_COPY_MTID, &C, arg, ext, directory_status);
DISCARD_TEXT(ext);
return C;
}

View file

@ -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);
}
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;
LOOP_OVER_LINKED_LIST(N, inbuild_nest, search_list) {
inbuild_genre *G;
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) {
VMETHOD_CALL(C->edition->work->genre, GENRE_COPY_TO_NEST_MTID, C, destination_nest, syncing);
void Nests::copy_to(inbuild_copy *C, inbuild_nest *destination_nest, int 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);
}

View file

@ -10,6 +10,7 @@ typedef struct inbuild_requirement {
struct inbuild_work *work;
struct inbuild_version_number min_version;
struct inbuild_version_number max_version;
int allow_malformed;
MEMORY_MANAGEMENT
} inbuild_requirement;
@ -19,6 +20,7 @@ inbuild_requirement *Requirements::new(inbuild_work *work,
req->work = work;
req->min_version = min;
req->max_version = max;
req->allow_malformed = FALSE;
return req;
}
@ -74,7 +76,18 @@ void Requirements::impose_clause(inbuild_requirement *req, text_stream *T, text_
Str::trim_white_space(value);
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"version")) {
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)
return FALSE;
}
if ((req->allow_malformed) && (Str::len(edition->work->title) == 0)) return TRUE;
if (Str::len(req->work->title) > 0) {
if (Str::ne_insensitive(req->work->title, edition->work->title))
return FALSE;

View 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);
}

View file

@ -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;
}

View 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);
}

View file

@ -27,7 +27,7 @@ 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 ExtensionManager::path_within_nest(N);
return NULL;
}
@ -35,7 +35,7 @@ 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 ExtensionManager::path_within_nest(N);
return NULL;
}
@ -94,7 +94,7 @@ any number of other domains. The following code scans one.
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);
pathname *P = ExtensionManager::path_within_nest(N);
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@> =
filename *F =
Extensions::filename_in_nest(current_extension_domain,
ExtensionManager::filename_in_nest(current_extension_domain,
item_name, cs->parent);
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);
if (Str::len(error_text) > 0) {
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)
else {
#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);
#endif
}

View 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;
}

View file

@ -2,78 +2,9 @@
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 {
struct inbuild_copy *as_copy;
struct text_stream *name;
@ -107,7 +38,7 @@ typedef struct element_activation {
pathname *Kits::find(text_stream *name, linked_list *nest_list) {
inbuild_nest *N;
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);
filename *F = Filenames::in_folder(P, I"kit_metadata.txt");
if (TextFiles::exists(F)) return P;
@ -115,8 +46,9 @@ pathname *Kits::find(text_stream *name, linked_list *nest_list) {
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);
K->as_copy = NULL;
K->name = Str::duplicate(name);
K->attachment_point = Str::new();
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");
TextFiles::read(F, FALSE,
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;
}
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) {
pathname *P = Kits::find(name, nest_list);
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) {

View file

@ -16,7 +16,11 @@ Chapter 2: Conceptual Framework
Build Steps
Nests
Chapter 3: The Genres
Kits
Extensions
Chapter 3: Managing Genres of Work
Kit Manager
Extension Manager
Chapter 4: Services for the Inform Compiler
Kit Services
Extension Services
Extension Census

View file

@ -1,3 +0,0 @@
Include Extendswithoutbegins Extension by Araminta Intest.
Xenon is a room.

View file

@ -1,3 +0,0 @@
Include Extmisidentified Extension by Araminta Intest.
Xenon is a room.

View file

@ -1,3 +0,0 @@
Include Extnobeginshere Extension by Araminta Intest.
Xenon is a room.

View file

@ -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.

View file

@ -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.

View file

@ -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 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 Extmiswordedbeginshere Extension by Araminta Intest, which is 12 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 2328 words long.
I've also read Standard Rules by Graham Nelson, which is 32123 words long.
Problem__ PM_ExtMiswordedBeginsHere
>--> has a misworded 'begins here' sentence ('Extmiswordedbeginshere
Extension'), which contains no 'by'. Recall that every extension should
begin with a sentence such as 'Quantum Mechanics by Max Planck begins
here.', and end with a matching 'Quantum Mechanics ends here.', perhaps
with documentation to follow.
Inform 7 has finished: 8 centiseconds used.
>--> The extension Extmiswordedbeginshere Extension by Araminta Intest, which
your source text makes use of, seems to be damaged or incorrect: its
identifying opening line is wrong. Specifically, the titling line does not
give both author and title.
I've also read Extmiswordedbeginshere Extension by Araminta Intest, which is 12 words long.
Inform 7 has finished: 11 centiseconds used.

View file

@ -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.

View file

@ -435,6 +435,13 @@ with "Output.i6t".
@<Ensure inter pipeline variables dictionary@>;
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));
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;
if (inter_processing_file)
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);
else {
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");
if (TextFiles::exists(F)) {
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");
CodeGen::Pipeline::set_repository(SS, Emit::tree());
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));
}

View file

@ -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_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 };
@ -486,7 +485,6 @@ template files, language definitions and website templates.
void Locations::EILT_at(int area, pathname *P) {
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");
}
@h Location of extensions.

View file

@ -128,7 +128,11 @@ file.
@<Open a file for input, if necessary@> =
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) {
Input_File = Filenames::fopen(index_template, "r");
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
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

View file

@ -170,8 +170,9 @@ application to communicate the problem badly.
text_stream *title = EF->ef_req->work->title;
inbuild_work *work = Works::new(extension_genre, title, author_name);
inbuild_requirement *req = Requirements::any_version_of(work);
req->allow_malformed = TRUE;
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;
LOOP_OVER_LINKED_LIST(search_result, inbuild_search_result, L) {
eventual = search_result->copy->location_if_file;

View file

@ -139,7 +139,7 @@ source text.
@<Check we can end an extension here@> =
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;
case 2: sfsm_extension_position++; break;
case 3: Problems::Issue::extension_problem(_p_(PM_ExtMultipleEndsHere),

View file

@ -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",
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(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,
ascmon[the_present->tm_mon], the_present->tm_year+1900,
the_present->tm_hour, the_present->tm_min);

View file

@ -171,7 +171,7 @@ extension_file *Extensions::Files::new(wording AW, wording NW, wording VMW, int
inform_extension *Extensions::Files::find(extension_file *ef) {
if (ef == 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
@ -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) {
inform_extension *E = Extensions::load_at(
ef->ef_req->work->title, ef->ef_req->work->author_name, sf->name);
ef->found = E->as_copy;
E->read_into_file = sf;
TEMPORARY_TEXT(error_text);
inbuild_copy *C = ExtensionManager::claim_file_as_copy(sf->name, error_text, TRUE);
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;
}
DISCARD_TEXT(error_text);
}
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) {
inform_extension *E = Extensions::Files::find(ef);
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) {
@ -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) {
inform_extension *E = Extensions::Files::find(ef);
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

View file

@ -334,11 +334,11 @@ problem messages if it is malformed.
=
<begins-here-sentence-subject> ::=
<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;
Problems::Issue::handmade_problem(_p_(PM_ExtMiswordedBeginsHere));
Problems::Issue::handmade_problem(_p_(BelievedImpossible)); // since inbuild's scan catches this first
Problems::issue_problem_segment(
"has a misworded 'begins here' sentence ('%2'), which contains "
"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)
extensions area: but suppose that file turns out instead to be French Cuisine
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@> =
Problems::quote_extension(1, ef);
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(
"The extension %1, which your source text makes use of, seems to be "
"misidentified: its 'begins here' sentence declares it as '%2'. "

View file

@ -152,13 +152,13 @@ void Main::act(void) {
if ((pipeline_as_file) || (pipeline_as_text)) {
if (NUMBER_CREATED(inter_file) > 0)
Errors::fatal("-pipeline and -pipeline-file cannot be combined with inter file parameters");
int NO_FS_AREAS = 0;
pathname *pathname_of_inter_resources[1];
if (template_path) { NO_FS_AREAS = 1; pathname_of_inter_resources[0] = template_path; }
int no_subnests = 0;
pathname *subnests[1];
if (template_path) { no_subnests = 1; subnests[0] = template_path; }
codegen_pipeline *SS;
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);
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 if (unit_test_file) {
UnitTests::run(unit_test_file);