From c2fabbe5055150ced1523a29e078c03d0b65dfd7 Mon Sep 17 00:00:00 2001 From: Graham Nelson Date: Tue, 23 May 2023 23:33:29 +0300 Subject: [PATCH] Policing spurious or missing project directory contents --- docs/core-module/2-pwst.html | 7 + docs/inbuild/1-mn.html | 24 ++- docs/inbuild/M-rc.html | 1 + docs/inform7/M-pm.html | 78 ++++--- docs/inform7/preform-diagnostics.txt | 4 +- docs/supervisor-module/2-ce.html | 4 +- docs/supervisor-module/2-cps.html | 2 +- docs/supervisor-module/4-pbm.html | 196 +++++++++++++++++- docs/supervisor-module/5-es.html | 33 ++- inbuild/Figures/help.txt | 1 + .../supervisor-module/Chapter 2/Copy Errors.w | 2 + .../Chapter 4/Project Bundle Manager.w | 171 ++++++++++++++- inform7/Figures/memory-diagnostics.txt | 20 +- inform7/Figures/timings-diagnostics.txt | 45 ++-- .../Chapter 2/Problems With Source Text.w | 7 + 15 files changed, 501 insertions(+), 94 deletions(-) diff --git a/docs/core-module/2-pwst.html b/docs/core-module/2-pwst.html index 28861f6cf..6ae442520 100644 --- a/docs/core-module/2-pwst.html +++ b/docs/core-module/2-pwst.html @@ -118,6 +118,13 @@ group. "which I don't recognise (which is not fine). Specifically, %2."); Problems::issue_problem_end(); break; + case PROJECT_MALFORMED_CE: + Problems::quote_stream(1, CE->details); + StandardProblems::handmade_problem(Task::syntax_tree(), _p_(Untestable)); + Problems::issue_problem_segment( + "This project seems to be malformed. Specifically, %1."); + Problems::issue_problem_end(); + break; case METADATA_MALFORMED_CE: if (CE->copy->found_by) { Problems::quote_work(1, CE->copy->found_by->work); diff --git a/docs/inbuild/1-mn.html b/docs/inbuild/1-mn.html index 42267bfc3..acc0c447f 100644 --- a/docs/inbuild/1-mn.html +++ b/docs/inbuild/1-mn.html @@ -70,6 +70,7 @@ function togglePopup(material_id) { int inbuild_task = INSPECT_TTASK; pathname *path_to_tools = NULL; int dry_run_mode = FALSE, build_trace_mode = FALSE, confirmed = FALSE; +int contents_of_used = FALSE, recursive = FALSE; inbuild_nest *destination_nest = NULL; inbuild_registry *selected_registry = NULL; text_stream *filter_text = NULL; @@ -395,8 +396,21 @@ the order in which filenames are read from a directory listing. } void Main::add_file_or_path_as_target(text_stream *arg, int throwing_error) { + int is_folder = Platform::is_folder_separator(Str::get_last_char(arg)); inbuild_copy *C = Main::file_or_path_to_copy(arg, throwing_error); - if (C) Main::add_target(C); + if (C) { + Main::add_target(C); + } else if ((recursive) && (is_folder)) { + pathname *P = Pathnames::from_text(arg); + linked_list *L = Directories::listing(P); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + TEMPORARY_TEXT(FILENAME) + WRITE_TO(FILENAME, "%p%c%S", P, FOLDER_SEPARATOR, entry); + Main::add_file_or_path_as_target(FILENAME, throwing_error); + DISCARD_TEXT(FILENAME) + } + } }

