From c2fabbe5055150ced1523a29e078c03d0b65dfd7 Mon Sep 17 00:00:00 2001
From: Graham Nelson §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.
§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.
§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:
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
-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
-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) + } ++
§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) + } + } ++
§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) + } + } ++
§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; ++
§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) + } + } + } ++
§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 = +
+ +
+ ;
+
+§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);
+ @