Skip to content

Commit

Permalink
♻️ Centralized logging headers + decorators
Browse files Browse the repository at this point in the history
- Logging statements now add a default set of headers (only for warnings and errors) with options to add predefined headers for any logging needs
- Special header decorators are similarly centrally added to messages (based on decorator status) and also decorators used in strings are removed if decoration disabled
- Restored decorators in CLI messages
- Fixed some CLI silent handling and oopsie newlines in help headings
- Centralized exception reporting in bin/ and lib/ to a single boom_handler() in bin/
- Cleaned up exception handling edge case for `build` CLI task retry
- Cleaned up exception logging to all flow through streaminator properly
- Modified report generation for test results to adapt verbosity to test failure vs. test success case
- Fixed verbosity handling for examples list CLI command
  • Loading branch information
mkarlesky committed Apr 30, 2024
1 parent 893bdb0 commit af0ef22
Show file tree
Hide file tree
Showing 30 changed files with 360 additions and 191 deletions.
58 changes: 48 additions & 10 deletions bin/ceedling
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,37 @@ $LOAD_PATH.unshift( CEEDLING_APPCFG[:ceedling_lib_base_path] )

require 'cli' # Located alongside this file in CEEDLING_BIN
require 'constructor' # Assumed installed via Ceedling gem dependencies
require 'ceedling/constants'

# Single exception handler for multiple booms
def bootloader_boom_handler(loginator, exception)
$stderr.puts( "\n" )
loginator.stream_puts( exception.message, Verbosity::ERRORS, LogLabels::EXCEPTION )
$stderr.puts( exception.backtrace ) if ( defined?( PROJECT_DEBUG ) and PROJECT_DEBUG )
end


# Centralized exception handler for:
# 1. Bootloader (bin/)
# 2. Application (lib/) last resort / outer exception handling
def boom_handler(streaminator, exception)
$stderr.puts( "\n" )

if !streaminator.nil?
streaminator.stream_puts( exception.message, Verbosity::ERRORS, LogLabels::EXCEPTION )
streaminator.stream_puts( "Backtrace ==>", Verbosity::DEBUG )
# Output to console the exception backtrace, formatted like Ruby does it
streaminator.stream_puts( "#{exception.backtrace.first}: #{exception.message} (#{exception.class})", Verbosity::DEBUG )
streaminator.stream_puts( exception.backtrace.drop(1).map{|s| "\t#{s}"}.join("\n"), Verbosity::DEBUG )

# Something went really wrong... logging isn't even up and running yet
else
$stderr.puts( "#{exception.class} ==> #{exception.message}" )
$stderr.puts( "Backtrace ==>" )
$stderr.puts( exception.backtrace )
end
end


# Entry point
begin
Expand All @@ -42,6 +73,7 @@ begin

require 'diy'
bin_objects_filepath = File.join( ceedling_bin_path, 'objects.yml' )
objects = {} # Empty initial hash to be redefined (fingers crossed)
objects = DIY::Context.from_yaml( File.read( bin_objects_filepath ) )
objects.build_everything()

Expand Down Expand Up @@ -86,17 +118,23 @@ rescue Thor::UndefinedCommandError
# and try again by forcing the Thor `build` command at the beginning of the command line.
# This way, our Thor handling will process option flags and properly pass the Rake tasks
# along as well.
CeedlingTasks::CLI.start( _ARGV.unshift( 'build' ),
{
:app_cfg => CEEDLING_APPCFG,
:objects => objects,
}
)

# Bootloader boom handling (ideally this never runs... we failed to build much if we're here)
rescue StandardError => e
$stderr.puts( "\nERROR: #{e.message}" )
$stderr.puts( e.backtrace ) if ( defined?( PROJECT_DEBUG ) and PROJECT_DEBUG )
# Necessary nested exception handling
begin
CeedlingTasks::CLI.start( _ARGV.unshift( 'build' ),
{
:app_cfg => CEEDLING_APPCFG,
:objects => objects,
}
)
rescue StandardError => ex
boom_handler( objects[:streaminator], ex )
exit(1)
end

# Bootloader boom handling
rescue StandardError => ex
boom_handler( objects[:streaminator], ex )
exit(1)
end

