climatic 0.2.26

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +52 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/climatic.gemspec +28 -0
  13. data/example/simple_app/.gitignore +12 -0
  14. data/example/simple_app/.rspec +2 -0
  15. data/example/simple_app/.ruby-version +1 -0
  16. data/example/simple_app/.travis.yml +5 -0
  17. data/example/simple_app/CODE_OF_CONDUCT.md +74 -0
  18. data/example/simple_app/Gemfile +6 -0
  19. data/example/simple_app/LICENSE.txt +21 -0
  20. data/example/simple_app/README.md +43 -0
  21. data/example/simple_app/Rakefile +6 -0
  22. data/example/simple_app/bin/console +14 -0
  23. data/example/simple_app/bin/setup +8 -0
  24. data/example/simple_app/etc/command_line.yml +52 -0
  25. data/example/simple_app/exe/user +57 -0
  26. data/example/simple_app/lib/tst_climatic/version.rb +3 -0
  27. data/example/simple_app/lib/tst_climatic.rb +5 -0
  28. data/example/simple_app/spec/spec_helper.rb +14 -0
  29. data/example/simple_app/spec/tst_climatic_spec.rb +11 -0
  30. data/example/simple_app/tst_climatic.gemspec +39 -0
  31. data/lib/climatic/config_layers/command_line_layer.rb +46 -0
  32. data/lib/climatic/config_layers/command_line_manager_binder.rb +84 -0
  33. data/lib/climatic/config_layers/env_layer.rb +41 -0
  34. data/lib/climatic/config_layers/executable_gem_layer.rb +45 -0
  35. data/lib/climatic/config_layers/gem_layer.rb +50 -0
  36. data/lib/climatic/config_layers/generic_layer.rb +62 -0
  37. data/lib/climatic/config_layers/global_layer.rb +28 -0
  38. data/lib/climatic/config_layers/program_description_helper.rb +94 -0
  39. data/lib/climatic/config_layers/project_layer.rb +61 -0
  40. data/lib/climatic/config_layers/provided_config_file_layer.rb +26 -0
  41. data/lib/climatic/config_layers/source_helper.rb +48 -0
  42. data/lib/climatic/config_layers/system_layer.rb +37 -0
  43. data/lib/climatic/config_layers/user_layer.rb +40 -0
  44. data/lib/climatic/initializer.rb +87 -0
  45. data/lib/climatic/layers_manager.rb +81 -0
  46. data/lib/climatic/logger/accumulator.rb +49 -0
  47. data/lib/climatic/logger/manager.rb +50 -0
  48. data/lib/climatic/logger/wrapper.rb +12 -0
  49. data/lib/climatic/processes/base.rb +51 -0
  50. data/lib/climatic/processes/command.rb +15 -0
  51. data/lib/climatic/processes/synchronous.rb +51 -0
  52. data/lib/climatic/processes/time_management.rb +21 -0
  53. data/lib/climatic/proxy.rb +27 -0
  54. data/lib/climatic/script/base.rb +65 -0
  55. data/lib/climatic/script/simple.rb +31 -0
  56. data/lib/climatic/script/unimplemented_processor.rb +19 -0
  57. data/lib/climatic/script.rb +4 -0
  58. data/lib/climatic/utils/error.rb +5 -0
  59. data/lib/climatic/utils/input.rb +65 -0
  60. data/lib/climatic/utils/safe_exec.rb +28 -0
  61. data/lib/climatic/utils/script_helper.rb +18 -0
  62. data/lib/climatic/version.rb +3 -0
  63. data/lib/climatic.rb +45 -0
  64. metadata +162 -0
