diff --git a/assets/project.yml b/assets/project.yml index 6e819df2..caa2fdbd 100644 --- a/assets/project.yml +++ b/assets/project.yml @@ -269,9 +269,6 @@ # :html_encoding: UTF-8 # :module_generator: -# :path_src: source/ -# :path_inc: includes/ -# :path_tst: tests/ # :naming: :snake #options: :bumpy, :camel, :caps, or :snake # :includes: # :tst: [] diff --git a/examples/temp_sensor/project.yml b/examples/temp_sensor/project.yml index ad28806d..50fa52a5 100644 --- a/examples/temp_sensor/project.yml +++ b/examples/temp_sensor/project.yml @@ -221,10 +221,6 @@ # :html_encoding: UTF-8 # :module_generator: -# :project_root: ./ -# :source_root: source/ -# :inc_root: includes/ -# :test_root: tests/ # :naming: :snake #options: :bumpy, :camel, :caps, or :snake # :includes: # :tst: [] diff --git a/lib/ceedling/file_finder_helper.rb b/lib/ceedling/file_finder_helper.rb index 66c4e55f..db6fc665 100644 --- a/lib/ceedling/file_finder_helper.rb +++ b/lib/ceedling/file_finder_helper.rb @@ -49,9 +49,9 @@ def find_file_in_collection(filename, file_list, complain, original_filepath="") return nil end - def find_best_path_in_collection(pathname, path_list, complain) + def find_best_path_in_collection(pathname, path_list, complain, description) # search our collection for the specified exact path - raise "No path list provided for search" if path_list.nil? + raise "No path list provided for #{description} search" if path_list.nil? return pathname if path_list.include?(pathname) # Determine the closest match by looking for matching path segments, especially paths ENDING the same @@ -68,6 +68,20 @@ def find_best_path_in_collection(pathname, path_list, complain) best_match_value = num end end + + # If none of the options were a good match, handle to the best of our ability + if (best_match_value == 0) && (reverse_original_pieces.length > 0) + case (complain) + when :error + raise CeedlingException.new( "Found no path `#{pathname}` in #{description} search paths." ) + when :warn + warning = "Found no path `#{pathname}` in #{description} search paths." + @loginator.log( warning, Verbosity::COMPLAIN ) + when :ignore + # nothing further to do + end + end + return path_list[best_match_index] end @@ -94,7 +108,7 @@ def blow_up(filename, extra_message="") def gripe(filename, extra_message="") warning = ["Found no file `#{filename}` in search paths.", extra_message].join(' ').strip - @loginator.log( warning + extra_message, Verbosity::COMPLAIN ) + @loginator.log( warning, Verbosity::COMPLAIN ) end end diff --git a/lib/ceedling/file_path_collection_utils.rb b/lib/ceedling/file_path_collection_utils.rb index d468ba33..217dfa86 100644 --- a/lib/ceedling/file_path_collection_utils.rb +++ b/lib/ceedling/file_path_collection_utils.rb @@ -39,7 +39,7 @@ def collect_paths(paths) # Expand paths using Ruby's Dir.glob() # - A simple path will yield that path # - A path glob will expand to one or more paths - # Note: `sort()` becuase of Github Issue #860 + # Note: `sort()` because of Github Issue #860 @file_wrapper.directory_listing( _reformed ).sort.each do |entry| # For each result, add it to the working list *only* if it's a directory # Previous validation has already made warnings about filepaths in the list @@ -57,7 +57,7 @@ def collect_paths(paths) # (Containing parent directory still exists) parents << FilePathUtils.no_decorators( _path ) if dirs.empty? - dirs += parents + dirs = parents + dirs end # Based on aggregation modifiers, add entries to plus and minus sets. diff --git a/plugins/module_generator/README.md b/plugins/module_generator/README.md index 3f263a8f..4b154425 100644 --- a/plugins/module_generator/README.md +++ b/plugins/module_generator/README.md @@ -35,6 +35,15 @@ In this example, we'd create 9 files total: 3 headers, 3 source files, and 3 tes files would be named `SecretLairModel`, `SecretLairConductor`, and `SecretLairHardware`. Isn't that nice? +The module generator understands the following patterns: + + - `src` -- generate only a source file + - `test` -- generate only a test file + - `dh` -- generate 6 files for the driver-hardware pattern + - `dih` -- generate 9 files for the driver-interrupt-hardware pattern + - `mch` -- generate 9 files for the model-conductor-hardware pattern + - `mvp` -- generate 9 files for the model-view-presenter pattern + ### Paths The directories found in the project `:paths:` are reused. You can also specify an alternative default generation path using: @@ -97,13 +106,60 @@ You can see that more complicated structures will have files placed in the wrong time... no worries... you can move the file after it's created... but if your project has any kind of consistent structure, the guessing engine does a good job of making it work. -Three more quick notes about the path-matching: +A few more quick notes about the path-matching: 1. You can give multiple ordered hints that map roughly to folder nesting! `lab:secret:lair` will happily match to put `lair.c` in a folder like `my/lab/secret/`. -2. Whenever the matcher fails to find a good candidate (or if it finds multiple equally good - candidates), it will always guess in the order you have the paths listed in your project.yml file +2. If the matcher isn't given any path information or it finds multiple equally good candidates, + it will always guess in the order you have the paths listed in your project.yml file + +3. If the matcher completely fails to find a good candidate, it will return an error, allowing you + to rectify the situation. + +4. What path is "first" when you're using `**` pattern in your `:paths:` specifications? The parent + folder is used first, then its children alphabetically, and so on. This is important when + considering where files (or especially subfolders... see below) will be generated. + +### Adding to Paths + +In addition to the pattern matching above, you can add slashes (`/`) to your filename specification. +When a slash is used instead of a colon (`:`), it will stop using pattern matching and assume the +subdirectory is supposed to be there. If the subdir is NOT there, it will automatically add it. + +Let's try an example with our previous path specification: + +``` +:paths: + :source: + - src/** #this might contain subfolders lab, lair, and other + :include: + - inc/** #again, this might contain subfolders lab, lair, other, and shared + :test: + - test +``` + +Then, use the following command: + +``` +ceedling module:create[newlab/SecretLair] +``` + +This will create the following 3 files: + + - `src/newlab/SecretLair.c` + - `inc/newlab/SecretLair.h` + - `test/newlab/TestSecretLair.c` + +Technically, you can start with a colon and specify part of a path, then use a slash +to specify the remainder of the path to use. In most circumstances, this should work... however +it suffers from the same potential issues as the matcher above, with the added joy of creating +new subdirectories for you without asking. + +It's important to note the module generator doesn't care if these new subdirectories are covered +by the current path specification. This allows you to create the files before updating the +`project.yml` file if desired, but it also means you might accidentally think you've created +a file that Ceedling can see, when it can't. ## Stubbing @@ -130,7 +186,6 @@ follows the default ceedling structure... but what if you have a different struc ``` :module_generator: - :project_root: ./ :naming: :bumpy :includes: - :src: [] diff --git a/plugins/module_generator/Rakefile b/plugins/module_generator/Rakefile index 93dbd911..8d9a3ab0 100644 --- a/plugins/module_generator/Rakefile +++ b/plugins/module_generator/Rakefile @@ -27,7 +27,7 @@ def prep_test end def prep_stub(num) - FileUtils.cp_r("../assets/stubby#{num}.h","./i/stubby.h") + FileUtils.cp_r("../assets/stubby#{num}.h","./i/rev/stubby.h") end def assert_file_exist(path) @@ -81,6 +81,16 @@ def call_create(cmd) end end +def call_create_expecting_error(cmd) + retval = `#{CEEDLING_CLI_EXEC} module:create[#{cmd}] 2>&1` + puts retval # Debug logging + if retval.match? /Error/i + puts "Received expected error when creating #{cmd}" + else + raise "Should have received error when creating #{cmd}" + end +end + def call_destroy(cmd) retval = `#{CEEDLING_CLI_EXEC} module:destroy[#{cmd}] 2>&1` puts retval # Debug logging @@ -101,6 +111,10 @@ def call_stub(cmd) end end +def puts_heading(title) + puts "\n\n#{title}\n#{"="*title.length}" +end + desc "Run integration test on example" task :integration_test do chdir("./example/") do @@ -111,16 +125,16 @@ task :integration_test do # Add a module without path. # It should be added to first path on list of each category - puts "\nVerifying Default Create:" + puts_heading "Verifying Default Create:" call_create("a_file") - assert_file_exist("s/rev/a_file.c") - assert_file_exist("i/rev/a_file.h") + assert_file_exist("s/a_file.c") + assert_file_exist("i/a_file.h") assert_file_exist("sub/t/test_a_file.c") assert_test_run_contains("TESTED: 1") # Make sure that we can add modules properly when the directory # pattern is subdirs with src, inc, and test folders each - puts "\nVerifying Subdirectory Create:" + puts_heading "Verifying Subdirectory Create:" call_create("sub:b_file") assert_file_exist("sub/s/b_file.c") assert_file_exist("sub/i/b_file.h") @@ -129,7 +143,7 @@ task :integration_test do # Make sure that we can add modules properly when the directory # pattern is subdirs under the src, inc, and test folders - puts "\nVerifying Reverse Subdirectory Create:" + puts_heading "Verifying Reverse Subdirectory Create:" call_create("rev:c_file") assert_file_exist("s/rev/c_file.c") assert_file_exist("i/rev/c_file.h") @@ -137,29 +151,29 @@ task :integration_test do assert_test_run_contains("TESTED: 3") # Does our Boilerplate mechanism work? - puts "\nVerifying Boilerplate:" + puts_heading "Verifying Boilerplate:" assert_file_contains("s/rev/c_file.c", "MAY THE SOURCE BE WITH YOU") assert_file_contains("i/rev/c_file.h", "feel included") assert_file_contains("t/rev/test_c_file.c", "Don't Test Me, Sir") # Are other essentials being injected - puts "\nVerifying Guts:" - assert_file_contains("s/rev/a_file.c", "#include \"a_file.h\"") - assert_file_contains("i/rev/a_file.h", "#ifndef A_FILE_H") + puts_heading "Verifying Guts:" + assert_file_contains("s/a_file.c", "#include \"a_file.h\"") + assert_file_contains("i/a_file.h", "#ifndef A_FILE_H") assert_file_contains("sub/t/test_a_file.c", "test_a_file_NeedToImplement") # Destroy a module without path. # It should be removed from first path on list of each category - puts "\nVerifying Default Destroy:" + puts_heading "Verifying Default Destroy:" call_destroy("a_file") - assert_file_not_exist("s/rev/a_file.c") - assert_file_not_exist("i/rev/a_file.h") + assert_file_not_exist("s/a_file.c") + assert_file_not_exist("i/a_file.h") assert_file_not_exist("sub/t/test_a_file.c") assert_test_run_contains("TESTED: 2") # Make sure that we can destroy modules properly when the directory # pattern is subdirs with src, inc, and test folders each - puts "\nVerifying Subdirectory Destroy:" + puts_heading "Verifying Subdirectory Destroy:" call_destroy("sub:b_file") assert_file_not_exist("sub/s/b_file.c") assert_file_not_exist("sub/i/b_file.h") @@ -168,7 +182,7 @@ task :integration_test do # Make sure that we can destroy modules properly when the directory # pattern is subdirs under the src, inc, and test folders - puts "\nVerifying Reverse Subdirectory Destroy:" + puts_heading "Verifying Reverse Subdirectory Destroy:" call_destroy("rev:c_file") assert_file_not_exist("s/rev/c_file.c") assert_file_not_exist("i/rev/c_file.h") @@ -176,28 +190,41 @@ task :integration_test do assert_test_run_contains("No tests executed") # Verify stubbing functionality can make a new source file - puts "\nVerifying Stubbing:" + puts_heading "Verifying Stubbing:" prep_stub(1) - call_stub("i:stubby") + call_stub("rev:stubby") assert_file_contains("s/rev/stubby.c","void shorty") # Verify stubbing functionality can update a source file - puts "\nVerifying Stub Updating:" + puts_heading "Verifying Stub Updating:" prep_stub(2) - call_stub("i:stubby") + call_stub("rev:stubby") assert_file_contains("s/rev/stubby.c","void shorty") assert_file_contains("s/rev/stubby.c","void shrimpy") assert_file_contains("s/rev/stubby.c","int tiny") # Make sure that we can destroy modules properly even when the # entire set doesn't exist - puts "\nVerifying Partial Destroy:" - call_destroy("i:stubby") + puts_heading "Verifying Partial Destroy:" + call_destroy("rev:stubby") assert_file_not_exist("s/rev/stubby.c") assert_file_not_exist("i/rev/stubby.h") - prep_test - puts "\nPASSES MODULE SELF-TESTS" + # Make sure that it complains about path hints that don't match paths + puts_heading "Verifying Matcher Failure Handling:" + call_create_expecting_error("bad:stubby") + assert_file_not_exist("s/bad/stubby.c") + assert_file_not_exist("i/bad/stubby.h") + + # Make sure it allows us to create a subdir if slash used + puts_heading "Verifying Default Create:" + call_create("slash/d_file") + assert_file_exist("s/slash/d_file.c") + assert_file_exist("i/slash/d_file.h") + assert_file_exist("sub/t/slash/test_d_file.c") + + prep_test + puts_heading "PASSES MODULE SELF-TESTS" end end diff --git a/plugins/module_generator/config/module_generator.yml b/plugins/module_generator/config/module_generator.yml index 394a47ba..60755e2f 100644 --- a/plugins/module_generator/config/module_generator.yml +++ b/plugins/module_generator/config/module_generator.yml @@ -6,7 +6,6 @@ # ========================================================================= :module_generator: - :project_root: ./ :naming: :snake #options: :bumpy, :camel, :caps, or :snake :boilerplates: :src: "" diff --git a/plugins/module_generator/example/project.yml b/plugins/module_generator/example/project.yml index 85b8b2b6..595bc5b4 100644 --- a/plugins/module_generator/example/project.yml +++ b/plugins/module_generator/example/project.yml @@ -162,14 +162,13 @@ :release: [] :module_generator: - :project_root: ./ :naming: :snake #options: :bumpy, :camel, :caps, or :snake :boilerplates: :src: "/* MAY THE SOURCE BE WITH YOU */" :inc: | /* ================================== | It's important to make everyone - | feel included, particularly in + | feel included, particularly | when making important decisions. ===================================*/ :tst: "// Don't Test Me, Sir." diff --git a/plugins/module_generator/lib/module_generator.rb b/plugins/module_generator/lib/module_generator.rb index 16fdcf52..1c7b309b 100755 --- a/plugins/module_generator/lib/module_generator.rb +++ b/plugins/module_generator/lib/module_generator.rb @@ -12,7 +12,7 @@ class ModuleGenerator < Plugin - attr_reader :config + attr_reader :config, :interactionizer def create(module_name, optz={}) @@ -110,10 +110,10 @@ def divine_options(optz={}) unity_generator_options[:path_tst] ||= unity_generator_options[:paths_tst][0] else # A path was specified. Do our best to determine which is the best choice based on this information - unity_generator_options[:skeleton_path] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_src], :ignore) || unity_generator_options[:paths_src][0] - unity_generator_options[:path_src] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_src], :ignore) || unity_generator_options[:paths_src][0] - unity_generator_options[:path_inc] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_inc], :ignore) || unity_generator_options[:paths_inc][0] - unity_generator_options[:path_tst] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_tst], :ignore) || unity_generator_options[:paths_tst][0] + unity_generator_options[:path_src] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_src], :error, 'source') || unity_generator_options[:paths_src][0] + unity_generator_options[:path_inc] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_inc], :error, 'include') || unity_generator_options[:paths_inc][0] + unity_generator_options[:path_tst] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_tst], :error, 'test') || unity_generator_options[:paths_tst][0] + unity_generator_options[:skeleton_path] = unity_generator_options[:path_src] end return unity_generator_options