§2.9. Command line. Note the call below to Supervisor::declare_options, which adds a whole lot of @@ -420,6 +434,7 @@ other options to the selection defined here. enum BUILD_TRACE_CLSW enum TOOLS_CLSW enum CONTENTS_OF_CLSW +enum RECURSIVE_CLSW enum MATCHING_CLSW enum COPY_TO_CLSW enum SYNC_TO_CLSW @@ -483,6 +498,8 @@ other options to the selection defined here. 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_boolean_switch(RECURSIVE_CLSW, L"recursive", 1, + L"run -contents-of recursively to look through subdirectories too", FALSE); CommandLine::declare_switch(VERIFY_REGISTRY_CLSW, L"verify-registry", 2, L"verify roster.json metadata of registry in the directory X"); CommandLine::declare_switch(BUILD_REGISTRY_CLSW, L"build-registry", 2, @@ -529,8 +546,11 @@ other options to the selection defined here. case BUILD_MISSING_CLSW: inbuild_task = BUILD_MISSING_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: + case CONTENTS_OF_CLSW: contents_of_used = TRUE; Main::add_directory_contents_targets(Pathnames::from_text(arg)); break; + case RECURSIVE_CLSW: recursive = val; + if (contents_of_used) Errors::fatal("-recursive must be used before -contents-of"); + break; case DRY_CLSW: dry_run_mode = val; break; case BUILD_TRACE_CLSW: build_trace_mode = val; break; case COPY_TO_CLSW: inbuild_task = COPY_TO_TTASK; diff --git a/docs/inbuild/M-rc.html b/docs/inbuild/M-rc.html index 55f669d0c..124678ab4 100644 --- a/docs/inbuild/M-rc.html +++ b/docs/inbuild/M-rc.html @@ -81,6 +81,7 @@ and those not documented in this manual are covered in that one. -preprocess-html X construct HTML page based on X -preprocess-html-to X set destination for -preprocess-html to be X -rebuild completely rebuild target(s) +-recursive run -contents-of recursively to look through subdirectories too (default is -no-recursive) -no-repair don't quietly fix missing or incorrect extension metadata (default is -repair) -results X write HTML report file to X (for use within Inform GUI apps) -sync-to X forcibly copy target(s) to nest X, even if prior version already there diff --git a/docs/inform7/M-pm.html b/docs/inform7/M-pm.html index 61d0f6a8f..70a84322f 100644 --- a/docs/inform7/M-pm.html +++ b/docs/inform7/M-pm.html @@ -70,38 +70,34 @@ which take more than 1/1000th of the total running time.

 100.0% in inform7 run
-     70.3% in compilation to Inter
-         49.8% in Sequence::undertake_queued_tasks
-          5.0% in MajorNodes::pre_pass
-          3.3% in MajorNodes::pass_1
-          1.8% in ImperativeDefinitions::assess_all
-          1.4% in RTKindConstructors::compile
+     66.5% in compilation to Inter
+         44.8% in Sequence::undertake_queued_tasks
+          4.6% in MajorNodes::pre_pass
+          3.5% in MajorNodes::pass_1
+          1.7% in ImperativeDefinitions::assess_all
+          1.7% in RTKindConstructors::compile
           1.4% in RTPhrasebook::compile_entries
-          1.1% in Sequence::lint_inter
-          0.5% in ImperativeDefinitions::compile_first_block
-          0.5% in MajorNodes::pass_2
-          0.5% in Sequence::undertake_queued_tasks
-          0.5% in Sequence::undertake_queued_tasks
-          0.5% in World::stage_V
-          0.1% in CompletionModule::compile
-          0.1% in InferenceSubjects::emit_all
-          0.1% in RTKindConstructors::compile_permissions
-          0.1% in Task::make_built_in_kind_constructors
-          0.1% in World::stages_II_and_III
-          2.6% not specifically accounted for
-     26.2% in running Inter pipeline
-          9.8% in step 14/15: generate inform6 -> auto.inf
-          6.2% in step 5/15: load-binary-kits
-          5.3% in step 6/15: make-synoptic-module
-          1.6% in step 9/15: make-identifiers-unique
-          0.5% in step 4/15: compile-splats
+          1.0% in Sequence::lint_inter
+          0.7% in ImperativeDefinitions::compile_first_block
+          0.7% in Sequence::undertake_queued_tasks
+          0.7% in Sequence::undertake_queued_tasks
+          0.7% in World::stage_V
+          0.3% in CompletionModule::compile
+          0.3% in MajorNodes::pass_2
+          3.9% not specifically accounted for
+     29.5% in running Inter pipeline
+          9.6% in step 5/15: load-binary-kits
+          8.5% in step 14/15: generate inform6 -> auto.inf
+          5.6% in step 6/15: make-synoptic-module
+          2.1% in step 9/15: make-identifiers-unique
+          0.3% in step 11/15: eliminate-redundant-labels
           0.3% in step 12/15: eliminate-redundant-operations
+          0.3% in step 4/15: compile-splats
           0.3% in step 7/15: shorten-wiring
           0.3% in step 8/15: detect-indirect-calls
-          0.1% in step 11/15: eliminate-redundant-labels
-          1.2% not specifically accounted for
-      2.9% in supervisor
-      0.4% not specifically accounted for
+          1.8% not specifically accounted for
+      3.2% in supervisor
+      0.8% not specifically accounted for
 

§3. Memory consumption. The following gives some idea of which classes of object have the most instances, and also of how Inform's memory tends to be used in practice. @@ -110,19 +106,19 @@ represent less than 1/1000th of the total.

-Total memory consumption was 123156K = 120 MB
+Total memory consumption was 123162K = 120 MB
 
- ---- was used for 2063828 objects, in 367775 frames in 0 x 800K = 0K = 0 MB:
+ ---- was used for 2063850 objects, in 367797 frames in 0 x 800K = 0K = 0 MB:
 
     33.1%  inter_tree_node_array                    58 x 8192 = 475136 objects, 41813824 bytes
     20.9%  text_stream_array                        4684 x 100 = 468400 objects, 26380288 bytes
-    19.7%  linked_list                              44520 objects, 24931200 bytes
+    19.7%  linked_list                              44526 objects, 24934560 bytes
     11.2%  inter_symbol_array                       133 x 1024 = 136192 objects, 14168224 bytes
     10.7%  inter_error_stash_array                  103 x 1024 = 105472 objects, 13503712 bytes
      8.2%  parse_node                               130369 objects, 10429520 bytes
      6.0%  verb_conjugation                         164 objects, 7610912 bytes
      4.4%  parse_node_annotation_array              348 x 500 = 174000 objects, 5579136 bytes
-     2.8%  scan_directory                           866 objects, 3574848 bytes
+     2.8%  scan_directory                           872 objects, 3599616 bytes
      2.6%  pcalc_prop_array                         25 x 1000 = 25000 objects, 3400800 bytes
      2.5%  inter_name_array                         67 x 1000 = 67000 objects, 3218144 bytes
      2.1%  kind_array                               67 x 1000 = 67000 objects, 2682144 bytes
@@ -140,8 +136,8 @@ represent less than 1/1000th of the total.
      0.7%  inter_schema_node                        8965 objects, 1004080 bytes
      0.7%  adjective_meaning                        202 objects, 1000304 bytes
      0.7%  excerpt_meaning                          3119 objects, 973128 bytes
-     0.7%  production                               3968 objects, 920576 bytes
-     0.7%  ptoken                                   8627 objects, 897208 bytes
+     0.7%  production                               3969 objects, 920808 bytes
+     0.7%  ptoken                                   8632 objects, 897728 bytes
      0.6%  grammatical_usage                        3637 objects, 872880 bytes
      0.6%  individual_form                          2567 objects, 862512 bytes
      0.5%  unary_predicate_array                    16 x 1000 = 16000 objects, 640512 bytes
@@ -206,14 +202,14 @@ represent less than 1/1000th of the total.
      ----  parse_node_tree                          33 objects, 28776 bytes
      ----  action_pattern_array                     7 x 100 = 700 objects, 28224 bytes
      ----  shared_variable_set_array                6 x 100 = 600 objects, 24192 bytes
-     ----  filename                                 591 objects, 23640 bytes
+     ----  filename                                 590 objects, 23600 bytes
      ----  property                                 146 objects, 22192 bytes
      ----  backdrops_data                           672 objects, 21504 bytes
      ----  inter_node_list                          646 objects, 20672 bytes
      ----  nonlocal_variable                        94 objects, 20304 bytes
      ----  pipeline_step                            15 objects, 20280 bytes
      ----  action_name                              90 objects, 20160 bytes
-     ----  pathname                                 487 objects, 19480 bytes
+     ----  pathname                                 492 objects, 19680 bytes
      ----  timed_rules_rfd_data                     401 objects, 19248 bytes
      ----  method                                   390 objects, 18720 bytes
      ----  pcalc_prop_deferral                      86 objects, 17888 bytes
@@ -363,16 +359,16 @@ represent less than 1/1000th of the total.
 
 100.0% was used for memory not allocated for objects:
 
-    57.5%  text stream storage                      72566448 bytes in 487987 claims
+    57.5%  text stream storage                      72572792 bytes in 488027 claims
      4.2%  dictionary storage                       5319680 bytes in 7630 claims
-     ----  sorting                                  2720 bytes in 387 claims
+     ----  sorting                                  2752 bytes in 394 claims
      5.7%  source text                              7200000 bytes in 3 claims
      8.5%  source text details                      10800000 bytes in 2 claims
      0.2%  documentation fragments                  262144 bytes in 1 claim
      ----  linguistic stock array                   81920 bytes in 2 claims
      ----  small word set array                     105600 bytes in 22 claims
      3.6%  inter symbols storage                    4558480 bytes in 27989 claims
-    13.3%  inter bytecode storage                   16773540 bytes in 14 claims
+    13.2%  inter bytecode storage                   16773540 bytes in 14 claims
      4.9%  inter links storage                      6222976 bytes in 11 claims
      0.1%  inter tree location list storage         191232 bytes in 32 claims
      1.3%  instance-of-kind counting                1705636 bytes in 1 claim
@@ -381,7 +377,7 @@ represent less than 1/1000th of the total.
      ----  code generation workspace for objects    3480 bytes in 19 claims
      0.2%  emitter array storage                    280544 bytes in 2001 claims
 
--150.0% was overhead - -189255168 bytes = -184819K = -180 MB
+-150.0% was overhead - -189284208 bytes = -184847K = -180 MB
 

§4. Preform grammar. The full annotated description of the Preform grammar (see About Preform (in words)), with optimisation details and hit/miss statistics added, is also long: it's @@ -424,7 +420,7 @@ sample, showing the nonterminal used to parse literals in Inform 7 source text:

diff --git a/docs/inform7/preform-diagnostics.txt b/docs/inform7/preform-diagnostics.txt index a25d1396a..6cedcc9b4 100644 --- a/docs/inform7/preform-diagnostics.txt +++ b/docs/inform7/preform-diagnostics.txt @@ -6199,7 +6199,9 @@ nti 24 constraint (none) extremes [1, infinity) English: - kind/kinds of +
kind/kinds of {} + constraint DS = {24} extremes [4, infinity) + kind/kinds of {} constraint DS = {24} extremes [3, infinity) constraint (none) extremes [1, infinity) diff --git a/docs/supervisor-module/2-ce.html b/docs/supervisor-module/2-ce.html index acdde5fb8..1e1b61e5d 100644 --- a/docs/supervisor-module/2-ce.html +++ b/docs/supervisor-module/2-ce.html @@ -79,6 +79,7 @@ fields are blank. enum EXT_TITLE_TOO_LONG_CE enum EXT_AUTHOR_TOO_LONG_CE enum EXT_RANEOUS_CE +enum PROJECT_MALFORMED_CE enum LANGUAGE_UNAVAILABLE_CE enum LANGUAGE_DEFICIENT_CE enum LEXER_CE an error generated by the words module @@ -126,7 +127,7 @@ fields are blank. return CE; } -copy_error *CopyErrors::new_T(int cat, int subcat, text_stream *NB) { +copy_error *CopyErrors::new_T(int cat, int subcat, text_stream *NB) { copy_error *CE = CopyErrors::new(cat, subcat); CE->details = Str::duplicate(NB); return CE; @@ -199,6 +200,7 @@ output. case EXT_BAD_DIRNAME_CE: WRITE("extension directory name wrong: %S", CE->details); break; case EXT_BAD_FILENAME_CE: WRITE("extension filename wrong: %S", CE->details); break; case EXT_RANEOUS_CE: WRITE("extraneous content: %S", CE->details); break; + case PROJECT_MALFORMED_CE: WRITE("project malformed: %S", CE->details); break; case METADATA_MALFORMED_CE: WRITE("%S has incorrect metadata: %S", CE->copy->edition->work->genre->genre_name, CE->details); break; case EXT_TITLE_TOO_LONG_CE: WRITE("title too long: %d characters (max is %d)", diff --git a/docs/supervisor-module/2-cps.html b/docs/supervisor-module/2-cps.html index 0f846c506..6d861ec4c 100644 --- a/docs/supervisor-module/2-cps.html +++ b/docs/supervisor-module/2-cps.html @@ -147,7 +147,7 @@ for later reporting. These are stored in a list.

-void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
+void Copies::attach_error(inbuild_copy *C, copy_error *CE) {
     if (C == NULL) internal_error("no copy to attach to");
     CopyErrors::supply_attached_copy(CE, C);
     ADD_TO_LINKED_LIST(CE, copy_error, C->errors_reading_source_text);
diff --git a/docs/supervisor-module/4-pbm.html b/docs/supervisor-module/4-pbm.html
index f59ddeaff..f032dce4c 100644
--- a/docs/supervisor-module/4-pbm.html
+++ b/docs/supervisor-module/4-pbm.html
@@ -132,12 +132,200 @@ not a file, false if we know the reverse, and otherwise not applicable.
 }
 
 inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) {
-    filename *canary = Filenames::in(Pathnames::down(P, I"Source"), I"story.ni");
-    if (TextFiles::exists(canary))
-        return ProjectBundleManager::new_copy(Pathnames::directory_name(P), P);
-    return NULL;
+    if (Directories::exists(P) == FALSE) return NULL;
+    inbuild_copy *C = ProjectBundleManager::new_copy(Pathnames::directory_name(P), P);
+    Police extraneous contents3.1;
+    return C;
 }
 
+

§3.1. Police extraneous contents3.1 = +

+ +
+    int uuid_found = FALSE, source_found = FALSE;
+    linked_list *L = Directories::listing(P);
+    text_stream *entry;
+    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
+        if (Platform::is_folder_separator(Str::get_last_char(entry))) {
+            TEMPORARY_TEXT(subdir)
+            WRITE_TO(subdir, "%S", entry);
+            Str::delete_last_character(subdir);
+            if (Str::eq_insensitive(subdir, I"Source")) {
+                Police Source contents3.1.1;
+            } else if (Str::eq_insensitive(subdir, I"Build")) {
+                Police Build contents3.1.2;
+            } else if (Str::eq_insensitive(subdir, I"Index")) {
+                Police Index contents3.1.3;
+            } else if (Str::eq_insensitive(subdir, I"Details")) {
+                Police spurious Details contents3.1.4;
+            } else {
+                TEMPORARY_TEXT(error_text)
+                WRITE_TO(error_text,
+                    "the project directory '%S' contains a subdirectory called '%S', "
+                    "which I don't recognise",
+                    Pathnames::directory_name(P), subdir);
+                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                DISCARD_TEXT(error_text)
+            }
+            DISCARD_TEXT(subdir)
+        } else {
+            if (Str::eq_insensitive(entry, I"manifest.plist")) continue;
+            if (Str::eq_insensitive(entry, I"Metadata.iFiction")) continue;
+            if (Str::eq_insensitive(entry, I"notes.rtf")) continue;
+            if (Str::eq_insensitive(entry, I"Release.blurb")) continue;
+            if (Str::eq_insensitive(entry, I"Settings.plist")) continue;
+            if (Str::eq_insensitive(entry, I"Skein.skein")) continue;
+            if (Str::eq_insensitive(entry, I"uuid.txt")) { uuid_found = TRUE; continue; }
+            TEMPORARY_TEXT(error_text)
+            WRITE_TO(error_text,
+                "the project directory '%S' contains a file called '%S', "
+                "which I don't recognise",
+                Pathnames::directory_name(P), entry);
+            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            DISCARD_TEXT(error_text)
+        }
+    }
+    if (uuid_found == FALSE) {
+        TEMPORARY_TEXT(error_text)
+        WRITE_TO(error_text,
+            "the project directory '%S' does not contain a 'uuid.txt' file",
+            Pathnames::directory_name(P));
+        Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, error_text));
+        DISCARD_TEXT(error_text)
+    }
+    if (source_found == FALSE) {
+        TEMPORARY_TEXT(error_text)
+        WRITE_TO(error_text,
+            "the project directory '%S' does not contain a 'story.ni' source file in a "
+            "'Source' subdirectory",
+            Pathnames::directory_name(P));
+        Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, error_text));
+        DISCARD_TEXT(error_text)
+    }
+
+
  • This code is used in §3.