8 changes: 7 additions & 1 deletion bin/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,14 +327,20 @@ def environment()
end

desc "examples", "List available example projects"
method_option :debug, :type => :boolean, :default => false, :hide => true
long_desc <<-LONGDESC
`ceedling examples` lists the names of the example projects that come packaged with Ceedling.
The output of this list is most useful when used by the `ceedling example` (no ‘s’) command
to extract an example project to your filesystem.
LONGDESC
def examples()
@handler.list_examples( ENV, @app_cfg )
# Get unfrozen copies so we can add / modify
_options = options.dup()

_options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil

@handler.list_examples( ENV, @app_cfg, _options )
end


Expand Down
60 changes: 39 additions & 21 deletions bin/cli_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,18 @@ def setup()

# Thor application help + Rake help (if available)
def app_help(env, app_cfg, options, command, &thor_help)
@helper.set_verbosity( options[:verbosity] )
verbosity = @helper.set_verbosity( options[:verbosity] )

# If help requested for a command, show it and skip listing build tasks
if !command.nil?
# Block handler
@streaminator.stream_puts( 'Ceedling Application ' )
@streaminator.out( 'Ceedling Application ', Verbosity::NORMAL, LogLabels::TITLE )
thor_help.call( command ) if block_given?
return
end

# Display Thor-generated help listing
@streaminator.stream_puts( 'Ceedling Application ' )
@streaminator.out( 'Ceedling Application ', Verbosity::NORMAL, LogLabels::TITLE )
thor_help.call( command ) if block_given?

# If it was help for a specific command, we're done
Expand All @@ -48,7 +48,14 @@ def app_help(env, app_cfg, options, command, &thor_help)
@path_validator.standardize_paths( options[:project], *options[:mixin], )
return if !@projectinator.config_available?( filepath:options[:project], env:env )

list_rake_tasks( env:env, app_cfg:app_cfg, filepath:options[:project], mixins:options[:mixin] )
list_rake_tasks(
env:env,
app_cfg: app_cfg,
filepath: options[:project],
mixins: options[:mixin],
# Silent Ceedling loading unless debug verbosity
silent: !(verbosity == Verbosity::DEBUG)
)
end


Expand Down Expand Up @@ -108,7 +115,8 @@ def new_project(env, app_cfg, options, name, dest)
@actions._touch_file( File.join( dest, 'test/support', '.gitkeep') )
end

