From 55cd3dbe3a1f2ce7c184b16de5974a89916b169c Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Fri, 26 Apr 2024 14:52:44 -0400 Subject: [PATCH 1/2] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Built-in=20mixins=20in?= =?UTF-8?q?=20code,=20not=20internal=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added BUILTIN_MIXINS hash for built-in mixin lookups - Modified mixin validation, loading, and merging to use built-in hash instead of YAML files stored in internal mixins/ directory --- bin/cli.rb | 6 ++--- bin/cli_handler.rb | 8 +++--- bin/configinator.rb | 24 ++++++++--------- bin/mixinator.rb | 35 +++++++++++++++++-------- bin/mixins.rb | 5 ++++ bin/path_validator.rb | 6 +++++ bin/projectinator.rb | 60 ++++++++++++++++++++++++------------------- 7 files changed, 88 insertions(+), 56 deletions(-) create mode 100644 bin/mixins.rb diff --git a/bin/cli.rb b/bin/cli.rb index a5af1c6dc..3295c2ffe 100644 --- a/bin/cli.rb +++ b/bin/cli.rb @@ -96,9 +96,9 @@ module CeedlingTasks LONGDOC_MIXIN_FLAG = "`--mixin` merges the specified configuration mixin. This flag may be repeated for multiple mixins. A simple mixin name initiates a - lookup from within mixin load paths in your project file and internally. A - filepath and/or filename (with extension) will instead merge the specified - YAML file. See documentation for complete details. + lookup from within mixin load paths in your project file and among built-in + mixins. A filepath and/or filename (with extension) will instead merge the + specified YAML file. See documentation for complete details. \x5> --mixin my_compiler --mixin my/path/mixin.yml" class CLI < Thor diff --git a/bin/cli_handler.rb b/bin/cli_handler.rb index c5acff2f7..cda3d6b23 100644 --- a/bin/cli_handler.rb +++ b/bin/cli_handler.rb @@ -5,6 +5,7 @@ # SPDX-License-Identifier: MIT # ========================================================================= +require 'mixins' # Built-in Mixins require 'ceedling/constants' # From Ceedling application class CliHandler @@ -153,7 +154,7 @@ def build(env:, app_cfg:, options:{}, tasks:) @path_validator.standardize_paths( options[:project], options[:logfile], *options[:mixin] ) - _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) default_tasks = @configinator.default_tasks( config:config, default_tasks:app_cfg[:default_tasks] ) @@ -214,7 +215,7 @@ def dumpconfig(env, app_cfg, options, filepath, sections) @path_validator.standardize_paths( filepath, options[:project], *options[:mixin] ) - _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) # Exception handling to ensure we dump the configuration regardless of config validation errors begin @@ -247,7 +248,7 @@ def environment(env, app_cfg, options) @path_validator.standardize_paths( options[:project], *options[:mixin] ) - _, config = @configinator.loadinate( filepath:options[:project], mixins:options[:mixin], env:env ) + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) # Save references app_cfg.set_project_config( config ) @@ -360,6 +361,7 @@ def version() def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[]) _, config = @configinator.loadinate( + builtin_mixins:BUILTIN_MIXINS, filepath: filepath, mixins: mixins, env: env diff --git a/bin/configinator.rb b/bin/configinator.rb index 0386db91c..c0602fe1a 100644 --- a/bin/configinator.rb +++ b/bin/configinator.rb @@ -9,12 +9,9 @@ class Configinator - # TODO: Temporary path until built-in mixins load path handling is replaced with internal hash - MIXINS_BASE_PATH = '.' - constructor :config_walkinator, :projectinator, :mixinator - def loadinate(filepath:nil, mixins:[], env:{}) + def loadinate(builtin_mixins:, filepath:nil, mixins:[], env:{}) # Aliases for clarity cmdline_filepath = filepath cmdline_mixins = mixins || [] @@ -23,10 +20,7 @@ def loadinate(filepath:nil, mixins:[], env:{}) project_filepath, config = @projectinator.load( filepath:cmdline_filepath, env:env ) # Extract cfg_enabled_mixins mixins list plus load paths list from config - cfg_enabled_mixins, cfg_load_paths = @projectinator.extract_mixins( - config: config, - mixins_base_path: MIXINS_BASE_PATH - ) + cfg_enabled_mixins, cfg_load_paths = @projectinator.extract_mixins( config: config ) # Get our YAML file extension yaml_ext = @projectinator.lookup_yaml_extension( config:config ) @@ -44,6 +38,7 @@ def loadinate(filepath:nil, mixins:[], env:{}) if not @projectinator.validate_mixins( mixins: cfg_enabled_mixins, load_paths: cfg_load_paths, + builtins: builtin_mixins, source: 'Config :mixins -> :enabled =>', yaml_extension: yaml_ext ) @@ -54,25 +49,28 @@ def loadinate(filepath:nil, mixins:[], env:{}) if not @projectinator.validate_mixins( mixins: cmdline_mixins, load_paths: cfg_load_paths, + builtins: builtin_mixins, source: 'Mixin', yaml_extension: yaml_ext ) raise 'Command line failed validation' end - # Find mixins from project file among load paths - # Return ordered list of filepaths + # Find mixins in project file among load paths or built-in mixins + # Return ordered list of filepaths or built-in mixin names config_mixins = @projectinator.lookup_mixins( mixins: cfg_enabled_mixins, load_paths: cfg_load_paths, + builtins: builtin_mixins, yaml_extension: yaml_ext ) - # Find mixins from command line among load paths - # Return ordered list of filepaths + # Find mixins from command line among load paths or built-in mixins + # Return ordered list of filepaths or built-in mixin names cmdline_mixins = @projectinator.lookup_mixins( mixins: cmdline_mixins, load_paths: cfg_load_paths, + builtins: builtin_mixins, yaml_extension: yaml_ext ) @@ -90,7 +88,7 @@ def loadinate(filepath:nil, mixins:[], env:{}) ) # Merge mixins - @mixinator.merge( config:config, mixins:mixins_assembled ) + @mixinator.merge( builtins:builtin_mixins, config:config, mixins:mixins_assembled ) return project_filepath, config end diff --git a/bin/mixinator.rb b/bin/mixinator.rb index 8037e575c..5f930da0c 100644 --- a/bin/mixinator.rb +++ b/bin/mixinator.rb @@ -74,27 +74,45 @@ def assemble_mixins(config:, env:, cmdline:) assembly = [] # Build list of hashses to facilitate deduplication - cmdline.each {|filepath| assembly << {'command line' => filepath}} + cmdline.each {|mixin| assembly << {'command line' => mixin}} assembly += env - config.each {|filepath| assembly << {'project configuration' => filepath}} + config.each {|mixin| assembly << {'project configuration' => mixin}} # Remove duplicates inline - # 1. Expand filepaths to absolute paths for correct deduplication + # 1. Expand filepaths to absolute paths for correct deduplication (skip expanding simple mixin names) # 2. Remove duplicates - assembly.uniq! {|entry| File.expand_path( entry.values.first )} + assembly.uniq! do |entry| + # If entry is filepath, expand it, otherwise leave entry untouched (it's a mixin name only) + @path_validator.filepath?( entry ) ? File.expand_path( entry.values.first ) : entry + end # Return the compacted list (in merge order) return assembly end - def merge(config:, mixins:) + def merge(builtins:, config:, mixins:) mixins.each do |mixin| source = mixin.keys.first filepath = mixin.values.first - _mixin = @yaml_wrapper.load( filepath ) + _mixin = {} # Empty initial value + + # Load mixin from filepath if it is a filepath + if @path_validator.filepath?( filepath ) + _mixin = @yaml_wrapper.load( filepath ) + + # Log what filepath we used for this mixin + @streaminator.stream_puts( " + Merging #{'(empty) ' if _mixin.nil?}#{source} mixin using #{filepath}", Verbosity::OBNOXIOUS ) - # Hnadle an empty mixin (it's unlikely but logically coherent) + # Reference mixin from built-in hash-based mixins + else + _mixin = builtins[filepath.to_sym()] + + # Log built-in mixin we used + @streaminator.stream_puts( " + Merging built-in mixin '#{filepath}' from #{source}", Verbosity::OBNOXIOUS ) + end + + # Hnadle an empty mixin (it's unlikely but logically coherent and a good safety check) _mixin = {} if _mixin.nil? # Sanitize the mixin config by removing any :mixins section (these should not end up in merges) @@ -102,9 +120,6 @@ def merge(config:, mixins:) # Merge this bad boy config.deep_merge( _mixin ) - - # Log what filepath we used for this mixin - @streaminator.stream_puts( " + Merged #{'(empty) ' if _mixin.empty?}#{source} mixin using #{filepath}", Verbosity::OBNOXIOUS ) end # Validate final configuration diff --git a/bin/mixins.rb b/bin/mixins.rb new file mode 100644 index 000000000..2c95a228e --- /dev/null +++ b/bin/mixins.rb @@ -0,0 +1,5 @@ + +BUILTIN_MIXINS = { + # Mixin name as symbol => mixin config hash + # :mixin => {} +} diff --git a/bin/path_validator.rb b/bin/path_validator.rb index b0d3029db..d3f534b2b 100644 --- a/bin/path_validator.rb +++ b/bin/path_validator.rb @@ -45,4 +45,10 @@ def standardize_paths( *paths ) end end + + def filepath?(str) + # If argument includes a file extension or a path separator, it's a filepath + return (!File.extname( str ).empty?) || (str.include?( File::SEPARATOR )) + end + end \ No newline at end of file diff --git a/bin/projectinator.rb b/bin/projectinator.rb index 158de24fc..0050e286f 100644 --- a/bin/projectinator.rb +++ b/bin/projectinator.rb @@ -85,22 +85,18 @@ def lookup_yaml_extension(config:) # Pick apart a :mixins projcet configuration section and return components # Layout mirrors :plugins section - def extract_mixins(config:, mixins_base_path:) - # Check if our base path exists - mixins_base_path = nil unless File.directory?(mixins_base_path) - + def extract_mixins(config:) # Get mixins config hash _mixins = config[:mixins] # If no :mixins section, return: # - Empty enabled list - # - Load paths with only the built-in Ceedling mixins/ path - return [], [mixins_base_path].compact if _mixins.nil? + # - Empty load paths + return [], [] if _mixins.nil? # Build list of load paths # Configured load paths are higher in search path ordering load_paths = _mixins[:load_paths] || [] - load_paths += [mixins_base_path].compact # += forces a copy of configuration section # Get list of mixins enabled = _mixins[:enabled] || [] @@ -128,29 +124,32 @@ def validate_mixin_load_paths(load_paths) # Validate mixins list - def validate_mixins(mixins:, load_paths:, source:, yaml_extension:) + def validate_mixins(mixins:, load_paths:, builtins:, source:, yaml_extension:) validated = true mixins.each do |mixin| # Validate mixin filepaths - if !File.extname( mixin ).empty? or mixin.include?( File::SEPARATOR ) + if @path_validator.filepath?( mixin ) if !@file_wrapper.exist?( mixin ) @streaminator.stream_puts( "ERROR: Cannot find mixin at #{mixin}" ) validated = false end - # Otherwise, validate that mixin name can be found among the load paths + # Otherwise, validate that mixin name can be found in load paths or builtins else found = false load_paths.each do |path| - if @file_wrapper.exist?( File.join( path, mixin + yaml_extension) ) + if @file_wrapper.exist?( File.join( path, mixin + yaml_extension ) ) found = true break end end + builtins.keys.each {|key| found = true if (mixin == key.to_s)} + if !found - @streaminator.stream_puts( "ERROR: #{source} '#{mixin}' cannot be found in the mixin load paths as '#{mixin + yaml_extension}'", Verbosity::ERRORS ) + msg = "ERROR: #{source} '#{mixin}' cannot be found in mixin load paths as '#{mixin + yaml_extension}' or among built-in mixins" + @streaminator.stream_puts( msg, Verbosity::ERRORS ) validated = false end end @@ -160,30 +159,37 @@ def validate_mixins(mixins:, load_paths:, source:, yaml_extension:) end - # Yield ordered list of filepaths - def lookup_mixins(mixins:, load_paths:, yaml_extension:) - filepaths = [] + # Yield ordered list of filepaths or built-in mixin names + def lookup_mixins(mixins:, load_paths:, builtins:, yaml_extension:) + _mixins = [] + + # Already validated, so we know: + # 1. Any mixin filepaths exists + # 2. Built-in mixin names exist in the internal hash - # Fill results hash with mixin name => mixin filepath - # Already validated, so we know the mixin filepath exists + # Fill filepaths array with filepaths or builtin names mixins.each do |mixin| # Handle explicit filepaths - if !File.extname( mixin ).empty? or mixin.include?( File::SEPARATOR ) - filepaths << mixin + if !@path_validator.filepath?( mixin ) + _mixins << mixin + next # Success, move on + end # Find name in load_paths (we already know it exists from previous validation) - else - load_paths.each do |path| - filepath = File.join( path, mixin + yaml_extension ) - if @file_wrapper.exist?( filepath ) - filepaths << filepath - break - end + load_paths.each do |path| + filepath = File.join( path, mixin + yaml_extension ) + if @file_wrapper.exist?( filepath ) + _mixins << filepath + next # Success, move on end end + + # Finally, just add the unmodified name to the list + # It's a built-in mixin + _mixins << mixin end - return filepaths + return _mixins end ### Private ### From b088f84e4e5f30f7412ea876a34dbc96140e75b6 Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Fri, 26 Apr 2024 14:53:15 -0400 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=93=9D=20Updated=20Mixins=20documenta?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No longer refers to internal default load path --- docs/CeedlingPacket.md | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index f811773a0..f2978c2b7 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1970,21 +1970,24 @@ Ceedling terminates with an error. ### Base project configuration file `:mixins` section entries Ceedling only recognizes a `:mixins` section in your base project -configuration file. A `:mixins` section in a mixin is ignored. The -`:mixins` section of a base project configuration file is filtered +configuration file. A `:mixins` section in a mixin is ignored. In addition, +the `:mixins` section of a base project configuration file is filtered out of the resulting configuration. -The `:mixins` configuration section contains two subsections. Both -are optional. +The `:mixins` configuration section can contain up to two subsections. +Each subsection is optional. * `:enabled` - An optional array of mixin filenames/filepaths and/or mixin names: + An optional array comprising (A) mixin filenames/filepaths and/or + (B) simple mixin names. 1. A filename contains a file extension. A filepath includes a - leading directory path. The file content is YAML. + directory path. The file content is YAML. 1. A simple name (no file extension and no path) is used - as a lookup in Ceedling's mixin load paths. + as a file lookup among any configured load paths (see next + section) and as a lookup name among Ceedling’s built-in mixins + (currently none). **Default**: `[]` @@ -1996,22 +1999,20 @@ are optional. configuration (`:extension` ↳ `:yaml`) it will be used for file lookups in the mixin load paths instead of _.yml_. - Searches start in the path at the top of the list and end in the - default internal mixin search path. + Searches start in the path at the top of the list. Both mixin names in the `:enabled` list (above) and on the command line via `--mixin` flag use this list of load paths for searches. - **Default**: `[]` (This default is - always present as the last path in the `:load_paths` list) + **Default**: `[]` Example `:mixins` YAML blurb: ```yaml :mixins: :enabled: - - foo # Ceedling looks for foo.yml in proj/mixins & support/ - - path/bar.yaml # Ceedling merges this file with base project conig + - foo # Search for foo.yml in proj/mixins & support/ and 'foo' among built-in mixins + - path/bar.yaml # Merge this file with base project conig :load_paths: - proj/mixins - support @@ -2021,12 +2022,14 @@ Relating the above example to command line `--mixin` flag handling: * A command line flag of `--mixin=foo` is equivalent to the `foo` entry in the `:enabled` mixin configuration. -* A command line flag of `--mixin=path/bay.yaml` is equivalent to the - `path/bay.yaml` entry in the `:enabled` mixin configuration. -* Note that while command line `--mixin` flags work identifically to +* A command line flag of `--mixin=path/bar.yaml` is equivalent to the + `path/bar.yaml` entry in the `:enabled` mixin configuration. +* Note that while command line `--mixin` flags work identically to entries in `:mixins` ↳ `:enabled`, they are merged first instead of last in the mixin precedence. +
+ # The Almighty Ceedling Project Configuration File (in Glorious YAML) ## Some YAML Learnin’