+

§3.1.1. Police Source contents3.1.1 = +

+ +
+    pathname *Q = Pathnames::down(P, subdir);
+    linked_list *L = Directories::listing(Q);
+    text_stream *entry;
+    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
+        if (Platform::is_folder_separator(Str::get_last_char(entry))) {
+            TEMPORARY_TEXT(subdir)
+            WRITE_TO(subdir, "%S", entry);
+            Str::delete_last_character(subdir);
+            TEMPORARY_TEXT(error_text)
+            WRITE_TO(error_text,
+                "the 'Source' subdirectory of the project directory '%S' contains a "
+                "further subdirectory called '%S', but should not have further subdirectories",
+                Pathnames::directory_name(P), subdir);
+            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            DISCARD_TEXT(error_text)
+            DISCARD_TEXT(subdir)
+        } else {
+            if (Str::eq_insensitive(entry, I"story.ni")) { source_found = TRUE; continue; }
+            TEMPORARY_TEXT(error_text)
+            WRITE_TO(error_text,
+                "the 'Source' subdirectory of the project directory '%S' contains a "
+                "file called '%S', but should only contain the source text file 'story.ni'",
+                Pathnames::directory_name(P), entry);
+            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            DISCARD_TEXT(error_text)
+        }
+    }
+
+
  • This code is used in §3.1.