@@ -0,0 +1,87 @@
1
+ module Climatic
2
+
3
+ module Initializer
4
+
5
+ include Climatic::Utils::ScriptHelper
6
+
7
+ attr_reader :config
8
+
9
+ def bootstrap(cmd_line_args: ARGV.dup,
10
+ command_manager: Climatic::ConfigLayers::CommandLineLayer.default_command_line_manager)
11
+ raise Climatic::Error, 'You cannot bootstrap Climatic framework twice !' if climatic_bootstrapped?
12
+ @climatic_status = :bootstrapping
13
+ setup_initial_logger
14
+ Climatic.logger.debug 'Starting Climatic framework setup...'
15
+ # Get the config first to correctly setup the definitive logger
16
+ setup_config_manager cmd_line_args, command_manager
17
+ # Now we can setup the definitive logger
18
+ setup_logger
19
+ Climatic.logger.debug 'Climatic framework setup complete.'
20
+ @climatic_status = :bootstrapped
21
+ rescue Slop::UnknownOption
22
+ Climatic.logger.debug 'Climatic initialization failed (wrong command line options) !'
23
+ end
24
+
25
+ def climatic_bootstrapping?
26
+ @climatic_status == :bootstrapping
27
+ end
28
+
29
+ def climatic_bootstrapped?
30
+ @climatic_status == :bootstrapped
31
+ end
32
+
33
+ private
34
+
35
+ def setup_config_manager(cmd_line_args, command_manager)
36
+ @config = Climatic::LayersManager.new
37
+ config.command_line_layer.command_line_manager = command_manager
38
+ config.command_line_layer.cmd_line_args = cmd_line_args
39
+ end
40
+
41
+ def setup_initial_logger
42
+ @logger = Climatic::Logger::Accumulator.new
43
+ UltraCommandLine.logger = @logger
44
+ end
45
+
46
+ def setup_logger
47
+ if config[:debug]
48
+ log_device = if config[:'log-file']
49
+ if File.exists? config[:'log-file']
50
+ if File.writable? config[:'log-file']
51
+ if config[:'truncate-log-file']
52
+ File.open(config[:'log-file'], 'w') do |log_file|
53
+ log_file.puts "Log truncated on #{Time.now}"
54
+ end
55
+ end
56
+ config[:'log-file']
57
+ else
58
+ STDERR.puts "WARNING: Log file '#{config[:'log-file']}' is not writable. Switching to STDERR..."
59
+ config[:'log-file'] = nil
60
+ STDERR
61
+ end
62
+ else
63
+ if File.writable? File.dirname(config[:'log-file'])
64
+ config[:'log-file']
65
+ else
66
+ STDERR.puts "WARNING: Cannot write log file in '#{File.dirname config[:'log-file']}'. Switching to STDERR..."
67
+ config[:'log-file'] = nil
68
+ STDERR
69
+ end
70
+ end
71
+ elsif config[:'debug-on-stderr']
72
+ STDERR
73
+ else
74
+ STDOUT
75
+ end
76
+
77
+ new_logger = ::Logger.new log_device
78
+ new_logger.level = config[:'log-level'] || Climatic::Logger::Manager::DEFAULT_LOG_LEVEL
79
+ self.logger = new_logger
80
+ else
81
+ self.logger = user_defined_logger
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ end
@@ -0,0 +1,81 @@
1
+ module Climatic
2
+ class LayersManager < SuperStack::Manager
3
+
4
+ include Climatic::ConfigLayers::ProgramDescriptionHelper
5
+
6
+ attr_reader :system_layer, :global_layer, :executable_gem_layer, :user_layer, :env_layer,
7
+ :command_line_layer, :provided_config_file_layer, :project_layer
8
+
9
+ def initialize
10
+ super
11
+ self.merge_policy = SuperStack::MergePolicies::FullMergePolicy
12
+ setup_layers
13
+ end
14
+
15
+
16
+ def self.default_config_file_base_name
17
+ File.basename($PROGRAM_NAME).gsub /\.[^\.]+$/, ''
18
+ end
19
+
20
+ def include_project_layer(file_or_directory, project_file_basename=nil, priority = 65)
21
+ @project_layer = Climatic::ConfigLayers::ProjectLayer.new file_or_directory, project_file_basename
22
+ project_layer.name = 'Project level'
23
+ project_layer.priority = priority
24
+ self << project_layer
25
+ end
26
+
27
+ def include_env_layer(filter = nil, priority = 70)
28
+ @env_layer = Climatic::ConfigLayers::EnvLayer.new filter
29
+ env_layer.name = 'Environment variables level'
30
+ env_layer.priority = priority
31
+ self << env_layer
32
+ end
33
+
34
+ def include_gem_layer_for(gem_name, priority = 30)
35
+ gem_layer = Climatic::ConfigLayers::GemLayer.new
36
+ gem_layer.gem_name = gem_name
37
+ raise "No config found in gem #{gem_name}" if gem_layer.file_name.nil?
38
+ gem_layer.name = "#{gem_name} Gem configuration level"
39
+ gem_layer.priority = priority
40
+ self << gem_layer
41
+ end
42
+
43
+ private
44
+
45
+ def setup_layers
46
+ # The command line level.
47
+ @command_line_layer = setup_layer Climatic::ConfigLayers::CommandLineLayer, 'Command line configuration level', 100
48
+
49
+ # The system level
50
+ @system_layer = setup_layer Climatic::ConfigLayers::SystemLayer, 'System-wide configuration level', 10
51
+
52
+ # The executable gem level
53
+ @executable_gem_layer = setup_layer Climatic::ConfigLayers::ExecutableGemLayer, 'Gem associated to the executable running configuration level', 20
54
+
55
+ # The global level
56
+ @global_layer = setup_layer Climatic::ConfigLayers::GlobalLayer, 'Global configuration level', 40
57
+
58
+ # The user level
59
+ @user_layer = setup_layer Climatic::ConfigLayers::UserLayer, 'User configuration level', 50
60
+
61
+ # The specifically provided config file level
62
+ @provided_config_file_layer = setup_layer Climatic::ConfigLayers::ProvidedConfigFileLayer, 'Specific config file configuration level', 60
63
+
64
+ # The layer to write something
65
+ override_layer = setup_layer SuperStack::Layer, 'Overridden configuration level', 1000
66
+ self.write_layer = override_layer
67
+
68
+ reload_layers
69
+ end
70
+
71
+ def setup_layer(class_type, name, priority)
72
+ layer = class_type.new
73
+ layer.name = name
74
+ layer.priority = priority
75
+ self << layer
76
+ layer
77
+ end
78
+
79
+
80
+ end
81
+ end
@@ -0,0 +1,49 @@
1
+ module Climatic
2
+ module Logger
3
+
4
+ class Accumulator
5
+
6
+ STACK_OPS = %i(debug info warn error fatal unknown).freeze
7
+
8
+ attr_accessor :level
9
+
10
+ def initialize
11
+ @log_lines = [{op: :debug, args: ['Starting special temporary "accumulator" logger...']}]
12
+ end
13
+
14
+ def stack(op, *args)
15
+ log_lines << {op: op, args: args}
16
+ end
17
+
18
+ def transfer_content_to(other_logger)
19
+ debug "Transferring accumulated logs to logger '#{other_logger.inspect}'"
20
+ if other_logger.nil?
21
+ @log_lines = []
22
+ return
23
+ end
24
+ log_lines.each do |log_line|
25
+ other_logger.send log_line[:op], *log_line[:args]
26
+ end
27
+ @log_lines = []
28
+ end
29
+
30
+ def method_missing(method_name, *args)
31
+ if STACK_OPS.include? method_name
32
+ stack method_name, *args
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def respond_to_missing?(method_name, include_private = false)
39
+ STACK_OPS.include?(method_name) || super
40
+ end
41
+
42
+ private
43
+
44
+ attr_reader :log_lines
45
+
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,50 @@
1
+ require 'logger'
2
+
3
+
4
+ module Climatic
5
+ module Logger
6
+
7
+ module Manager
8
+
9
+ attr_reader :logger
10
+
11
+ def puts_and_logs(*args)
12
+ logger.puts_and_logs *args
13
+ end
14
+
15
+ def logger=(new_logger)
16
+
17
+ new_logger ||= DEVNULL_LOGGER
18
+
19
+ unless climatic_bootstrapped? or climatic_bootstrapping?
20
+ @user_defined_logger = new_logger
21
+ return
22
+ end
23
+
24
+ if climatic_bootstrapped?
25
+ new_logger.level = config[:'log-level'].nil? ? Climatic::Logger::Manager::DEFAULT_LOG_LEVEL : config[:'log-level']
26
+ end
27
+
28
+ new_logger.extend Climatic::Logger::Wrapper
29
+
30
+ if self.logger.respond_to? :transfer_content_to
31
+ self.logger.transfer_content_to new_logger
32
+ end
33
+
34
+ UltraCommandLine.logger = new_logger
35
+ @logger = new_logger
36
+ end
37
+
38
+ protected
39
+
40
+ attr_reader :user_defined_logger
41
+
42
+ private
43
+
44
+ DEFAULT_LOG_LEVEL = ::Logger::Severity::WARN
45
+ DEVNULL_LOGGER = UltraCommandLine::Utils::BasicLogger::NullLogger.new
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ module Climatic
2
+ module Logger
3
+
4
+ module Wrapper
5
+ def puts_and_logs(*args)
6
+ puts *args if Climatic.config[:verbose]
7
+ info(*args)
8
+ end
9
+ end
10
+
11
+ end
12
+ end
@@ -0,0 +1,51 @@
1
+ require 'open3'
2
+
3
+ module Climatic
4
+ module Processes
5
+
6
+ class Base
7
+
8
+ include Climatic::Processes::Command
9
+ include Climatic::Processes::TimeManagement
10
+
11
+ attr_reader :process_state, :exit_status, :last_pid, :mode
12
+ attr_accessor :show_output, :log_output
13
+
14
+ def initialize(command = nil, mode = :synchronous)
15
+ self.command = command
16
+ self.process_state = :not_started
17
+ self.mode = mode
18
+ self.creation_time = Time.now
19
+ end
20
+
21
+ def mode=(mode)
22
+ mode_processor = Object.const_get "Climatic::Processes::#{mode.to_s.capitalize}"
23
+ self.extend mode_processor
24
+ @mode = mode.to_sym
25
+ rescue
26
+ raise "Invalid process mode '#{mode}'"
27
+ end
28
+
29
+ private
30
+
31
+ attr_writer :process_state, :exit_status, :last_pid
32
+
33
+ def report(message, to_stdout = true)
34
+ if show_output
35
+ to_stdout ? puts(message) : STDERR.puts(message)
36
+ end
37
+ if log_output
38
+ log_line = "[subprocess #{last_pid}] - #{message}"
39
+ if to_stdout
40
+ Climatic.logger.debug log_line
41
+ else
42
+ Climatic.logger.warn log_line
43
+ end
44
+ end
45
+
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,15 @@
1
+ module Climatic
2
+ module Processes
3
+
4
+ module Command
5
+
6
+ attr_accessor :command
7
+
8
+ def valid?
9
+ File.exists? self.command.split(' ').first
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,51 @@
1
+ module Climatic
2
+ module Processes
3
+
4
+ module Synchronous
5
+
6
+ def execute
7
+ self.exit_status = nil
8
+ self.last_pid = nil
9
+ self.process_state = :running
10
+ self.start_time = Time.now
11
+ Open3.popen3(command) do |stdin, stdout, stderr, wait_thread|
12
+ stdin.close
13
+ self.last_pid = wait_thread.pid
14
+ begin
15
+ monitored_streams = [stdout, stderr]
16
+ loop do
17
+ begin
18
+ readables, writables = IO.select(monitored_streams)
19
+ writables.each(&:close)
20
+ readables.each do |io|
21
+ begin
22
+ buffer = ''
23
+ buffer << io.read_nonblock(1) while buffer[-1] != "\n"
24
+ report buffer, io == stdout
25
+ rescue IO::WaitReadable
26
+ next
27
+ rescue EOFError => e
28
+ monitored_streams.delete io
29
+ monitored_streams.empty? ? raise(e) : next
30
+ end
31
+ end
32
+ rescue EOFError
33
+ report "End of process #{wait_thread.value.pid}"
34
+ break
35
+ end
36
+ end
37
+ rescue Errno::EAGAIN
38
+ retry
39
+ end
40
+ self.exit_status = wait_thread.value
41
+ return self.exit_status
42
+ end
43
+ ensure
44
+ self.end_time = Time.now
45
+ self.process_state = :terminated
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,21 @@
1
+ require 'open3'
2
+
3
+ module Climatic
4
+ module Processes
5
+
6
+ module TimeManagement
7
+
8
+ attr_reader :creation_time, :start_time, :end_time
9
+
10
+ def duration
11
+ end_time - start_time
12
+ end
13
+
14
+ private
15
+
16
+ attr_writer :creation_time, :start_time, :end_time
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Climatic
2
+
3
+ module Proxy
4
+
5
+ def config
6
+ Climatic.config
7
+ end
8
+
9
+ def logger
10
+ Climatic.logger
11
+ end
12
+
13
+ def command_line_manager
14
+ config.command_line_layer.command_line_manager
15
+ end
16
+
17
+ def help
18
+ config.command_line_layer.help
19
+ end
20
+
21
+ def puts_and_logs(*args)
22
+ Climatic.logger.puts_and_logs *args
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,65 @@
1
+ module Climatic
2
+ module Script
3
+
4
+ module Base
5
+
6
+ include Climatic::Utils::ScriptHelper
7
+
8
+ module ClassMethods
9
+
10
+ def start
11
+ script = new
12
+ script.run
13
+ end
14
+
15
+ end
16
+
17
+ def run
18
+ # logging startup configuration
19
+ Climatic.config.command_line_layer.cmd_line_args = ARGV.dup
20
+ Climatic.logger.debug "Config layers ->\n#{Climatic.config.detailed_layers_info}"
21
+ Climatic.logger.debug "Merged config -> #{Climatic.config[].to_yaml}"
22
+ # Displaying (and exiting) command line help
23
+ display_help_and_exit if Climatic.config[:help]
24
+ check_config
25
+ Climatic.logger.info 'Application is starting...'
26
+ do_process
27
+ Climatic.logger.info 'Application ended normally...'
28
+ rescue => e
29
+ display_exit_error e
30
+ exit_code = e.respond_to?(:exit_code) ? e.exit_code : 1
31
+ exit exit_code
32
+ ensure
33
+ Climatic.logger.info 'Exiting...'
34
+ end
35
+
36
+ def self.included(base)
37
+ base.extend ClassMethods
38
+ end
39
+
40
+ private
41
+
42
+ def display_help_and_exit
43
+ puts Climatic.config.command_line_help
44
+ exit 0
45
+ end
46
+
47
+ def do_process
48
+ cmd_line_mngr.processor.execute
49
+ end
50
+
51
+ def cmd_line_mngr
52
+ Climatic.config.command_line_layer.command_line_manager
53
+ end
54
+
55
+ def check_config
56
+ # Check options validity in terms of dependencies
57
+ cmd_line_mngr.command.valid? raise_error: true
58
+ # Delegates to the processor the functional checks of the config
59
+ cmd_line_mngr.processor.check_params cmd_line_mngr.cmd_line_args_for_command(cmd_line_mngr.command)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,31 @@
1
+ module Climatic
2
+
3
+ module Script
4
+
5
+ class Simple
6
+
7
+ include Climatic::Script::UnimplementedProcessor
8
+ include Climatic::Script::Base
9
+
10
+ def initialize
11
+ register_processor
12
+ end
13
+
14
+ def register_processor(commands = cmd_line_mngr.commands, processor = self)
15
+ commands = [commands] unless commands.is_a? Array
16
+ commands.each do |command|
17
+ cmd_line_mngr.register_processor command, processor
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def cmd_line_mngr
24
+ Climatic.config.command_line_layer.command_line_manager
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
31
+
@@ -0,0 +1,19 @@
1
+ module Climatic
2
+
3
+ module Script
4
+
5
+ module UnimplementedProcessor
6
+
7
+ def check_params(command_args)
8
+ Climatic.logger.debug Climatic.config[].inspect
9
+ true
10
+ end
11
+
12
+ def execute
13
+ raise Climatic::Error, 'Not yet implemented !'
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,4 @@
1
+ require 'climatic/script/unimplemented_processor'
2
+ require 'climatic/script/base'
3
+ require 'climatic/script/simple'
4
+
@@ -0,0 +1,5 @@
1
+ module Climatic
2
+
3
+ class Error < StandardError; end
4
+
5
+ end
@@ -0,0 +1,65 @@
1
+ module Climatic
2
+ module Utils
3
+
4
+ module Input
5
+
6
+ DEFAULT_CONFIRMATION_CHOICES = {
7
+ true => %w(Yes y),
8
+ false => %w(No n)
9
+ }
10
+
11
+ def get_user_confirmation(choices: DEFAULT_CONFIRMATION_CHOICES,
12
+ default_choice: 'No',
13
+ prompt: 'Are you sure ?',
14
+ strict: false)
15
+
16
+ raise Climatic::Error, 'Invalid choices !' unless choices.is_a? Hash
17
+ values = choices.values.flatten
18
+ raise Climatic::Error, "Invalid default choice '#{default_choice}' !" unless values.include? default_choice
19
+ if Climatic.config[:auto]
20
+ yield if block_given?
21
+ return true
22
+ end
23
+ full_prompt = '%s (%s): ' % [prompt, choices_string(values, default_choice)]
24
+ STDOUT.print full_prompt
25
+ STDOUT.flush
26
+ input = nil
27
+ until values.include? input
28
+ input = STDIN.gets.chomp
29
+ input = default_choice if input.nil? || input.empty?
30
+ unless strict
31
+ input = default_choice unless values.include? input
32
+ end
33
+ end
34
+ choices.each_pair do |res, possible_choices|
35
+ if possible_choices.include? input
36
+ if res and block_given?
37
+ yield
38
+ end
39
+ return res
40
+ end
41
+ end
42
+ raise 'Something wrong happened !'
43
+ end
44
+
45
+
46
+ def get_user_input(prompt, default=nil)
47
+ full_prompt = (default.nil? or default.empty?) ? "#{prompt}: " : "#{prompt} (default: #{default}): "
48
+ STDOUT.print full_prompt
49
+ STDOUT.flush
50
+ STDIN.gets.chomp
51
+ end
52
+
53
+ private
54
+
55
+
56
+ def choices_string(choices, default_choice, highlight= %w([ ]))
57
+ choices
58
+ .map { |choice| choice == default_choice ? "#{highlight.first}#{choice}#{highlight.last}" : choice }
59
+ .join '/'
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,28 @@
1
+ module Climatic
2
+ module Utils
3
+
4
+ module SafeExec
5
+
6
+ def safely_exec_code(*args, message: nil, &block)
7
+ if self.config[:simulate]
8
+ Climatic.logger.puts_and_logs "[SIMULATION MODE]: #{message}" unless message.nil?
9
+ else
10
+ Climatic.logger.puts_and_logs message
11
+ block.call *args
12
+ end
13
+ end
14
+
15
+ def safely_exec_command(command, message: nil, show_output: false, log_output: true)
16
+ safely_exec_code command, message: message do |cmd|
17
+ process = Climatic::Processes::Base.new cmd
18
+ process.show_output = show_output
19
+ process.log_output = log_output
20
+ process.execute
21
+ process
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Climatic
2
+ module Utils
3
+
4
+ module ScriptHelper
5
+
6
+ def display_exit_error(e)
7
+ puts "Program aborted with message: '#{e.message}'."
8
+ if Climatic.config[:debug]
9
+ Climatic.logger.fatal "#{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}"
10
+ else
11
+ puts ' Use --debug option for more detail (see --help).'
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end