@streaminator.stream_puts( "\nNew project '#{name}' created at #{dest}/\n" )
@streaminator.out( "\n" )
@streaminator.stream_puts( "New project '#{name}' created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE )
end


Expand All @@ -125,8 +133,8 @@ def upgrade_project(env, app_cfg, options, path)

which, _ = @helper.which_ceedling?( env:env, app_cfg:app_cfg )
if (which == :gem)
msg = "NOTICE: Project configuration specifies the Ceedling gem, not vendored Ceedling"
@streaminator.stream_puts( msg, Verbosity::NORMAL )
msg = "Project configuration specifies the Ceedling gem, not vendored Ceedling"
@streaminator.stream_puts( msg, Verbosity::NORMAL, LogLabels::NOTICE )
end

# Thor Actions for project tasks use paths in relation to this path
Expand All @@ -145,7 +153,8 @@ def upgrade_project(env, app_cfg, options, path)
@helper.copy_docs( app_cfg[:ceedling_root_path], path )
end

@streaminator.stream_puts( "\nUpgraded project at #{path}/\n" )
@streaminator.out( "\n" )
@streaminator.stream_puts( "Upgraded project at #{path}/\n", Verbosity::NORMAL, LogLabels::TITLE )
end


Expand Down Expand Up @@ -238,7 +247,9 @@ def dumpconfig(env, app_cfg, options, filepath, sections)
end
ensure
@helper.dump_yaml( config, filepath, sections )
@streaminator.stream_puts( "\nDumped project configuration to #{filepath}\n" )

@streaminator.out( "\n" )
@streaminator.stream_puts( "Dumped project configuration to #{filepath}\n", Verbosity::NORMAL, LogLabels::TITLE )
end
end

Expand Down Expand Up @@ -277,29 +288,33 @@ def environment(env, app_cfg, options)
end
end

output = "\nEnvironment variables:\n"
output = "Environment variables:\n"

env_list.sort.each do |line|
output << " * #{line}\n"
output << " #{line}\n"
end

@streaminator.stream_puts( output + "\n" )
@streaminator.out( "\n" )
@streaminator.stream_puts( output + "\n", Verbosity::NORMAL, LogLabels::TITLE )
end


def list_examples(env, app_cfg)
def list_examples(env, app_cfg, options)
@helper.set_verbosity( options[:verbosity] )

# Process which_ceedling for app_cfg modifications but ignore return values
@helper.which_ceedling?( env:env, app_cfg:app_cfg )

examples = @helper.lookup_example_projects( app_cfg[:ceedling_examples_path] )

raise( "No examples projects found") if examples.empty?

output = "\nAvailable example projects:\n"
output = "Available example projects:\n"

examples.each {|example| output << " * #{example}\n" }
examples.each {|example| output << " #{example}\n" }

@streaminator.stream_puts( output + "\n" )
@streaminator.out( "\n" )
@streaminator.stream_puts( output + "\n", Verbosity::NORMAL, LogLabels::TITLE )
end


Expand Down Expand Up @@ -338,7 +353,8 @@ def create_example(env, app_cfg, options, name, dest)
# Copy in documentation
@helper.copy_docs( app_cfg[:ceedling_root_path], dest ) if options[:docs]

@streaminator.stream_puts( "\nExample project '#{name}' created at #{dest}/\n" )
@streaminator.out( "\n" )
@streaminator.stream_puts( "Example project '#{name}' created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE )
end


Expand All @@ -350,21 +366,22 @@ def version()
Unity => #{Ceedling::Version::UNITY}
CException => #{Ceedling::Version::CEXCEPTION}
VERSION
@streaminator.stream_puts( version )
@streaminator.stream_puts( version, Verbosity::NORMAL, LogLabels::TITLE )
end


### Private ###

private

def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[])
def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[], silent:false)
_, config =
@configinator.loadinate(
builtin_mixins:BUILTIN_MIXINS,
filepath: filepath,
mixins: mixins,
env: env
env: env,
silent: silent
)

# Save reference to loaded configuration
Expand All @@ -378,7 +395,8 @@ def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[])
default_tasks: app_cfg[:default_tasks]
)

@streaminator.stream_puts( "Ceedling Build & Plugin Tasks:\n(Parameterized tasks tend to need enclosing quotes or escape sequences in most shells)" )
msg = "Ceedling Build & Plugin Tasks:\n(Parameterized tasks tend to need enclosing quotes or escape sequences in most shells)"
@streaminator.stream_puts( msg, Verbosity::NORMAL, LogLabels::TITLE )

@helper.print_rake_tasks()
end
Expand Down
4 changes: 2 additions & 2 deletions bin/configinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@ class Configinator

constructor :config_walkinator, :projectinator, :mixinator

def loadinate(builtin_mixins:, filepath:nil, mixins:[], env:{})
def loadinate(builtin_mixins:, filepath:nil, mixins:[], env:{}, silent:false)
# Aliases for clarity
cmdline_filepath = filepath
cmdline_mixins = mixins || []

# Load raw config from command line, environment variable, or default filepath
project_filepath, config = @projectinator.load( filepath:cmdline_filepath, env:env )
project_filepath, config = @projectinator.load( filepath:cmdline_filepath, env:env, silent:silent )

# Extract cfg_enabled_mixins mixins list plus load paths list from config
cfg_enabled_mixins, cfg_load_paths = @projectinator.extract_mixins( config: config )
Expand Down
6 changes: 3 additions & 3 deletions bin/path_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@ def validate(paths:, source:, type: :filepath)
# Error out on empty paths
if path.empty?
validated = false
@streaminator.stream_puts( "ERROR: #{source} contains an empty path", Verbosity::ERRORS )
@streaminator.stream_puts( "#{source} contains an empty path", Verbosity::ERRORS )
next
end