+

§3.1.2. Police Build contents3.1.2 = +

+ +
+    pathname *Q = Pathnames::down(P, subdir);
+    linked_list *L = Directories::listing(Q);
+    text_stream *entry;
+    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
+        if (Platform::is_folder_separator(Str::get_last_char(entry))) {
+            TEMPORARY_TEXT(subdir)
+            WRITE_TO(subdir, "%S", entry);
+            Str::delete_last_character(subdir);
+            TEMPORARY_TEXT(error_text)
+            WRITE_TO(error_text,
+                "the 'Build' subdirectory of the project directory '%S' contains a "
+                "further subdirectory called '%S', but should not have further subdirectories",
+                Pathnames::directory_name(P), subdir);
+            Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+            DISCARD_TEXT(error_text)
+            DISCARD_TEXT(subdir)
+        }
+    }
+
+
  • This code is used in §3.1.
+

§3.1.3. Police Index contents3.1.3 = +

+ +
+    pathname *Q = Pathnames::down(P, subdir);
+    linked_list *L = Directories::listing(Q);
+    text_stream *entry;
+    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
+        if (Platform::is_folder_separator(Str::get_last_char(entry))) {
+            TEMPORARY_TEXT(subdir)
+            WRITE_TO(subdir, "%S", entry);
+            Str::delete_last_character(subdir);
+            if (Str::eq_insensitive(subdir, I"Details")) {
+                Q = Pathnames::down(Q, subdir);
+                Check for non-HTML files3.1.3.1;
+            } else {
+                TEMPORARY_TEXT(error_text)
+                WRITE_TO(error_text,
+                    "the 'Index' subdirectory of the project directory '%S' contains a "
+                    "further subdirectory called '%S', but can only have one, 'Details'",
+                    Pathnames::directory_name(P), subdir);
+                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                DISCARD_TEXT(error_text)
+            }
+            DISCARD_TEXT(subdir)
+        }
+    }
+    Check for non-HTML files3.1.3.1;
+
+
  • This code is used in §3.1.
