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

Improved requirements and searching

This commit is contained in:
Graham Nelson 2020-02-05 10:10:07 +00:00
parent ace0230321
commit bee49ecc3d
10 changed files with 279 additions and 106 deletions

View file

@ -18,12 +18,12 @@ this plan out.
pathname *path_to_inbuild = NULL;
pathname *path_to_tools = NULL;
linked_list *nest_list = NULL;
linked_list *find_list = NULL;
int inbuild_task = INSPECT_TTASK;
int dry_run_mode = FALSE;
linked_list *targets = NULL; /* of |inbuild_copy| */
inbuild_nest *destination_nest = NULL;
text_stream *filter_text = NULL;
int main(int argc, char **argv) {
Foundation::start();
@ -31,7 +31,6 @@ int main(int argc, char **argv) {
InbuildModule::start();
targets = NEW_LINKED_LIST(inbuild_copy);
nest_list = NEW_LINKED_LIST(inbuild_nest);
find_list = NEW_LINKED_LIST(text_stream);
@<Read the command line@>;
if (FIRST_IN_LINKED_LIST(inbuild_nest, nest_list) == NULL)
Nests::add_to_search_sequence(nest_list,
@ -41,26 +40,39 @@ int main(int argc, char **argv) {
if (path_to_tools) BM = BuildSteps::methodology(path_to_tools, FALSE);
else BM = BuildSteps::methodology(Pathnames::up(path_to_inbuild), TRUE);
if (dry_run_mode == FALSE) BM->methodology = SHELL_METHODOLOGY;
text_stream *T;
LOOP_OVER_LINKED_LIST(T, text_stream, find_list) {
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
inbuild_work *work = Works::new(kit_genre, T, I"");
inbuild_requirement *req = Model::requirement(work,
VersionNumbers::null(), VersionNumbers::null());
Nests::locate(req, nest_list, L);
int n = 0;
inbuild_search_result *R;
LOOP_OVER_LINKED_LIST(R, inbuild_search_result, L) {
Model::write_copy(STDOUT, R->copy);
WRITE_TO(STDOUT, " (in nest %p)\n", R->nest->location);
n++;
if (Str::len(filter_text) > 0) {
if (LinkedLists::len(nest_list) == 0) {
Errors::with_text("can't apply this, since no nests have been given: '%S'", filter_text);
} else {
TEMPORARY_TEXT(errors);
inbuild_requirement *req = Requirements::from_text(filter_text, errors);
if (Str::len(errors) > 0) {
Errors::with_text("requirement malformed: %S", errors);
} else {
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
Nests::locate(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);
}
}
DISCARD_TEXT(errors);
}
if (n == 0) WRITE_TO(STDOUT, "%S not found\n", T);
}
inbuild_copy *C;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, targets) {
switch (inbuild_task) {
case INSPECT_TTASK: Graphs::describe(STDOUT, C->graph, FALSE); break;
case INSPECT_TTASK:
WRITE_TO(STDOUT, "%S: ", Model::genre_name(C->edition->work->genre));
Model::write_copy(STDOUT, C);
if (C->location_if_path) {
WRITE_TO(STDOUT, " at path %p", C->location_if_path);
}
if (C->location_if_file) {
WRITE_TO(STDOUT, " in directory %p", Filenames::get_path_to(C->location_if_file));
}
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;
@ -83,8 +95,8 @@ int main(int argc, char **argv) {
@e DRY_CLSW
@e TOOLS_CLSW
@e CONTENTS_OF_CLSW
@e MATCHING_CLSW
@e NEST_CLSW
@e FIND_CLSW
@e COPY_TO_CLSW
@e SYNC_TO_CLSW
@ -108,12 +120,12 @@ int main(int argc, char **argv) {
L"make X the directory of intools executables, and exit developer mode");
CommandLine::declare_boolean_switch(DRY_CLSW, L"dry", 1,
L"make this a dry run (print but do not execute shell commands)");
CommandLine::declare_boolean_switch(CONTENTS_OF_CLSW, L"contents-of", 2,
CommandLine::declare_switch(MATCHING_CLSW, L"matching", 2,
L"apply to all works in nest(s) matching requirement X");
CommandLine::declare_switch(CONTENTS_OF_CLSW, L"contents-of", 2,
L"apply to all targets in the directory X");
CommandLine::declare_switch(NEST_CLSW, L"nest", 2,
L"add the nest at pathname X to the search list");
CommandLine::declare_switch(FIND_CLSW, L"find", 2,
L"find copies in nests in the search list");
CommandLine::read(argc, argv, NULL, &Main::option, &Main::bareword);
@ -125,11 +137,11 @@ void Main::option(int id, int val, text_stream *arg, void *state) {
case INSPECT_CLSW: inbuild_task = INSPECT_TTASK; break;
case GRAPH_CLSW: inbuild_task = GRAPH_TTASK; break;
case TOOLS_CLSW: path_to_tools = Pathnames::from_text(arg); break;
case MATCHING_CLSW: filter_text = Str::duplicate(arg); break;
case CONTENTS_OF_CLSW: Main::load_many(Pathnames::from_text(arg)); break;
case DRY_CLSW: dry_run_mode = val; break;
case NEST_CLSW: Nests::add_to_search_sequence(nest_list,
Nests::new(Pathnames::from_text(arg))); break;
case FIND_CLSW: ADD_TO_LINKED_LIST(Str::duplicate(arg), text_stream, find_list); break;
case COPY_TO_CLSW: inbuild_task = COPY_TO_TTASK;
destination_nest = Nests::new(Pathnames::from_text(arg));
break;

View file

@ -33,6 +33,11 @@ inbuild_genre *Model::genre(text_stream *name) {
return gen;
}
text_stream *Model::genre_name(inbuild_genre *G) {
if (G == NULL) return I"(none)";
return G->genre_name;
}
@h Editions.
An "edition" of a work is a particular version numbered form of it. For
example, release 7 of Bronze by Emily Short would be an edition of Bronze.
@ -51,52 +56,6 @@ inbuild_edition *Model::edition(inbuild_work *work, inbuild_version_number versi
return edition;
}
@h Requirements.
A "requirement" is when we want to get hold of a work in some edition which
meets a range of possible versions. A null minimum version means "no minimum",
a null maximum means "no maximum".
=
typedef struct inbuild_requirement {
struct inbuild_work *work;
struct inbuild_version_number min_version;
struct inbuild_version_number max_version;
MEMORY_MANAGEMENT
} inbuild_requirement;
inbuild_requirement *Model::requirement(inbuild_work *work,
inbuild_version_number min, inbuild_version_number max) {
inbuild_requirement *req = CREATE(inbuild_requirement);
req->work = work;
req->min_version = min;
req->max_version = max;
return req;
}
int Model::meets(inbuild_version_number V, inbuild_requirement *req) {
if (req == NULL) return TRUE;
if (VersionNumbers::is_null(req->min_version) == FALSE) {
if (VersionNumbers::is_null(V)) return FALSE;
if (VersionNumbers::lt(V, req->min_version)) return FALSE;
}
if (VersionNumbers::is_null(req->max_version) == FALSE) {
if (VersionNumbers::is_null(V)) return TRUE;
if (VersionNumbers::gt(V, req->max_version)) return FALSE;
}
return TRUE;
}
int Model::ratchet_minimum(inbuild_version_number V, inbuild_requirement *req) {
if (req == NULL) internal_error("no requirement");
if (VersionNumbers::is_null(V)) return FALSE;
if ((VersionNumbers::is_null(req->min_version)) ||
(VersionNumbers::gt(V, req->min_version))) {
req->min_version = V;
return TRUE;
}
return FALSE;
}
@h Copies.
A "copy" of a work exists in the file system when we've actually got hold of
some edition of it. For some genres, copies will be files; for others,

View file

@ -67,7 +67,9 @@ void Nests::add_to_search_sequence(linked_list *search_list, inbuild_nest *N) {
void Nests::locate(inbuild_requirement *req, linked_list *search_list, linked_list *results) {
inbuild_nest *N;
LOOP_OVER_LINKED_LIST(N, inbuild_nest, search_list) {
VMETHOD_CALL(req->work->genre, GENRE_LOCATION_IN_NEST_MTID, N, req, results);
inbuild_genre *G;
LOOP_OVER(G, inbuild_genre)
VMETHOD_CALL(G, GENRE_LOCATION_IN_NEST_MTID, N, req, results);
}
}

View file

@ -0,0 +1,150 @@
[Requirements::] Requirements.
A requirement is a way to specify some subset of works: for example, those
with a given title, and/or version number.
@ A null minimum version means "no minimum", a null maximum means "no maximum".
=
typedef struct inbuild_requirement {
struct inbuild_work *work;
struct inbuild_version_number min_version;
struct inbuild_version_number max_version;
MEMORY_MANAGEMENT
} inbuild_requirement;
inbuild_requirement *Requirements::new(inbuild_work *work,
inbuild_version_number min, inbuild_version_number max) {
inbuild_requirement *req = CREATE(inbuild_requirement);
req->work = work;
req->min_version = min;
req->max_version = max;
return req;
}
inbuild_requirement *Requirements::any_version_of(inbuild_work *work) {
return Requirements::new(work, VersionNumbers::null(), VersionNumbers::null());
}
inbuild_requirement *Requirements::anything_of_genre(inbuild_genre *G) {
return Requirements::any_version_of(Works::new(G, I"", I""));
}
inbuild_requirement *Requirements::anything(void) {
return Requirements::anything_of_genre(NULL);
}
inbuild_requirement *Requirements::from_text(text_stream *T, text_stream *errors) {
inbuild_requirement *req = Requirements::anything();
int from = 0;
for (int at = 0; at < Str::len(T); at++) {
wchar_t c = Str::get_at(T, at);
if (c == ',') {
TEMPORARY_TEXT(initial);
Str::substr(initial, Str::at(T, from), Str::at(T, at));
Requirements::impose_clause(req, initial, errors);
DISCARD_TEXT(initial);
from = at + 1;
}
}
if (from < Str::len(T)) {
TEMPORARY_TEXT(final);
Str::substr(final, Str::at(T, from), Str::end(T));
Requirements::impose_clause(req, final, errors);
DISCARD_TEXT(final);
}
return req;
}
void Requirements::impose_clause(inbuild_requirement *req, text_stream *T, text_stream *errors) {
Str::trim_white_space(T);
if (Str::eq(T, I"all")) return;
TEMPORARY_TEXT(clause);
TEMPORARY_TEXT(value);
for (int at = 0; at < Str::len(T); at++) {
wchar_t c = Str::get_at(T, at);
if (c == '=') {
Str::substr(clause, Str::start(T), Str::at(T, at));
Str::substr(value, Str::at(T, at+1), Str::end(T));
break;
}
}
Str::trim_white_space(clause);
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);
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);
if (VersionNumbers::is_null(V)) {
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->min_version = V;
req->max_version = V;
} else if (Str::eq(clause, I"min")) {
inbuild_version_number V = VersionNumbers::from_text(value);
if (VersionNumbers::is_null(V)) {
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->min_version = V;
} else if (Str::eq(clause, I"max")) {
inbuild_version_number V = VersionNumbers::from_text(value);
if (VersionNumbers::is_null(V)) {
if (Str::len(errors) == 0)
WRITE_TO(errors, "not a valid version number: '%S'", value);
}
req->max_version = V;
} else {
if (Str::len(errors) == 0)
WRITE_TO(errors, "no such term as '%S'", clause);
}
} else {
if (Str::len(errors) == 0)
WRITE_TO(errors, "clause not in the form 'term=value': '%S'", T);
}
DISCARD_TEXT(clause);
DISCARD_TEXT(value);
}
int Requirements::meets(inbuild_edition *edition, inbuild_requirement *req) {
if (req == NULL) return TRUE;
if (req->work) {
if (req->work->genre) {
if (req->work->genre != edition->work->genre)
return FALSE;
}
if (Str::len(req->work->title) > 0) {
if (Str::ne_insensitive(req->work->title, edition->work->title))
return FALSE;
}
if (Str::len(req->work->author_name) > 0) {
if (Str::ne_insensitive(req->work->author_name, edition->work->author_name))
return FALSE;
}
}
if (VersionNumbers::is_null(req->min_version) == FALSE) {
if (VersionNumbers::is_null(edition->version)) return FALSE;
if (VersionNumbers::lt(edition->version, req->min_version)) return FALSE;
}
if (VersionNumbers::is_null(req->max_version) == FALSE) {
if (VersionNumbers::is_null(edition->version)) return TRUE;
if (VersionNumbers::gt(edition->version, req->max_version)) return FALSE;
}
return TRUE;
}
int Requirements::ratchet_minimum(inbuild_version_number V, inbuild_requirement *req) {
if (req == NULL) internal_error("no requirement");
if (VersionNumbers::is_null(V)) return FALSE;
if ((VersionNumbers::is_null(req->min_version)) ||
(VersionNumbers::gt(V, req->min_version))) {
req->min_version = V;
return TRUE;
}
return FALSE;
}

View file

@ -26,26 +26,30 @@ inbuild_copy *Extensions::claim(text_stream *arg, text_stream *ext, int director
if (directory_status == TRUE) return NULL;
if (Str::eq_insensitive(ext, I"i7x")) {
filename *F = Filenames::from_text(arg);
TEMPORARY_TEXT(author);
TEMPORARY_TEXT(title);
TEMPORARY_TEXT(error_text);
TEMPORARY_TEXT(rubric_text);
TEMPORARY_TEXT(requirement_text);
inbuild_version_number V = Extensions::scan_file(F, title, author, rubric_text, requirement_text, error_text);
inform_extension *E = Extensions::load_at(title, author, F);
if (Str::len(error_text) > 0) return NULL;
Works::add_to_database(E->as_copy->edition->work, CLAIMED_WDBC);
E->version_loaded = V; E->as_copy->edition->version = V;
DISCARD_TEXT(author);
DISCARD_TEXT(title);
DISCARD_TEXT(error_text);
DISCARD_TEXT(rubric_text);
DISCARD_TEXT(requirement_text);
return E->as_copy;
return 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);
}
@ -200,18 +204,45 @@ this is unambiguous.
=
void Extensions::location_in_nest(inbuild_genre *gen, inbuild_nest *N, inbuild_requirement *req, linked_list *search_results) {
pathname *P = Extensions::path_within_nest(N);
for (int i7x_flag = 1; i7x_flag >= 0; i7x_flag--) {
TEMPORARY_TEXT(leaf);
if (i7x_flag) WRITE_TO(leaf, "%S.i7x", req->work->title);
else WRITE_TO(leaf, "%S", req->work->title);
filename *F = Filenames::in_folder(Pathnames::subfolder(P, req->work->author_name), leaf);
if (TextFiles::exists(F)) {
inform_extension *E = Extensions::load_at(req->work->title, req->work->author_name, F);
if (Model::meets(E->version_loaded, req)) {
Nests::add_search_result(search_results, N, E->as_copy);
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(leaf);
DISCARD_TEXT(LEAFNAME);
Directories::close(D);
}
}

View file

@ -28,18 +28,29 @@ inbuild_copy *Kits::claim(text_stream *arg, text_stream *ext, int directory_stat
}
void Kits::write_copy(inbuild_genre *gen, OUTPUT_STREAM, inbuild_work *work) {
WRITE("Kit %S", work->title);
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");
P = Pathnames::subfolder(P, req->work->title);
filename *canary = Filenames::in_folder(P, I"kit_metadata.txt");
if (TextFiles::exists(canary)) {
inform_kit *K = Kits::load_at(Pathnames::directory_name(P), P);
if (Model::meets(K->version, req)) {
Nests::add_search_result(search_results, N, K->as_copy);
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);
}
}
@ -125,6 +136,7 @@ inform_kit *Kits::load_at(text_stream *name, pathname *P) {
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));

View file

@ -11,6 +11,7 @@ Chapter 2: Conceptual Framework
Conceptual Model
Works
Version Numbers
Requirements
Build Graphs
Build Steps
Nests

View file

@ -169,7 +169,7 @@ application to communicate the problem badly.
text_stream *author_name = EF->ef_req->work->author_name;
text_stream *title = EF->ef_req->work->title;
inbuild_work *work = Works::new(extension_genre, title, author_name);
inbuild_requirement *req = Model::requirement(work, VersionNumbers::null(), VersionNumbers::null());
inbuild_requirement *req = Requirements::any_version_of(work);
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
Nests::locate(req, search_list, L);
inbuild_search_result *search_result;

View file

@ -206,7 +206,7 @@ matter to the census errors system elsewhere.
Works::add_to_database(work, LOADED_WDBC);
inbuild_version_number min = VersionNumbers::null();
if (version_word >= 0) min = Extensions::Inclusion::parse_version(version_word);
ef->ef_req = Model::requirement(work, min, VersionNumbers::null());
ef->ef_req = Requirements::new(work, min, VersionNumbers::null());
if (Works::is_standard_rules(work)) standard_rules_extension = ef;
DISCARD_TEXT(exft);
DISCARD_TEXT(exfa);
@ -271,6 +271,12 @@ inbuild_version_number Extensions::Files::get_version(extension_file *ef) {
return E->version_loaded;
}
inbuild_edition *Extensions::Files::get_edition(extension_file *ef) {
inform_extension *E = Extensions::Files::find(ef);
if (E == NULL) return NULL;
return E->as_copy->edition;
}
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");
@ -349,7 +355,7 @@ check that they have been met.
void Extensions::Files::check_versions(void) {
extension_file *ef;
LOOP_OVER(ef, extension_file) {
if (Model::meets(Extensions::Files::get_version(ef), ef->ef_req) == FALSE) {
if (Requirements::meets(Extensions::Files::get_edition(ef), ef->ef_req) == FALSE) {
inbuild_version_number have = Extensions::Files::get_version(ef);
LOG("Need %v, have %v\n", &(ef->ef_req->min_version), &have);
current_sentence = ef->inclusion_sentence;

View file

@ -166,7 +166,7 @@ can't know at load time what we will ultimately require.)
@<This is an extension already loaded, so note any version number hike and return@> =
if (version_word >= 0) {
inbuild_version_number V = Extensions::Inclusion::parse_version(version_word);
if (Model::ratchet_minimum(V, ef->ef_req))
if (Requirements::ratchet_minimum(V, ef->ef_req))
ef->inclusion_sentence = current_sentence;
}
return ef;