# Error out if path is not a directory / does not exist
if (type == :directory) and !@file_wrapper.directory?( path )
validated = false
@streaminator.stream_puts( "ERROR: #{source} '#{path}' does not exist as a directory in the filesystem", Verbosity::ERRORS )
@streaminator.stream_puts( "#{source} '#{path}' does not exist as a directory in the filesystem", Verbosity::ERRORS )
end

# Error out if filepath does not exist
if (type == :filepath) and !@file_wrapper.exist?( path )
validated = false
@streaminator.stream_puts( "ERROR: #{source} '#{path}' does not exist in the filesystem", Verbosity::ERRORS )
@streaminator.stream_puts( "#{source} '#{path}' does not exist in the filesystem", Verbosity::ERRORS )
end
end

Expand Down
9 changes: 6 additions & 3 deletions bin/projectinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def validate_mixins(mixins:, load_paths:, builtins:, source:, yaml_extension:)
# Validate mixin filepaths
if @path_validator.filepath?( mixin )
if !@file_wrapper.exist?( mixin )
@streaminator.stream_puts( "ERROR: Cannot find mixin at #{mixin}" )
@streaminator.stream_puts( "Cannot find mixin at #{mixin}", Verbosity::ERRORS )
validated = false
end

Expand All @@ -148,7 +148,7 @@ def validate_mixins(mixins:, load_paths:, builtins:, source:, yaml_extension:)
builtins.keys.each {|key| found = true if (mixin == key.to_s)}

if !found
msg = "ERROR: #{source} '#{mixin}' cannot be found in mixin load paths as '#{mixin + yaml_extension}' or among built-in mixins"
msg = "#{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
Expand Down Expand Up @@ -206,7 +206,10 @@ def load_filepath(filepath, method, silent)
config = {} if config.nil?

# Log what the heck we loaded
@streaminator.stream_puts( "Loaded #{'(empty) ' if config.empty?}project configuration #{method} using #{filepath}" ) if !silent
if !silent
msg = "Loaded #{'(empty) ' if config.empty?}project configuration #{method} using #{filepath}"
@streaminator.stream_puts( msg, Verbosity::NORMAL, LogLabels::TITLE )
end

return config
rescue Errno::ENOENT
Expand Down
14 changes: 7 additions & 7 deletions lib/ceedling/config_matchinator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def get_config(primary:, secondary:, tertiary:nil)
return elem if tertiary.nil?

# Otherwise, if an tertiary is specified but we have an array, go boom
error = "ERROR: :#{primary} ↳ :#{secondary} present in project configuration but does not contain :#{tertiary}."
raise CeedlingException.new(error)
error = ":#{primary} ↳ :#{secondary} present in project configuration but does not contain :#{tertiary}."
raise CeedlingException.new( error )

# If [primary][secondary] is a hash
elsif elem.class == Hash
Expand All @@ -81,8 +81,8 @@ def get_config(primary:, secondary:, tertiary:nil)

# If [primary][secondary] is nothing we expect--something other than an array or hash
else
error = "ERROR: :#{primary} ↳ :#{secondary} in project configuration is neither a list nor hash."
raise CeedlingException.new(error)
error = ":#{primary} ↳ :#{secondary} in project configuration is neither a list nor hash."
raise CeedlingException.new( error )
end

return nil
Expand All @@ -93,8 +93,8 @@ def validate_matchers(hash:, section:, context:, operation:nil)
hash.each do |k, v|
if v == nil
path = generate_matcher_path( section, context, operation )
error = "ERROR: Missing list of values for #{path} ↳ '#{k}' matcher in project configuration."
raise CeedlingException.new(error)
error = "Missing list of values for #{path} ↳ '#{k}' matcher in project configuration."
raise CeedlingException.new( error )
end
end
end
Expand All @@ -106,7 +106,7 @@ def matches?(hash:, filepath:, section:, context:, operation:nil)
# Sanity check
if filepath.nil?
path = generate_matcher_path(section, context, operation)
error = "ERROR: #{path}#{matcher} matching provided nil #{filepath}"
error = "#{path}#{matcher} matching provided nil #{filepath}"
raise CeedlingException.new(error)
end

Expand Down
Loading

0 comments on commit af0ef22

Please sign in to comment.