+

§3.1.3.1. Check for non-HTML files3.1.3.1 = +

+ +
+    linked_list *L = Directories::listing(Q);
+    text_stream *entry;
+    LOOP_OVER_LINKED_LIST(entry, text_stream, L) {
+        if (Platform::is_folder_separator(Str::get_last_char(entry)) == FALSE) {
+            TEMPORARY_TEXT(ext)
+            Filenames::write_extension(ext, Filenames::from_text(entry));
+            if (Str::eq_insensitive(ext, I".html") == FALSE) {
+                TEMPORARY_TEXT(error_text)
+                WRITE_TO(error_text,
+                    "the 'Index' subdirectory of the project directory '%S' contains a "
+                    "file called '%S', but can only contain HTML files",
+                    Pathnames::directory_name(P), entry);
+                Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text));
+                DISCARD_TEXT(error_text)
+            }
+        }
+    }
+
+
  • This code is used in §3.1.3 (twice).
+

§3.1.4. For now, we will allow a subdirectory called Details to exist, because a bug +in intest at one point caused the temporary workspace projects used when testing Inform +to be created with such a subdirectory. +

+ +

Police spurious Details contents3.1.4 = +

+ +
+    ;
+
+
  • This code is used in §3.1.

§4. Searching. Here we look through a nest to find all projects matching the supplied requirements; though in fact... projects are not nesting birds.

diff --git a/docs/supervisor-module/5-es.html b/docs/supervisor-module/5-es.html index 338fcbf7b..c19d6ed95 100644 --- a/docs/supervisor-module/5-es.html +++ b/docs/supervisor-module/5-es.html @@ -345,14 +345,35 @@ not a situation we need to contend with.

-    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 {
-        Str::copy(claimed_title, titling_line);
+    int quote_found = FALSE, brackets_underflowed = FALSE, brackets_in_author = FALSE;
+    int which = 1, bl = 0;
+    for (int i=0; i<Str::len(titling_line); i++) {
+        wchar_t c = Str::get_at(titling_line, i);
+        if (c == '(') { bl++; if (which == 2) brackets_in_author = TRUE; }
+        if (c == ')') { bl--; if (bl < 0) brackets_underflowed = TRUE; }
+        if (c == '\"') quote_found = TRUE;
+        if ((bl == 0) && (Str::includes_at(titling_line, i, I" By "))) {
+            if (which == 1) {
+                i += 3;
+                which = 2;
+                continue;
+            }
+        }
+        if (which == 1) PUT_TO(claimed_title, c);
+        else PUT_TO(claimed_author_name, c);
+    }
+    if ((bl != 0) || (brackets_underflowed))
+        Copies::attach_error(C, CopyErrors::new_T(EXT_MISWORDED_CE, -1,
+            I"brackets '(' and ')' are used in an unbalanced way in the titling line"));
+    else if (brackets_in_author)
+        Copies::attach_error(C, CopyErrors::new_T(EXT_MISWORDED_CE, -1,
+            I"brackets '(' and ')' are used as part of the author name in the titling line"));
+    if (quote_found)
+        Copies::attach_error(C, CopyErrors::new_T(EXT_MISWORDED_CE, -1,
+            I"the titling line includes a double-quotation mark"));
+    if (which == 1)
         Copies::attach_error(C, CopyErrors::new_T(EXT_MISWORDED_CE, -1,
             I"the titling line does not give both author and title"));
-    }
 

