mirror of
https://github.com/ganelson/inform.git
synced 2024-06-26 04:00:43 +03:00
Convoluted refactor of projects, adding Source to materials
This commit is contained in:
parent
62aa1026ae
commit
67c3f1a121
|
@ -66,9 +66,9 @@ int main(int argc, char **argv) {
|
|||
}
|
||||
WRITE_TO(STDOUT, "\n");
|
||||
break;
|
||||
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 GRAPH_TTASK: Graphs::describe(STDOUT, C->vertex, TRUE); break;
|
||||
case BUILD_TTASK: Graphs::build(C->vertex, BM); break;
|
||||
case REBUILD_TTASK: Graphs::rebuild(C->vertex, BM); 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;
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ Setting up the use of this module.
|
|||
@e inbuild_edition_MT
|
||||
@e inbuild_requirement_MT
|
||||
@e inbuild_copy_MT
|
||||
@e build_graph_MT
|
||||
@e build_vertex_MT
|
||||
@e build_methodology_MT
|
||||
@e build_script_MT
|
||||
@e build_step_MT
|
||||
|
@ -41,7 +41,7 @@ ALLOCATE_INDIVIDUALLY(inbuild_work)
|
|||
ALLOCATE_INDIVIDUALLY(inbuild_edition)
|
||||
ALLOCATE_INDIVIDUALLY(inbuild_requirement)
|
||||
ALLOCATE_INDIVIDUALLY(inbuild_copy)
|
||||
ALLOCATE_INDIVIDUALLY(build_graph)
|
||||
ALLOCATE_INDIVIDUALLY(build_vertex)
|
||||
ALLOCATE_INDIVIDUALLY(build_methodology)
|
||||
ALLOCATE_INDIVIDUALLY(build_script)
|
||||
ALLOCATE_INDIVIDUALLY(build_step)
|
||||
|
|
|
@ -163,8 +163,12 @@ void SharedCLI::create_shared_project(void) {
|
|||
shared_project = ProjectFileManager::from_copy(C);
|
||||
}
|
||||
@<Create the materials nest@>;
|
||||
if (shared_project)
|
||||
Projects::set_source_filename(shared_project, filename_of_i7_source);
|
||||
if (shared_project) {
|
||||
pathname *P = (shared_materials_nest)?(shared_materials_nest->location):NULL;
|
||||
if (P) P = Pathnames::subfolder(P, I"Source");
|
||||
if (Str::len(project_file_request) > 0) P = NULL;
|
||||
Projects::set_source_filename(shared_project, P, filename_of_i7_source);
|
||||
}
|
||||
}
|
||||
|
||||
@<Create the materials nest@> =
|
||||
|
@ -204,6 +208,30 @@ pathname *SharedCLI::pathname_of_materials(pathname *pathname_of_bundle) {
|
|||
return materials;
|
||||
}
|
||||
|
||||
@h Kit requests.
|
||||
|
||||
=
|
||||
linked_list *kits_requested_at_command_line = NULL;
|
||||
void SharedCLI::request_kit(text_stream *name) {
|
||||
if (kits_requested_at_command_line == NULL)
|
||||
kits_requested_at_command_line = NEW_LINKED_LIST(text_stream);
|
||||
text_stream *kit_name;
|
||||
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested_at_command_line)
|
||||
if (Str::eq(kit_name, name))
|
||||
return;
|
||||
ADD_TO_LINKED_LIST(Str::duplicate(name), text_stream, kits_requested_at_command_line);
|
||||
}
|
||||
|
||||
void SharedCLI::pass_kit_requests(void) {
|
||||
if ((shared_project) && (kits_requested_at_command_line)) {
|
||||
text_stream *kit_name;
|
||||
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested_at_command_line) {
|
||||
Projects::add_kit_dependency(shared_project, kit_name);
|
||||
Projects::not_necessarily_parser_IF(shared_project);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@h Command line.
|
||||
We add the following switches:
|
||||
|
||||
|
@ -243,7 +271,7 @@ void SharedCLI::option(int id, int val, text_stream *arg, void *state) {
|
|||
case INTERNAL_CLSW: SharedCLI::add_nest(Pathnames::from_text(arg), INTERNAL_NEST_TAG); break;
|
||||
case EXTERNAL_CLSW: SharedCLI::add_nest(Pathnames::from_text(arg), EXTERNAL_NEST_TAG); break;
|
||||
case TRANSIENT_CLSW: shared_transient_resources = Pathnames::from_text(arg); break;
|
||||
case KIT_CLSW: Kits::request(arg); break;
|
||||
case KIT_CLSW: SharedCLI::request_kit(arg); break;
|
||||
case PROJECT_CLSW:
|
||||
if (SharedCLI::set_I7_bundle(arg) == FALSE)
|
||||
Errors::fatal_with_text("can't specify the project twice: '%S'", arg);
|
||||
|
@ -262,4 +290,5 @@ options remain to be processed.
|
|||
void SharedCLI::optioneering_complete(void) {
|
||||
SharedCLI::create_shared_project();
|
||||
SharedCLI::sort_nest_list();
|
||||
SharedCLI::pass_kit_requests();
|
||||
}
|
||||
|
|
|
@ -13,48 +13,50 @@ belongs to a single copy, and internal vertices, each of which represents
|
|||
a different file inside the copy.
|
||||
|
||||
=
|
||||
typedef struct build_graph {
|
||||
typedef struct build_vertex {
|
||||
struct inbuild_copy *buildable_if_copy;
|
||||
struct filename *buildable_if_internal_file;
|
||||
struct linked_list *arrows; /* of pointers to other |build_graph| nodes */
|
||||
struct text_stream *annotation;
|
||||
struct linked_list *arrows; /* of pointers to other |build_vertex| nodes */
|
||||
struct build_script *script;
|
||||
time_t timestamp;
|
||||
MEMORY_MANAGEMENT
|
||||
} build_graph;
|
||||
} build_vertex;
|
||||
|
||||
build_graph *Graphs::internal_vertex(filename *F) {
|
||||
build_graph *G = CREATE(build_graph);
|
||||
build_vertex *Graphs::internal_vertex(filename *F) {
|
||||
build_vertex *G = CREATE(build_vertex);
|
||||
G->buildable_if_copy = NULL;
|
||||
G->buildable_if_internal_file = F;
|
||||
G->arrows = NEW_LINKED_LIST(build_graph);
|
||||
G->arrows = NEW_LINKED_LIST(build_vertex);
|
||||
G->timestamp = (time_t) 0;
|
||||
G->script = BuildSteps::new_script();
|
||||
G->annotation = NULL;
|
||||
return G;
|
||||
}
|
||||
|
||||
build_graph *Graphs::copy_vertex(inbuild_copy *C) {
|
||||
build_vertex *Graphs::copy_vertex(inbuild_copy *C) {
|
||||
if (C == NULL) internal_error("no copy");
|
||||
if (C->graph == NULL) {
|
||||
C->graph = Graphs::internal_vertex(NULL);
|
||||
C->graph->buildable_if_copy = C;
|
||||
if (C->vertex == NULL) {
|
||||
C->vertex = Graphs::internal_vertex(NULL);
|
||||
C->vertex->buildable_if_copy = C;
|
||||
}
|
||||
return C->graph;
|
||||
return C->vertex;
|
||||
}
|
||||
|
||||
void Graphs::arrow(build_graph *from, build_graph *to) {
|
||||
void Graphs::arrow(build_vertex *from, build_vertex *to) {
|
||||
if (from == NULL) internal_error("no from");
|
||||
if (to == NULL) internal_error("no to");
|
||||
if (from == to) internal_error("graph node depends on itself");
|
||||
build_graph *G;
|
||||
LOOP_OVER_LINKED_LIST(G, build_graph, from->arrows)
|
||||
build_vertex *G;
|
||||
LOOP_OVER_LINKED_LIST(G, build_vertex, from->arrows)
|
||||
if (G == to) return;
|
||||
ADD_TO_LINKED_LIST(to, build_graph, from->arrows);
|
||||
ADD_TO_LINKED_LIST(to, build_vertex, from->arrows);
|
||||
}
|
||||
|
||||
void Graphs::describe(OUTPUT_STREAM, build_graph *G, int recurse) {
|
||||
void Graphs::describe(OUTPUT_STREAM, build_vertex *G, int recurse) {
|
||||
Graphs::describe_r(OUT, 0, G, recurse);
|
||||
}
|
||||
void Graphs::describe_r(OUTPUT_STREAM, int depth, build_graph *V, int recurse) {
|
||||
void Graphs::describe_r(OUTPUT_STREAM, int depth, build_vertex *V, int recurse) {
|
||||
for (int i=0; i<depth; i++) WRITE(" ");
|
||||
if (V->buildable_if_copy) {
|
||||
WRITE("[copy%d] ", V->allocation_id);
|
||||
|
@ -67,13 +69,13 @@ void Graphs::describe_r(OUTPUT_STREAM, int depth, build_graph *V, int recurse) {
|
|||
else WRITE("\n");
|
||||
}
|
||||
if (recurse) {
|
||||
build_graph *W;
|
||||
LOOP_OVER_LINKED_LIST(W, build_graph, V->arrows)
|
||||
build_vertex *W;
|
||||
LOOP_OVER_LINKED_LIST(W, build_vertex, V->arrows)
|
||||
Graphs::describe_r(OUT, depth+1, W, TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
void Graphs::update_timestamp(build_graph *V) {
|
||||
void Graphs::update_timestamp(build_vertex *V) {
|
||||
if (V == NULL) return;
|
||||
if (V->buildable_if_internal_file == NULL) return;
|
||||
char transcoded_pathname[4*MAX_FILENAME_LENGTH];
|
||||
|
@ -86,23 +88,23 @@ void Graphs::update_timestamp(build_graph *V) {
|
|||
V->timestamp = filestat.st_mtime;
|
||||
}
|
||||
|
||||
void Graphs::build(build_graph *G, build_methodology *meth) {
|
||||
void Graphs::build(build_vertex *G, build_methodology *meth) {
|
||||
Graphs::build_r(FALSE, G, meth);
|
||||
}
|
||||
void Graphs::rebuild(build_graph *G, build_methodology *meth) {
|
||||
void Graphs::rebuild(build_vertex *G, build_methodology *meth) {
|
||||
Graphs::build_r(TRUE, G, meth);
|
||||
}
|
||||
void Graphs::build_r(int forcing_build, build_graph *V, build_methodology *meth) {
|
||||
void Graphs::build_r(int forcing_build, build_vertex *V, build_methodology *meth) {
|
||||
int needs_building = forcing_build;
|
||||
if (V->buildable_if_internal_file)
|
||||
if (TextFiles::exists(V->buildable_if_internal_file) == FALSE)
|
||||
needs_building = TRUE;
|
||||
build_graph *W;
|
||||
LOOP_OVER_LINKED_LIST(W, build_graph, V->arrows)
|
||||
build_vertex *W;
|
||||
LOOP_OVER_LINKED_LIST(W, build_vertex, V->arrows)
|
||||
Graphs::build_r(forcing_build, W, meth);
|
||||
if (needs_building == FALSE) {
|
||||
Graphs::update_timestamp(V);
|
||||
LOOP_OVER_LINKED_LIST(W, build_graph, V->arrows) {
|
||||
LOOP_OVER_LINKED_LIST(W, build_vertex, V->arrows) {
|
||||
Graphs::update_timestamp(W);
|
||||
double since = difftime(V->timestamp, W->timestamp);
|
||||
if (since < 0) { needs_building = TRUE; break; }
|
||||
|
|
|
@ -69,7 +69,7 @@ typedef struct inbuild_copy {
|
|||
struct pathname *location_if_path;
|
||||
struct filename *location_if_file;
|
||||
general_pointer content; /* the type of which depends on the work's genre */
|
||||
struct build_graph *graph;
|
||||
struct build_vertex *vertex;
|
||||
MEMORY_MANAGEMENT
|
||||
} inbuild_copy;
|
||||
|
||||
|
@ -79,7 +79,7 @@ inbuild_copy *Model::copy_in_file(inbuild_edition *edition, filename *F, general
|
|||
copy->location_if_path = NULL;
|
||||
copy->location_if_file = F;
|
||||
copy->content = C;
|
||||
copy->graph = NULL;
|
||||
copy->vertex = NULL;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
@ -89,7 +89,7 @@ inbuild_copy *Model::copy_in_directory(inbuild_edition *edition, pathname *P, ge
|
|||
copy->location_if_path = P;
|
||||
copy->location_if_file = NULL;
|
||||
copy->content = C;
|
||||
copy->graph = NULL;
|
||||
copy->vertex = NULL;
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ inbuild_copy *ExtensionManager::claim_file_as_copy(filename *F, text_stream *err
|
|||
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);
|
||||
ExtensionManager::build_vertex(C);
|
||||
} else {
|
||||
C = NULL;
|
||||
}
|
||||
|
@ -348,6 +348,6 @@ 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) {
|
||||
void ExtensionManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ 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);
|
||||
KitManager::build_vertex(C);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
return C;
|
||||
}
|
||||
|
@ -166,12 +166,12 @@ 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) {
|
||||
void KitManager::build_vertex(inbuild_copy *C) {
|
||||
pathname *P = C->location_if_path;
|
||||
build_graph *KV = Graphs::copy_vertex(C);
|
||||
build_vertex *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];
|
||||
build_vertex *BV[4];
|
||||
for (int i=0; i<4; i++) {
|
||||
filename *FV = Filenames::in_folder(P, binaries[i]);
|
||||
BV[i] = Graphs::internal_vertex(FV);
|
||||
|
@ -181,7 +181,7 @@ void KitManager::build_graph(inbuild_copy *C) {
|
|||
}
|
||||
|
||||
filename *contents_page = Filenames::in_folder(C->location_if_path, I"Contents.w");
|
||||
build_graph *CV = Graphs::internal_vertex(contents_page);
|
||||
build_vertex *CV = Graphs::internal_vertex(contents_page);
|
||||
for (int i=0; i<4; i++) Graphs::arrow(BV[i], CV);
|
||||
|
||||
kit_contents_section_state CSS;
|
||||
|
@ -192,7 +192,7 @@ void KitManager::build_graph(inbuild_copy *C) {
|
|||
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);
|
||||
build_vertex *SV = Graphs::internal_vertex(SF);
|
||||
for (int i=0; i<4; i++) Graphs::arrow(BV[i], SV);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,7 @@ inbuild_copy *LanguageManager::claim_folder_as_copy(pathname *P) {
|
|||
filename *canary = Filenames::in_folder(P, I"about.txt");
|
||||
if (TextFiles::exists(canary)) {
|
||||
inbuild_copy *C = LanguageManager::new_copy(Pathnames::directory_name(P), P);
|
||||
LanguageManager::build_graph(C);
|
||||
LanguageManager::build_vertex(C);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
return C;
|
||||
}
|
||||
|
@ -170,6 +170,6 @@ The build graph for a language bundle is just a single node: you don't need to
|
|||
build it at all.
|
||||
|
||||
=
|
||||
void LanguageManager::build_graph(inbuild_copy *C) {
|
||||
void LanguageManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ inbuild_copy *PipelineManager::claim_file_as_copy(filename *F, text_stream *erro
|
|||
Model::edition(Works::new_raw(pipeline_genre, unext, NULL), V), F);
|
||||
DISCARD_TEXT(unext);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
PipelineManager::build_graph(C);
|
||||
PipelineManager::build_vertex(C);
|
||||
return C;
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,6 @@ The build graph for a pipeline is just a single node: you don't need to
|
|||
build a pipeline at all.
|
||||
|
||||
=
|
||||
void PipelineManager::build_graph(inbuild_copy *C) {
|
||||
void PipelineManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ void ProjectBundleManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C,
|
|||
|
||||
inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
|
||||
inbuild_copy *C = ProjectBundleManager::new_copy(Pathnames::directory_name(P), P);
|
||||
ProjectBundleManager::build_graph(C);
|
||||
ProjectBundleManager::build_vertex(C);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
return C;
|
||||
}
|
||||
|
@ -87,6 +87,6 @@ void ProjectBundleManager::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inb
|
|||
The build graph for a project will need further thought.
|
||||
|
||||
=
|
||||
void ProjectBundleManager::build_graph(inbuild_copy *C) {
|
||||
void ProjectBundleManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ void ProjectFileManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C,
|
|||
|
||||
inbuild_copy *ProjectFileManager::claim_file_as_copy(filename *F) {
|
||||
inbuild_copy *C = ProjectFileManager::new_copy(Filenames::get_leafname(F), F);
|
||||
ProjectFileManager::build_graph(C);
|
||||
ProjectFileManager::build_vertex(C);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
return C;
|
||||
}
|
||||
|
@ -90,6 +90,6 @@ void ProjectFileManager::copy_to_nest(inbuild_genre *gen, inbuild_copy *C, inbui
|
|||
The build graph for a project will need further thought.
|
||||
|
||||
=
|
||||
void ProjectFileManager::build_graph(inbuild_copy *C) {
|
||||
void ProjectFileManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ inbuild_copy *TemplateManager::claim_folder_as_copy(pathname *P) {
|
|||
filename *canary2 = Filenames::in_folder(P, I"index.html");
|
||||
if ((TextFiles::exists(canary1)) || (TextFiles::exists(canary2))) {
|
||||
inbuild_copy *C = TemplateManager::new_copy(Pathnames::directory_name(P), P);
|
||||
TemplateManager::build_graph(C);
|
||||
TemplateManager::build_vertex(C);
|
||||
Works::add_to_database(C->edition->work, CLAIMED_WDBC);
|
||||
return C;
|
||||
}
|
||||
|
@ -148,6 +148,6 @@ The build graph for a template is just a single node: you don't need to
|
|||
build a template at all.
|
||||
|
||||
=
|
||||
void TemplateManager::build_graph(inbuild_copy *C) {
|
||||
void TemplateManager::build_vertex(inbuild_copy *C) {
|
||||
Graphs::copy_vertex(C);
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ 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);
|
||||
inbuild_copy *C = KitManager::new_copy(name, P);
|
||||
KitManager::build_graph(C);
|
||||
if (C->vertex == NULL) KitManager::build_vertex(C);
|
||||
return KitManager::from_copy(C);
|
||||
}
|
||||
|
||||
|
@ -133,165 +133,63 @@ void Kits::read_metadata(text_stream *text, text_file_position *tfp, void *state
|
|||
Regexp::dispose_of(&mr);
|
||||
}
|
||||
|
||||
int Kits::loaded(text_stream *name) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER(K, inform_kit)
|
||||
if (Str::eq(K->name, name))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void Kits::perform_ittt(linked_list *nest_list) {
|
||||
int changes_made = TRUE;
|
||||
while (changes_made) {
|
||||
changes_made = FALSE;
|
||||
inform_kit *K;
|
||||
LOOP_OVER(K, inform_kit) {
|
||||
inform_kit_ittt *ITTT;
|
||||
LOOP_OVER_LINKED_LIST(ITTT, inform_kit_ittt, K->ittt)
|
||||
if ((Kits::loaded(ITTT->then_name) == FALSE) &&
|
||||
(Kits::loaded(ITTT->if_name) == ITTT->if_included)) {
|
||||
Kits::load(ITTT->then_name, nest_list);
|
||||
changes_made = TRUE;
|
||||
}
|
||||
int Kits::perform_ittt(inform_kit *K, inform_project *project, int parity) {
|
||||
int changes_made = FALSE;
|
||||
inform_kit_ittt *ITTT;
|
||||
LOOP_OVER_LINKED_LIST(ITTT, inform_kit_ittt, K->ittt)
|
||||
if ((ITTT->if_included == parity) &&
|
||||
(Projects::uses_kit(project, ITTT->then_name) == FALSE) &&
|
||||
(Projects::uses_kit(project, ITTT->if_name) == ITTT->if_included)) {
|
||||
Projects::add_kit_dependency(project, ITTT->then_name);
|
||||
changes_made = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
linked_list *kits_requested = NULL;
|
||||
linked_list *kits_to_include = NULL;
|
||||
void Kits::request(text_stream *name) {
|
||||
if (kits_requested == NULL) kits_requested = NEW_LINKED_LIST(text_stream);
|
||||
text_stream *kit_name;
|
||||
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested)
|
||||
if (Str::eq(kit_name, name))
|
||||
return;
|
||||
ADD_TO_LINKED_LIST(Str::duplicate(name), text_stream, kits_requested);
|
||||
return changes_made;
|
||||
}
|
||||
|
||||
#ifdef CORE_MODULE
|
||||
void Kits::determine(void) {
|
||||
linked_list *nest_list = SharedCLI::nest_list();
|
||||
if (kits_requested == NULL) Kits::request(I"CommandParserKit");
|
||||
Kits::request(I"BasicInformKit");
|
||||
Languages::request_required_kits();
|
||||
text_stream *kit_name;
|
||||
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested)
|
||||
Kits::load(kit_name, nest_list);
|
||||
|
||||
Kits::perform_ittt(nest_list);
|
||||
|
||||
kits_to_include = NEW_LINKED_LIST(inform_kit);
|
||||
for (int p=0; p<100; p++) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER(K, inform_kit)
|
||||
if (K->priority == p)
|
||||
ADD_TO_LINKED_LIST(K, inform_kit, kits_to_include);
|
||||
}
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include)
|
||||
LOG("Using Inform kit '%S' (priority %d).\n", K->name, K->priority);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CORE_MODULE
|
||||
void Kits::load_types(void) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include) {
|
||||
text_stream *segment;
|
||||
LOOP_OVER_LINKED_LIST(segment, text_stream, K->kind_definitions) {
|
||||
pathname *P = Pathnames::subfolder(K->as_copy->location_if_path, I"kinds");
|
||||
filename *F = Filenames::in_folder(P, segment);
|
||||
LOG("Loading kinds definitions from %f\n", F);
|
||||
I6T::interpret_kindt(F);
|
||||
}
|
||||
void Kits::load_types(inform_kit *K) {
|
||||
text_stream *segment;
|
||||
LOOP_OVER_LINKED_LIST(segment, text_stream, K->kind_definitions) {
|
||||
pathname *P = Pathnames::subfolder(K->as_copy->location_if_path, I"kinds");
|
||||
filename *F = Filenames::in_folder(P, segment);
|
||||
LOG("Loading kinds definitions from %f\n", F);
|
||||
I6T::interpret_kindt(F);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CORE_MODULE
|
||||
void Kits::activate_plugins(void) {
|
||||
LOG("Activate plugins...\n");
|
||||
Plugins::Manage::activate(CORE_PLUGIN_NAME);
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include) {
|
||||
element_activation *EA;
|
||||
LOOP_OVER_LINKED_LIST(EA, element_activation, K->activations) {
|
||||
int S = Plugins::Manage::parse(EA->element_name);
|
||||
if (S == -1)
|
||||
Problems::Issue::sentence_problem(_p_(Untestable),
|
||||
"one of the Inform kits made reference to a language segment which does not exist",
|
||||
"which strongly suggests that Inform is not properly installed.");
|
||||
if (S >= 0) {
|
||||
if (EA->activate) Plugins::Manage::activate(S);
|
||||
else Plugins::Manage::deactivate(S);
|
||||
}
|
||||
void Kits::activate_plugins(inform_kit *K) {
|
||||
element_activation *EA;
|
||||
LOOP_OVER_LINKED_LIST(EA, element_activation, K->activations) {
|
||||
int S = Plugins::Manage::parse(EA->element_name);
|
||||
if (S == -1)
|
||||
Problems::Issue::sentence_problem(_p_(Untestable),
|
||||
"one of the Inform kits made reference to a language segment which does not exist",
|
||||
"which strongly suggests that Inform is not properly installed.");
|
||||
if (S >= 0) {
|
||||
if (EA->activate) Plugins::Manage::activate(S);
|
||||
else Plugins::Manage::deactivate(S);
|
||||
}
|
||||
}
|
||||
Plugins::Manage::show(DL, "Included", TRUE);
|
||||
Plugins::Manage::show(DL, "Excluded", FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
int Kits::Main_defined(void) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include)
|
||||
if (K->defines_Main)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
void Kits::early_source_text(OUTPUT_STREAM, inform_kit *K) {
|
||||
text_stream *X;
|
||||
LOOP_OVER_LINKED_LIST(X, text_stream, K->extensions)
|
||||
WRITE("Include %S.\n\n", X);
|
||||
if (K->early_source) WRITE("%S\n\n", K->early_source);
|
||||
}
|
||||
|
||||
text_stream *Kits::index_template(void) {
|
||||
text_stream *I = NULL;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include)
|
||||
if (K->index_template)
|
||||
I = K->index_template;
|
||||
return I;
|
||||
}
|
||||
|
||||
@ Every source text read into Inform is automatically prefixed by a few words
|
||||
loading the fundamental "extensions" -- text such as "Include Basic Inform by
|
||||
Graham Nelson." If Inform were a computer, this would be the BIOS which boots
|
||||
up its operating system. Each kit can contribute such extensions, so there
|
||||
may be multiple sentences, which we need to count up.
|
||||
|
||||
=
|
||||
void Kits::feed_early_source_text(OUTPUT_STREAM) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include) {
|
||||
text_stream *X;
|
||||
LOOP_OVER_LINKED_LIST(X, text_stream, K->extensions)
|
||||
WRITE("Include %S.\n\n", X);
|
||||
if (K->early_source) WRITE("%S\n\n", K->early_source);
|
||||
}
|
||||
}
|
||||
|
||||
int Kits::number_of_early_fed_sentences(void) {
|
||||
int Kits::number_of_early_fed_sentences(inform_kit *K) {
|
||||
int N = 0;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include) {
|
||||
text_stream *X;
|
||||
LOOP_OVER_LINKED_LIST(X, text_stream, K->extensions) N++;
|
||||
if (K->early_source) N++;
|
||||
}
|
||||
text_stream *X;
|
||||
LOOP_OVER_LINKED_LIST(X, text_stream, K->extensions) N++;
|
||||
if (K->early_source) N++;
|
||||
return N;
|
||||
}
|
||||
|
||||
#ifdef CODEGEN_MODULE
|
||||
linked_list *requirements_list = NULL;
|
||||
linked_list *Kits::list_of_inter_libraries(void) {
|
||||
requirements_list = NEW_LINKED_LIST(link_instruction);
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, kits_to_include) {
|
||||
link_instruction *link = CodeGen::LinkInstructions::new(
|
||||
K->as_copy->location_if_path, K->attachment_point);
|
||||
ADD_TO_LINKED_LIST(link, link_instruction, requirements_list);
|
||||
}
|
||||
return requirements_list;
|
||||
}
|
||||
#endif
|
||||
|
||||
linked_list *Kits::inter_paths(void) {
|
||||
linked_list *inter_paths = NEW_LINKED_LIST(pathname);
|
||||
inbuild_nest *N;
|
||||
|
|
|
@ -125,7 +125,7 @@ that's the language in which the SR are written, and French Language, because
|
|||
that's the language of play.
|
||||
|
||||
=
|
||||
void Languages::request_required_kits(void) {
|
||||
void Languages::request_required_kits(inform_project *project) {
|
||||
inform_language *L;
|
||||
LOOP_OVER(L, inform_language)
|
||||
if (L->kit_required) {
|
||||
|
@ -134,7 +134,7 @@ void Languages::request_required_kits(void) {
|
|||
WRITE_TO(TEMP, "%+W", L->language_field[KIT_LFIELD]);
|
||||
else
|
||||
WRITE_TO(TEMP, "%+WLanguageKit", L->language_field[NAME_IN_ENGLISH_LFIELD]);
|
||||
Kits::request(TEMP);
|
||||
Projects::add_kit_dependency(project, TEMP);
|
||||
DISCARD_TEXT(TEMP);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,54 @@ An Inform 7 project.
|
|||
typedef struct inform_project {
|
||||
struct inbuild_copy *as_copy;
|
||||
struct inbuild_version_number version;
|
||||
struct filename *source_text;
|
||||
struct linked_list *source_vertices; /* of |build_vertex| */
|
||||
int assumed_to_be_parser_IF;
|
||||
struct linked_list *kits_to_include; /* of |inform_kit| */
|
||||
MEMORY_MANAGEMENT
|
||||
} inform_project;
|
||||
|
||||
inform_project *Projects::new_ip(text_stream *name, filename *F, pathname *P) {
|
||||
inform_project *T = CREATE(inform_project);
|
||||
T->as_copy = NULL;
|
||||
T->version = VersionNumbers::null();
|
||||
return T;
|
||||
inform_project *project = CREATE(inform_project);
|
||||
project->as_copy = NULL;
|
||||
project->version = VersionNumbers::null();
|
||||
project->source_vertices = NEW_LINKED_LIST(build_vertex);
|
||||
project->kits_to_include = NEW_LINKED_LIST(inform_kit);
|
||||
project->assumed_to_be_parser_IF = TRUE;
|
||||
return project;
|
||||
}
|
||||
|
||||
void Projects::set_source_filename(inform_project *project, filename *F) {
|
||||
project->source_text = F;
|
||||
void Projects::not_necessarily_parser_IF(inform_project *project) {
|
||||
project->assumed_to_be_parser_IF = FALSE;
|
||||
}
|
||||
|
||||
void Projects::set_source_filename(inform_project *project, pathname *P, filename *F) {
|
||||
if (P) {
|
||||
filename *manifest = Filenames::in_folder(P, I"Contents.txt");
|
||||
linked_list *L = NEW_LINKED_LIST(text_stream);
|
||||
TextFiles::read(manifest, FALSE,
|
||||
NULL, FALSE, Projects::manifest_helper, NULL, (void *) L);
|
||||
text_stream *leafname;
|
||||
LOOP_OVER_LINKED_LIST(leafname, text_stream, L) {
|
||||
build_vertex *S = Graphs::internal_vertex(Filenames::in_folder(P, leafname));
|
||||
S->annotation = leafname;
|
||||
ADD_TO_LINKED_LIST(S, build_vertex, project->source_vertices);
|
||||
}
|
||||
}
|
||||
if ((LinkedLists::len(project->source_vertices) == 0) && (F)) {
|
||||
build_vertex *S = Graphs::internal_vertex(F);
|
||||
S->annotation = I"your source text";
|
||||
ADD_TO_LINKED_LIST(S, build_vertex, project->source_vertices);
|
||||
}
|
||||
}
|
||||
|
||||
// Graphs::arrow(project->as_copy->vertex, S);
|
||||
|
||||
void Projects::manifest_helper(text_stream *text, text_file_position *tfp, void *state) {
|
||||
linked_list *L = (linked_list *) state;
|
||||
Str::trim_white_space(text);
|
||||
wchar_t c = Str::get_first_char(text);
|
||||
if ((c == 0) || (c == '#')) return;
|
||||
ADD_TO_LINKED_LIST(Str::duplicate(text), text_stream, L);
|
||||
}
|
||||
|
||||
pathname *Projects::path(inform_project *project) {
|
||||
|
@ -26,7 +61,129 @@ pathname *Projects::path(inform_project *project) {
|
|||
return project->as_copy->location_if_path;
|
||||
}
|
||||
|
||||
filename *Projects::source(inform_project *project) {
|
||||
linked_list *Projects::source(inform_project *project) {
|
||||
if (project == NULL) return NULL;
|
||||
return project->source_text;
|
||||
return project->source_vertices;
|
||||
}
|
||||
|
||||
void Projects::add_kit_dependency(inform_project *project, text_stream *kit_name) {
|
||||
if (Projects::uses_kit(project, kit_name)) return;
|
||||
linked_list *nest_list = SharedCLI::nest_list();
|
||||
inform_kit *kit = Kits::load(kit_name, nest_list);
|
||||
ADD_TO_LINKED_LIST(kit, inform_kit, project->kits_to_include);
|
||||
}
|
||||
|
||||
int Projects::uses_kit(inform_project *project, text_stream *name) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
if (Str::eq(K->name, name))
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void Projects::finalise_kit_dependencies(inform_project *project) {
|
||||
Projects::add_kit_dependency(project, I"BasicInformKit");
|
||||
Languages::request_required_kits(project);
|
||||
if (project->assumed_to_be_parser_IF)
|
||||
Projects::add_kit_dependency(project, I"CommandParserKit");
|
||||
|
||||
int parity = TRUE;
|
||||
@<Perform if-this-then-that@>;
|
||||
parity = FALSE;
|
||||
@<Perform if-this-then-that@>;
|
||||
|
||||
linked_list *sorted = NEW_LINKED_LIST(inform_kit);
|
||||
for (int p=0; p<100; p++) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
if (K->priority == p)
|
||||
ADD_TO_LINKED_LIST(K, inform_kit, sorted);
|
||||
}
|
||||
|
||||
project->kits_to_include = sorted;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
LOG("Using Inform kit '%S' (priority %d).\n", K->name, K->priority);
|
||||
}
|
||||
|
||||
@<Perform if-this-then-that@> =
|
||||
int changes_made = TRUE;
|
||||
while (changes_made) {
|
||||
changes_made = FALSE;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
if (Kits::perform_ittt(K, project, parity))
|
||||
changes_made = TRUE;
|
||||
}
|
||||
|
||||
@ =
|
||||
#ifdef CORE_MODULE
|
||||
void Projects::load_types(inform_project *project) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
Kits::load_types(K);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CORE_MODULE
|
||||
void Projects::activate_plugins(inform_project *project) {
|
||||
LOG("Activate plugins...\n");
|
||||
Plugins::Manage::activate(CORE_PLUGIN_NAME);
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
Kits::activate_plugins(K);
|
||||
Plugins::Manage::show(DL, "Included", TRUE);
|
||||
Plugins::Manage::show(DL, "Excluded", FALSE);
|
||||
}
|
||||
#endif
|
||||
|
||||
int Projects::Main_defined(inform_project *project) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
if (K->defines_Main)
|
||||
return TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
text_stream *Projects::index_template(inform_project *project) {
|
||||
text_stream *I = NULL;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
if (K->index_template)
|
||||
I = K->index_template;
|
||||
return I;
|
||||
}
|
||||
|
||||
@ Every source text read into Inform is automatically prefixed by a few words
|
||||
loading the fundamental "extensions" -- text such as "Include Basic Inform by
|
||||
Graham Nelson." If Inform were a computer, this would be the BIOS which boots
|
||||
up its operating system. Each kit can contribute such extensions, so there
|
||||
may be multiple sentences, which we need to count up.
|
||||
|
||||
=
|
||||
void Projects::early_source_text(OUTPUT_STREAM, inform_project *project) {
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
Kits::early_source_text(OUT, K);
|
||||
}
|
||||
|
||||
int Projects::number_of_early_fed_sentences(inform_project *project) {
|
||||
int N = 0;
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include)
|
||||
N += Kits::number_of_early_fed_sentences(K);
|
||||
return N;
|
||||
}
|
||||
|
||||
#ifdef CODEGEN_MODULE
|
||||
linked_list *Projects::list_of_inter_libraries(inform_project *project) {
|
||||
linked_list *requirements_list = NEW_LINKED_LIST(link_instruction);
|
||||
inform_kit *K;
|
||||
LOOP_OVER_LINKED_LIST(K, inform_kit, project->kits_to_include) {
|
||||
link_instruction *link = CodeGen::LinkInstructions::new(
|
||||
K->as_copy->location_if_path, K->attachment_point);
|
||||
ADD_TO_LINKED_LIST(link, link_instruction, requirements_list);
|
||||
}
|
||||
return requirements_list;
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -212,7 +212,7 @@ list is not exhaustive.
|
|||
doc_references_top = lexer_wordcount - 1;
|
||||
|
||||
@<Work out our kit requirements@> =
|
||||
Kits::determine();
|
||||
Projects::finalise_kit_dependencies(SharedCLI::project());
|
||||
|
||||
@<Perform lexical analysis@> =
|
||||
ProgressBar::update_progress_bar(0, 0);
|
||||
|
@ -223,7 +223,7 @@ list is not exhaustive.
|
|||
@<Perform semantic analysis@> =
|
||||
ProgressBar::update_progress_bar(1, 0);
|
||||
if (problem_count == 0) CoreMain::go_to_log_phase(I"Semantic analysis Ia");
|
||||
COMPILATION_STEP(Kits::activate_plugins, I"Kits::activate_plugins");
|
||||
Projects::activate_plugins(SharedCLI::project());
|
||||
COMPILATION_STEP(ParseTreeUsage::plant_parse_tree, I"ParseTreeUsage::plant_parse_tree")
|
||||
COMPILATION_STEP(StructuralSentences::break_source, I"StructuralSentences::break_source")
|
||||
COMPILATION_STEP(Extensions::Inclusion::traverse, I"Extensions::Inclusion::traverse")
|
||||
|
@ -231,7 +231,7 @@ list is not exhaustive.
|
|||
|
||||
if (problem_count == 0) CoreMain::go_to_log_phase(I"Initialise language semantics");
|
||||
COMPILATION_STEP(Plugins::Manage::start_plugins, I"Plugins::Manage::start_plugins");
|
||||
COMPILATION_STEP(Kits::load_types, I"Kits::load_types");
|
||||
Projects::load_types(SharedCLI::project());
|
||||
COMPILATION_STEP(BinaryPredicates::make_built_in, I"BinaryPredicates::make_built_in")
|
||||
COMPILATION_STEP(NewVerbs::add_inequalities, I"NewVerbs::add_inequalities")
|
||||
|
||||
|
@ -368,7 +368,7 @@ with "Output.i6t".
|
|||
|
||||
COMPILATION_STEP(Lists::check, I"Lists::check")
|
||||
COMPILATION_STEP(Lists::compile, I"Lists::compile")
|
||||
if (Kits::Main_defined() == FALSE)
|
||||
if (Projects::Main_defined(SharedCLI::project()) == FALSE)
|
||||
COMPILATION_STEP(Phrases::invoke_to_begin, I"Phrases::invoke_to_begin")
|
||||
COMPILATION_STEP(Phrases::Manager::compile_as_needed, I"Phrases::Manager::compile_as_needed")
|
||||
COMPILATION_STEP(Strings::compile_responses, I"Strings::compile_responses")
|
||||
|
@ -450,7 +450,7 @@ with "Output.i6t".
|
|||
}
|
||||
CodeGen::Pipeline::set_repository(SS, Emit::tree());
|
||||
CodeGen::Pipeline::run(Filenames::get_path_to(filename_of_compiled_i6_code),
|
||||
SS, Kits::inter_paths(), Kits::list_of_inter_libraries());
|
||||
SS, Kits::inter_paths(), Projects::list_of_inter_libraries(SharedCLI::project()));
|
||||
}
|
||||
LOG("Back end elapsed time: %dcs\n", ((int) (clock() - front_end)) / (CLOCKS_PER_SEC/100));
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ inform_language *NaturalLanguages::English(void) {
|
|||
void NaturalLanguages::produce_index(void) {
|
||||
I6T::interpret_indext(
|
||||
Filenames::in_folder(
|
||||
Languages::path_to_bundle(language_of_index), Kits::index_template()));
|
||||
Languages::path_to_bundle(language_of_index), Projects::index_template(SharedCLI::project())));
|
||||
}
|
||||
|
||||
@
|
||||
|
|
|
@ -30,11 +30,29 @@ int SourceFiles::read_extension_source_text(extension_file *EF,
|
|||
|
||||
void SourceFiles::read_primary_source_text(void) {
|
||||
TEMPORARY_TEXT(early);
|
||||
Kits::feed_early_source_text(early);
|
||||
Projects::early_source_text(early, SharedCLI::project());
|
||||
if (Str::len(early) > 0) Feeds::feed_stream(early);
|
||||
DISCARD_TEXT(early);
|
||||
SourceFiles::read_further_mandatory_text();
|
||||
SourceFiles::read_file(Projects::source(SharedCLI::project()), I"your source text", NULL, FALSE);
|
||||
linked_list *L = Projects::source(SharedCLI::project());
|
||||
if (L) {
|
||||
build_vertex *N;
|
||||
LOOP_OVER_LINKED_LIST(N, build_vertex, L) {
|
||||
filename *F = N->buildable_if_internal_file;
|
||||
if (TextFiles::exists(F) == FALSE) {
|
||||
Problems::quote_stream(1, Filenames::get_leafname(F));
|
||||
Problems::Issue::handmade_problem(_p_(Untestable));
|
||||
Problems::issue_problem_segment(
|
||||
"I can't open the file '%1' of source text. %P"
|
||||
"If you are using the 'Source' subfolder of Materials to "
|
||||
"hold your source text, maybe your 'Contents.txt' has a "
|
||||
"typo in it?");
|
||||
Problems::issue_problem_end();
|
||||
} else {
|
||||
SourceFiles::read_file(F, N->annotation, NULL, FALSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ The following reads in the text of the optional file of use options, if
|
||||
|
|
|
@ -453,7 +453,7 @@ source texts implicitly begin with an inclusion of the Standard Rules.)
|
|||
<if-start-of-source-text> internal 0 {
|
||||
int w1 = Wordings::first_wn(W);
|
||||
#ifdef CORE_MODULE
|
||||
int N = 1 + Kits::number_of_early_fed_sentences();
|
||||
int N = 1 + Projects::number_of_early_fed_sentences(SharedCLI::project());
|
||||
#endif
|
||||
#ifndef CORE_MODULE
|
||||
int N = 3;
|
||||
|
|
Loading…
Reference in a new issue