§2.2.1.3.3. Similarly, extension titles are not allowed to contain parentheses, so diff --git a/inbuild/Figures/help.txt b/inbuild/Figures/help.txt index a42291e99..79b2e166a 100644 --- a/inbuild/Figures/help.txt +++ b/inbuild/Figures/help.txt @@ -22,6 +22,7 @@ usage: inbuild [-TASK] TARGET1 TARGET2 ... -preprocess-html X construct HTML page based on X -preprocess-html-to X set destination for -preprocess-html to be X -rebuild completely rebuild target(s) +-recursive run -contents-of recursively to look through subdirectories too (default is -no-recursive) -no-repair don't quietly fix missing or incorrect extension metadata (default is -repair) -results X write HTML report file to X (for use within Inform GUI apps) -sync-to X forcibly copy target(s) to nest X, even if prior version already there diff --git a/inbuild/supervisor-module/Chapter 2/Copy Errors.w b/inbuild/supervisor-module/Chapter 2/Copy Errors.w index 344031527..36c400bde 100644 --- a/inbuild/supervisor-module/Chapter 2/Copy Errors.w +++ b/inbuild/supervisor-module/Chapter 2/Copy Errors.w @@ -20,6 +20,7 @@ fields are blank. @e EXT_TITLE_TOO_LONG_CE @e EXT_AUTHOR_TOO_LONG_CE @e EXT_RANEOUS_CE +@e PROJECT_MALFORMED_CE @e LANGUAGE_UNAVAILABLE_CE @e LANGUAGE_DEFICIENT_CE @e LEXER_CE /* an error generated by the |words| module */ @@ -135,6 +136,7 @@ void CopyErrors::write(OUTPUT_STREAM, copy_error *CE) { case EXT_BAD_DIRNAME_CE: WRITE("extension directory name wrong: %S", CE->details); break; case EXT_BAD_FILENAME_CE: WRITE("extension filename wrong: %S", CE->details); break; case EXT_RANEOUS_CE: WRITE("extraneous content: %S", CE->details); break; + case PROJECT_MALFORMED_CE: WRITE("project malformed: %S", CE->details); break; case METADATA_MALFORMED_CE: WRITE("%S has incorrect metadata: %S", CE->copy->edition->work->genre->genre_name, CE->details); break; case EXT_TITLE_TOO_LONG_CE: WRITE("title too long: %d characters (max is %d)", diff --git a/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w b/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w index 51b14690d..4b3f1771c 100644 --- a/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w +++ b/inbuild/supervisor-module/Chapter 4/Project Bundle Manager.w @@ -71,12 +71,175 @@ void ProjectBundleManager::claim_as_copy(inbuild_genre *gen, inbuild_copy **C, } inbuild_copy *ProjectBundleManager::claim_folder_as_copy(pathname *P) { - filename *canary = Filenames::in(Pathnames::down(P, I"Source"), I"story.ni"); - if (TextFiles::exists(canary)) - return ProjectBundleManager::new_copy(Pathnames::directory_name(P), P); - return NULL; + if (Directories::exists(P) == FALSE) return NULL; + inbuild_copy *C = ProjectBundleManager::new_copy(Pathnames::directory_name(P), P); + @; + return C; } +@ = + int uuid_found = FALSE, source_found = FALSE; + linked_list *L = Directories::listing(P); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry))) { + TEMPORARY_TEXT(subdir) + WRITE_TO(subdir, "%S", entry); + Str::delete_last_character(subdir); + if (Str::eq_insensitive(subdir, I"Source")) { + @; + } else if (Str::eq_insensitive(subdir, I"Build")) { + @; + } else if (Str::eq_insensitive(subdir, I"Index")) { + @; + } else if (Str::eq_insensitive(subdir, I"Details")) { + @; + } else { + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the project directory '%S' contains a subdirectory called '%S', " + "which I don't recognise", + Pathnames::directory_name(P), subdir); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + DISCARD_TEXT(subdir) + } else { + if (Str::eq_insensitive(entry, I"manifest.plist")) continue; + if (Str::eq_insensitive(entry, I"Metadata.iFiction")) continue; + if (Str::eq_insensitive(entry, I"notes.rtf")) continue; + if (Str::eq_insensitive(entry, I"Release.blurb")) continue; + if (Str::eq_insensitive(entry, I"Settings.plist")) continue; + if (Str::eq_insensitive(entry, I"Skein.skein")) continue; + if (Str::eq_insensitive(entry, I"uuid.txt")) { uuid_found = TRUE; continue; } + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the project directory '%S' contains a file called '%S', " + "which I don't recognise", + Pathnames::directory_name(P), entry); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + } + if (uuid_found == FALSE) { + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the project directory '%S' does not contain a 'uuid.txt' file", + Pathnames::directory_name(P)); + Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + if (source_found == FALSE) { + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the project directory '%S' does not contain a 'story.ni' source file in a " + "'Source' subdirectory", + Pathnames::directory_name(P)); + Copies::attach_error(C, CopyErrors::new_T(METADATA_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + +@ = + pathname *Q = Pathnames::down(P, subdir); + linked_list *L = Directories::listing(Q); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry))) { + TEMPORARY_TEXT(subdir) + WRITE_TO(subdir, "%S", entry); + Str::delete_last_character(subdir); + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the 'Source' subdirectory of the project directory '%S' contains a " + "further subdirectory called '%S', but should not have further subdirectories", + Pathnames::directory_name(P), subdir); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + DISCARD_TEXT(subdir) + } else { + if (Str::eq_insensitive(entry, I"story.ni")) { source_found = TRUE; continue; } + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the 'Source' subdirectory of the project directory '%S' contains a " + "file called '%S', but should only contain the source text file 'story.ni'", + Pathnames::directory_name(P), entry); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + } + +@ = + pathname *Q = Pathnames::down(P, subdir); + linked_list *L = Directories::listing(Q); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry))) { + TEMPORARY_TEXT(subdir) + WRITE_TO(subdir, "%S", entry); + Str::delete_last_character(subdir); + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the 'Build' subdirectory of the project directory '%S' contains a " + "further subdirectory called '%S', but should not have further subdirectories", + Pathnames::directory_name(P), subdir); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + DISCARD_TEXT(subdir) + } + } + +@ = + pathname *Q = Pathnames::down(P, subdir); + linked_list *L = Directories::listing(Q); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry))) { + TEMPORARY_TEXT(subdir) + WRITE_TO(subdir, "%S", entry); + Str::delete_last_character(subdir); + if (Str::eq_insensitive(subdir, I"Details")) { + Q = Pathnames::down(Q, subdir); + @; + } else { + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the 'Index' subdirectory of the project directory '%S' contains a " + "further subdirectory called '%S', but can only have one, 'Details'", + Pathnames::directory_name(P), subdir); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + DISCARD_TEXT(subdir) + } + } + @; + +@ = + linked_list *L = Directories::listing(Q); + text_stream *entry; + LOOP_OVER_LINKED_LIST(entry, text_stream, L) { + if (Platform::is_folder_separator(Str::get_last_char(entry)) == FALSE) { + TEMPORARY_TEXT(ext) + Filenames::write_extension(ext, Filenames::from_text(entry)); + if (Str::eq_insensitive(ext, I".html") == FALSE) { + TEMPORARY_TEXT(error_text) + WRITE_TO(error_text, + "the 'Index' subdirectory of the project directory '%S' contains a " + "file called '%S', but can only contain HTML files", + Pathnames::directory_name(P), entry); + Copies::attach_error(C, CopyErrors::new_T(PROJECT_MALFORMED_CE, -1, error_text)); + DISCARD_TEXT(error_text) + } + } + } + +@ For now, we will allow a subdirectory called Details to exist, because a bug +in intest at one point caused the temporary workspace projects used when testing Inform +to be created with such a subdirectory. + +@ = + ; + @h Searching. Here we look through a nest to find all projects matching the supplied requirements; though in fact... projects are not nesting birds. diff --git a/inform7/Figures/memory-diagnostics.txt b/inform7/Figures/memory-diagnostics.txt index e4148eadb..829c8b369 100644 --- a/inform7/Figures/memory-diagnostics.txt +++ b/inform7/Figures/memory-diagnostics.txt @@ -1,16 +1,16 @@ -Total memory consumption was 123156K = 120 MB +Total memory consumption was 123162K = 120 MB - ---- was used for 2063834 objects, in 367781 frames in 0 x 800K = 0K = 0 MB: + ---- was used for 2063850 objects, in 367797 frames in 0 x 800K = 0K = 0 MB: 33.1% inter_tree_node_array 58 x 8192 = 475136 objects, 41813824 bytes 20.9% text_stream_array 4684 x 100 = 468400 objects, 26380288 bytes - 19.7% linked_list 44520 objects, 24931200 bytes + 19.7% linked_list 44526 objects, 24934560 bytes 11.2% inter_symbol_array 133 x 1024 = 136192 objects, 14168224 bytes 10.7% inter_error_stash_array 103 x 1024 = 105472 objects, 13503712 bytes 8.2% parse_node 130369 objects, 10429520 bytes 6.0% verb_conjugation 164 objects, 7610912 bytes 4.4% parse_node_annotation_array 348 x 500 = 174000 objects, 5579136 bytes - 2.8% scan_directory 866 objects, 3574848 bytes + 2.8% scan_directory 872 objects, 3599616 bytes 2.6% pcalc_prop_array 25 x 1000 = 25000 objects, 3400800 bytes 2.5% inter_name_array 67 x 1000 = 67000 objects, 3218144 bytes 2.1% kind_array 67 x 1000 = 67000 objects, 2682144 bytes @@ -94,14 +94,14 @@ Total memory consumption was 123156K = 120 MB ---- parse_node_tree 33 objects, 28776 bytes ---- action_pattern_array 7 x 100 = 700 objects, 28224 bytes ---- shared_variable_set_array 6 x 100 = 600 objects, 24192 bytes - ---- filename 591 objects, 23640 bytes + ---- filename 590 objects, 23600 bytes ---- property 146 objects, 22192 bytes ---- backdrops_data 672 objects, 21504 bytes ---- inter_node_list 646 objects, 20672 bytes ---- nonlocal_variable 94 objects, 20304 bytes ---- pipeline_step 15 objects, 20280 bytes ---- action_name 90 objects, 20160 bytes - ---- pathname 487 objects, 19480 bytes + ---- pathname 492 objects, 19680 bytes ---- timed_rules_rfd_data 401 objects, 19248 bytes ---- method 390 objects, 18720 bytes ---- pcalc_prop_deferral 86 objects, 17888 bytes @@ -251,16 +251,16 @@ Total memory consumption was 123156K = 120 MB 100.0% was used for memory not allocated for objects: - 57.5% text stream storage 72566844 bytes in 487989 claims + 57.5% text stream storage 72572792 bytes in 488027 claims 4.2% dictionary storage 5319680 bytes in 7630 claims - ---- sorting 2720 bytes in 387 claims + ---- sorting 2752 bytes in 394 claims 5.7% source text 7200000 bytes in 3 claims 8.5% source text details 10800000 bytes in 2 claims 0.2% documentation fragments 262144 bytes in 1 claim ---- linguistic stock array 81920 bytes in 2 claims ---- small word set array 105600 bytes in 22 claims 3.6% inter symbols storage 4558480 bytes in 27989 claims - 13.3% inter bytecode storage 16773540 bytes in 14 claims + 13.2% inter bytecode storage 16773540 bytes in 14 claims 4.9% inter links storage 6222976 bytes in 11 claims 0.1% inter tree location list storage 191232 bytes in 32 claims 1.3% instance-of-kind counting 1705636 bytes in 1 claim @@ -269,5 +269,5 @@ Total memory consumption was 123156K = 120 MB ---- code generation workspace for objects 3480 bytes in 19 claims 0.2% emitter array storage 280544 bytes in 2001 claims --150.0% was overhead - -189255920 bytes = -184820K = -180 MB +-150.0% was overhead - -189284208 bytes = -184847K = -180 MB diff --git a/inform7/Figures/timings-diagnostics.txt b/inform7/Figures/timings-diagnostics.txt index 33f932081..7da8181fb 100644 --- a/inform7/Figures/timings-diagnostics.txt +++ b/inform7/Figures/timings-diagnostics.txt @@ -1,32 +1,29 @@ 100.0% in inform7 run - 70.1% in compilation to Inter - 49.6% in //Sequence::undertake_queued_tasks// - 4.9% in //MajorNodes::pre_pass// + 66.5% in compilation to Inter + 44.8% in //Sequence::undertake_queued_tasks// + 4.6% in //MajorNodes::pre_pass// 3.5% in //MajorNodes::pass_1// 1.7% in //ImperativeDefinitions::assess_all// - 1.5% in //RTKindConstructors::compile// - 1.3% in //RTPhrasebook::compile_entries// - 0.9% in //Sequence::lint_inter// - 0.5% in //ImperativeDefinitions::compile_first_block// - 0.5% in //Sequence::undertake_queued_tasks// - 0.5% in //World::stage_V// + 1.7% in //RTKindConstructors::compile// + 1.4% in //RTPhrasebook::compile_entries// + 1.0% in //Sequence::lint_inter// + 0.7% in //ImperativeDefinitions::compile_first_block// + 0.7% in //Sequence::undertake_queued_tasks// + 0.7% in //Sequence::undertake_queued_tasks// + 0.7% in //World::stage_V// + 0.3% in //CompletionModule::compile// 0.3% in //MajorNodes::pass_2// - 0.3% in //Sequence::undertake_queued_tasks// - 0.1% in //CompletionModule::compile// - 0.1% in //InferenceSubjects::emit_all// - 0.1% in //RTKindConstructors::compile_permissions// - 0.1% in //Task::make_built_in_kind_constructors// - 3.0% not specifically accounted for - 26.2% in running Inter pipeline - 10.0% in step 14/15: generate inform6 -> auto.inf - 5.9% in step 5/15: load-binary-kits - 5.5% in step 6/15: make-synoptic-module - 1.7% in step 9/15: make-identifiers-unique + 3.9% not specifically accounted for + 29.5% in running Inter pipeline + 9.6% in step 5/15: load-binary-kits + 8.5% in step 14/15: generate inform6 -> auto.inf + 5.6% in step 6/15: make-synoptic-module + 2.1% in step 9/15: make-identifiers-unique + 0.3% in step 11/15: eliminate-redundant-labels 0.3% in step 12/15: eliminate-redundant-operations 0.3% in step 4/15: compile-splats 0.3% in step 7/15: shorten-wiring 0.3% in step 8/15: detect-indirect-calls - 0.1% in step 11/15: eliminate-redundant-labels - 1.2% not specifically accounted for - 2.9% in supervisor - 0.6% not specifically accounted for + 1.8% not specifically accounted for + 3.2% in supervisor + 0.8% not specifically accounted for diff --git a/inform7/core-module/Chapter 2/Problems With Source Text.w b/inform7/core-module/Chapter 2/Problems With Source Text.w index 134a77628..adfcd2fe1 100644 --- a/inform7/core-module/Chapter 2/Problems With Source Text.w +++ b/inform7/core-module/Chapter 2/Problems With Source Text.w @@ -62,6 +62,13 @@ void SourceProblems::issue_problems_arising(inbuild_copy *C) { "which I don't recognise (which is not fine). Specifically, %2."); Problems::issue_problem_end(); break; + case PROJECT_MALFORMED_CE: + Problems::quote_stream(1, CE->details); + StandardProblems::handmade_problem(Task::syntax_tree(), _p_(Untestable)); + Problems::issue_problem_segment( + "This project seems to be malformed. Specifically, %1."); + Problems::issue_problem_end(); + break; case METADATA_MALFORMED_CE: if (CE->copy->found_by) { Problems::quote_work(1, CE->copy->found_by->work);