easy_app_helper 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/Rakefile CHANGED
@@ -1 +1,8 @@
1
- require "bundler/gem_tasks"
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
8
+
@@ -1,26 +1,27 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'easy_app_helper/version'
5
-
6
- Gem::Specification.new do |spec|
7
- spec.name = "easy_app_helper"
8
- spec.version = EasyAppHelper::EASY_APP_HELPER_VERSION
9
- spec.authors = ["L.Briais"]
10
- spec.email = ["lbnetid+rb@gmail.com"]
11
- spec.description = %q{Easy Application Helpers framework}
12
- spec.summary = %q{Provides cool helpers to your application, including configuration and logging features}
13
- spec.homepage = "https://github.com/lbriais/easy_app_helper"
14
- spec.license = "MIT"
15
-
16
- spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
- spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
- spec.require_paths = ["lib"]
20
-
21
- spec.add_development_dependency "bundler"
22
- spec.add_development_dependency "rake"
23
- spec.add_development_dependency "pry"
24
-
25
- spec.add_runtime_dependency "slop"
26
- end
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'easy_app_helper/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "easy_app_helper"
8
+ spec.version = EasyAppHelper::EASY_APP_HELPER_VERSION
9
+ spec.authors = ["L.Briais"]
10
+ spec.email = ["lbnetid+rb@gmail.com"]
11
+ spec.description = %q{Easy Application Helpers framework}
12
+ spec.summary = %q{Provides cool helpers to your application, including configuration and logging features}
13
+ spec.homepage = "https://github.com/lbriais/easy_app_helper"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|rspec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "pry"
24
+ spec.add_development_dependency "rspec"
25
+
26
+ spec.add_runtime_dependency "slop"
27
+ end
@@ -1,20 +1,20 @@
1
- ################################################################################
2
- # EasyAppHelper
3
- #
4
- # Copyright (c) 2013 L.Briais under MIT license
5
- # http://opensource.org/licenses/MIT
6
- ################################################################################
7
-
8
- require 'easy_app_helper/version'
9
-
10
- # When this module is included in any class, it mixes in automatically
11
- # EasyAppHelper::ModuleManager methods both into the
12
- # instance and the class of the instance that includes it.
13
- # Thus to have access to the helper methods, the only requirement is to include
14
- # this module...
15
- module EasyAppHelper
16
- require 'easy_app_helper/module_manager'
17
- include ModuleManager
18
- end
19
-
20
-
1
+ ################################################################################
2
+ # EasyAppHelper
3
+ #
4
+ # Copyright (c) 2013 L.Briais under MIT license
5
+ # http://opensource.org/licenses/MIT
6
+ ################################################################################
7
+
8
+ require 'easy_app_helper/version'
9
+
10
+ # When this module is included in any class, it mixes in automatically
11
+ # EasyAppHelper::ModuleManager methods both into the
12
+ # instance and the class of the instance that includes it.
13
+ # Thus to have access to the helper methods, the only requirement is to include
14
+ # this module...
15
+ module EasyAppHelper
16
+ require 'easy_app_helper/module_manager'
17
+ include ModuleManager
18
+ end
19
+
20
+
@@ -1,142 +1,169 @@
1
- ################################################################################
2
- # EasyAppHelper
3
- #
4
- # Copyright (c) 2013 L.Briais under MIT license
5
- # http://opensource.org/licenses/MIT
6
- ################################################################################
7
-
8
- require 'slop'
9
-
10
- # This class is the base class for the {EasyAppHelper::Core::Config config} object.
11
- # It handles the internal_configs hash that actually contains all configurations read from
12
- # various sources: command line, config files etc...
13
-
14
- class EasyAppHelper::Core::Base
15
- CHANGED_BY_CODE = 'Changed by code'
16
-
17
- attr_reader :script_filename, :app_name, :app_version, :app_description, :internal_configs, :logger
18
-
19
- def initialize(logger)
20
- @app_name = @app_version = @app_description = ""
21
- @script_filename = File.basename $0, '.*'
22
- @internal_configs = {modified: {content: {}, source: CHANGED_BY_CODE}}
23
- @logger = logger
24
- @slop_definition = Slop.new
25
- build_command_line_options
26
- end
27
-
28
-
29
- # @return [String] The formatted command line help
30
- def help
31
- @slop_definition.to_s
32
- end
33
-
34
- # sets the filename while maintaining the slop definition upto date
35
- # @param [String] filename
36
- def script_filename=(filename)
37
- @script_filename = filename
38
- @slop_definition.banner = build_banner
39
- end
40
- # sets the application name used for logging while maintaining the slop definition upto date
41
- # @param [String] fname
42
- def app_name=(name)
43
- @app_name = name
44
- @slop_definition.banner = build_banner
45
- end
46
- # sets the version while maintaining the slop definition upto date
47
- # @param [String] version
48
- def app_version=(version)
49
- @app_version = version
50
- @slop_definition.banner = build_banner
51
- end
52
- # sets the filename while maintaining the slop definition upto date
53
- # @param [String] description
54
- def app_description=(description)
55
- @app_description = description
56
- @slop_definition.banner = build_banner
57
- end
58
-
59
- # helper to add in one command any of the four base properties used
60
- # by the logger and the config objects.
61
- # @param [String] app_name
62
- # @param [String] script_filename
63
- # @param [String] app_version
64
- # @param [String] app_description
65
- def describes_application(app_name: nil, script_filename: nil, app_version: nil, app_description: nil)
66
- self.app_name = app_name unless app_name.nil?
67
- self.app_version = app_version unless app_version.nil?
68
- self.app_description = app_description unless app_description.nil?
69
- self.script_filename = script_filename unless script_filename.nil?
70
- end
71
-
72
- # @return [Hash] This hash built from slop definition correspond to the :command_line layer of internal_configs
73
- def command_line_config
74
- @slop_definition.parse
75
- @slop_definition.to_hash
76
- end
77
-
78
- # Yields a slop definition to modify the command line parameters
79
- # @param [String] title used to insert a slop separator
80
- def add_command_line_section(title='Script specific')
81
- raise "Incorrect usage" unless block_given?
82
- @slop_definition.separator build_separator(title)
83
- yield @slop_definition
84
- end
85
-
86
- # Sets the :command_line layer of internal_configs to the computed {#command_line_config}
87
- def load_config
88
- internal_configs[:command_line] = {content: command_line_config, source: 'Command line'}
89
- end
90
-
91
- # Any modification done to the config is in fact stored in the :modified layer of internal_configs
92
- # @param [String] key
93
- # @param [String] value
94
- def []=(key,value)
95
- internal_configs[:modified][:content][key] = value
96
- end
97
-
98
- # Reset the :modified layer of internal_configs rolling back any change done to the config
99
- def reset
100
- internal_configs[:modified] = {content: {}, source: CHANGED_BY_CODE}
101
- end
102
-
103
-
104
- # @return [Array] List of layers
105
- def layers
106
- internal_configs.keys
107
- end
108
-
109
- # Executes code (block given) unless :simulate is in the config.
110
- # If :simulate specified then display message instead of executing the code (block).
111
- def safely_exec(message, *args)
112
- raise "No block given" unless block_given?
113
- if self[:simulate]
114
- logger.puts_and_logs "SIMULATING: #{message}" unless message.nil?
115
- else
116
- logger.puts_and_logs message
117
- yield(*args)
118
- end
119
- end
120
-
121
-
122
- private
123
-
124
- def build_separator(title)
125
- "-- #{title} ".ljust 80, '-'
126
- end
127
-
128
- # Builds common used command line options
129
- def build_command_line_options
130
- add_command_line_section('Generic options') do |slop|
131
- slop.on :auto, 'Auto mode. Bypasses questions to user.', :argument => false
132
- slop.on :simulate, 'Do not perform the actual underlying actions.', :argument => false
133
- slop.on :v, :verbose, 'Enable verbose mode.', :argument => false
134
- slop.on :h, :help, 'Displays this help.', :argument => false
135
- end
136
- end
137
-
138
- def build_banner
139
- "\nUsage: #{script_filename} [options]\n#{app_name} Version: #{app_version}\n\n#{app_description}"
140
- end
141
-
1
+ ################################################################################
2
+ # EasyAppHelper
3
+ #
4
+ # Copyright (c) 2013 L.Briais under MIT license
5
+ # http://opensource.org/licenses/MIT
6
+ ################################################################################
7
+
8
+ require 'slop'
9
+
10
+ # This class is the base class for the {EasyAppHelper::Core::Config config} object.
11
+ # It handles the internal_configs hash that actually contains all configurations read from
12
+ # various sources: command line, config files etc...
13
+
14
+ class EasyAppHelper::Core::Base
15
+ CHANGED_BY_CODE = 'Changed by code'
16
+
17
+ attr_reader :script_filename, :app_name, :app_version, :app_description, :internal_configs, :logger
18
+
19
+ def initialize(logger)
20
+ @app_name = @app_version = @app_description = ""
21
+ @script_filename = File.basename $0, '.*'
22
+ @internal_configs = {modified: {content: {}, source: CHANGED_BY_CODE}}
23
+ @logger = logger
24
+ @slop_definition = Slop.new
25
+ build_command_line_options
26
+ end
27
+
28
+
29
+ # @return [String] The formatted command line help
30
+ def help
31
+ @slop_definition.to_s
32
+ end
33
+
34
+ # sets the filename while maintaining the slop definition upto date
35
+ # @param [String] filename
36
+ def script_filename=(filename)
37
+ @script_filename = filename
38
+ @slop_definition.banner = build_banner
39
+ end
40
+ # sets the application name used for logging while maintaining the slop definition upto date
41
+ # @param [String] fname
42
+ def app_name=(name)
43
+ @app_name = name
44
+ @slop_definition.banner = build_banner
45
+ end
46
+ # sets the version while maintaining the slop definition upto date
47
+ # @param [String] version
48
+ def app_version=(version)
49
+ @app_version = version
50
+ @slop_definition.banner = build_banner
51
+ end
52
+ # sets the filename while maintaining the slop definition upto date
53
+ # @param [String] description
54
+ def app_description=(description)
55
+ @app_description = description
56
+ @slop_definition.banner = build_banner
57
+ end
58
+
59
+ # helper to add in one command any of the four base properties used
60
+ # by the logger and the config objects.
61
+ # @param [String] app_name
62
+ # @param [String] script_filename
63
+ # @param [String] app_version
64
+ # @param [String] app_description
65
+ def describes_application(app_name: nil, script_filename: nil, app_version: nil, app_description: nil)
66
+ self.app_name = app_name unless app_name.nil?
67
+ self.app_version = app_version unless app_version.nil?
68
+ self.app_description = app_description unless app_description.nil?
69
+ self.script_filename = script_filename unless script_filename.nil?
70
+ end
71
+
72
+ # @return [Hash] This hash built from slop definition correspond to the :command_line layer of internal_configs
73
+ def command_line_config
74
+ @slop_definition.parse
75
+ @slop_definition.to_hash
76
+ end
77
+
78
+ # Yields a slop definition to modify the command line parameters
79
+ # @param [String] title used to insert a slop separator
80
+ def add_command_line_section(title='Script specific')
81
+ raise "Incorrect usage" unless block_given?
82
+ @slop_definition.separator build_separator(title)
83
+ yield @slop_definition
84
+ end
85
+
86
+ # Sets the :command_line layer of internal_configs to the computed {#command_line_config}
87
+ def load_config
88
+ internal_configs[:command_line] = {content: command_line_config, source: 'Command line'}
89
+ end
90
+
91
+ # Any modification done to the config is in fact stored in the :modified layer of internal_configs
92
+ # @param [String] key
93
+ # @param [String] value
94
+ def []=(key,value)
95
+ internal_configs[:modified][:content][key] = value unless check_hardcoded_properties key, value
96
+ end
97
+
98
+ # Reset the :modified layer of internal_configs rolling back any change done to the config
99
+ def reset
100
+ internal_configs[:modified] = {content: {}, source: CHANGED_BY_CODE}
101
+ end
102
+
103
+
104
+ # @return [Array] List of layers
105
+ def layers
106
+ internal_configs.keys
107
+ end
108
+
109
+ def find_layer(key)
110
+ [:modified, :command_line].each do |layer|
111
+ return layer if internal_configs[layer][:content][key]
112
+ end
113
+ nil
114
+ end
115
+
116
+
117
+ # Executes code (block given) unless :simulate is in the config.
118
+ # If :simulate specified then display message instead of executing the code (block).
119
+ def safely_exec(message, *args)
120
+ raise "No block given" unless block_given?
121
+ if self[:simulate]
122
+ logger.puts_and_logs "SIMULATING: #{message}" unless message.nil?
123
+ else
124
+ logger.puts_and_logs message
125
+ yield(*args)
126
+ end
127
+ end
128
+
129
+
130
+ private
131
+
132
+ # Performs actions related the very specific config parameters
133
+ # @param [String] key The parameter to check
134
+ # @param [Object] value The value it expects to be set to
135
+ # @param [Symbol] layer Optional layer, default is :modified
136
+ # @return [Boolean] Whether or not the internal state has been changed
137
+ def check_hardcoded_properties(key, value, layer = :modified)
138
+ processed = false
139
+ case key
140
+ when :'log-level'
141
+ logger.send :level=, value, false
142
+ when :'config-file'
143
+ internal_configs[layer][:content][key] = value
144
+ force_reload
145
+ processed = true
146
+ end
147
+ processed
148
+ end
149
+
150
+ # Builds a nice separator
151
+ def build_separator(title, width = 80, filler = '-')
152
+ "#{filler * 2} #{title} ".ljust width, filler
153
+ end
154
+
155
+ # Builds common used command line options
156
+ def build_command_line_options
157
+ add_command_line_section('Generic options') do |slop|
158
+ slop.on :auto, 'Auto mode. Bypasses questions to user.', :argument => false
159
+ slop.on :simulate, 'Do not perform the actual underlying actions.', :argument => false
160
+ slop.on :v, :verbose, 'Enable verbose mode.', :argument => false
161
+ slop.on :h, :help, 'Displays this help.', :argument => false
162
+ end
163
+ end
164
+
165
+ def build_banner
166
+ "\nUsage: #{script_filename} [options]\n#{app_name} Version: #{app_version}\n\n#{app_description}"
167
+ end
168
+
142
169
  end
@@ -1,203 +1,213 @@
1
- ################################################################################
2
- # EasyAppHelper
3
- #
4
- # Copyright (c) 2013 L.Briais under MIT license
5
- # http://opensource.org/licenses/MIT
6
- ################################################################################
7
-
8
- require 'yaml'
9
- # This is the class that will handle the configuration.
10
- # Configuration is read from different sources:
11
- # - config files (system, global, user, specified on the command line)
12
- # - command line options
13
- # - any extra config you provide programmatically
14
- #
15
- # == Config files:
16
- # system, global, and user config files are searched in the file system according to
17
- # complex rules. First the place where to search them depends on the OS
18
- # (Provided by {EasyAppHelper::Core::Config::Places}), and then multiple file extensions are
19
- # tested ({EasyAppHelper::Core::Config::CONFIG_FILE_POSSIBLE_EXTENSIONS}). This is basically
20
- # performed by the private method {#find_file}. The config specified on command line (if any)
21
- # is loaded the same way.
22
- #
23
- # == Command line:
24
- # Any option can be declared as being callable from the command line. Modules add already some
25
- # command line options, but the application can obviously add its own (see
26
- # {EasyAppHelper::Core::Base#add_command_line_section}).
27
- #
28
- # Each of the config sources are kept in a separated "layer" and addressed this way using the
29
- # #internal_configs attribute reader. But of course the config object provides a "merged" config
30
- # result of the computation of all the sources. See the {#to_hash} method to see the order for the
31
- # merge.
32
- # Any option can be accessed or modified directly using the {#[]} and {#[]=} methods.
33
- # Any change to the global config should be done using the {#[]=} method and is kept in the last separated
34
- # layer called "modified". Therefore the config can be easily reset using the {#reset}
35
- # method.
36
- class EasyAppHelper::Core::Config < EasyAppHelper::Core::Base
37
- end
38
-
39
- require 'easy_app_helper/core/places'
40
- require 'easy_app_helper/core/merge_policies'
41
-
42
-
43
- class EasyAppHelper::Core::Config < EasyAppHelper::Core::Base
44
- include EasyAppHelper::Core::HashesMergePolicies
45
- include EasyAppHelper::Core::Config::Places.get_OS_module
46
-
47
- ADMIN_CONFIG_FILENAME = EasyAppHelper.name
48
-
49
-
50
- # Potential extensions a config file can have
51
- CONFIG_FILE_POSSIBLE_EXTENSIONS = %w(conf yml cfg yaml CFG YML YAML Yaml)
52
- ADMIN_CONFIG_FILENAME
53
-
54
- # @param [EasyAppHelper::Core::Logger] logger
55
- # The logger passed to this constructor should be a temporary logger until the full config is loaded.
56
- def initialize(logger)
57
- super
58
- add_cmd_line_options
59
- load_config
60
- end
61
-
62
- # After calling the super method, triggers a forced reload of the file based config.
63
- # @param [String] name of the config file
64
- # @see Base#script_filename=
65
- def script_filename=(name)
66
- super
67
- force_reload
68
- end
69
-
70
- # Sets the Application name and passes it to the logger.
71
- # @param [String] name
72
- # @see Base#app_name=
73
- def app_name=(name)
74
- super
75
- logger.progname = name
76
- end
77
-
78
- # Loads all config (command line and config files)
79
- # Do not reload a file if already loaded unless forced too.
80
- # It *does not flush the "modified" layer*. Use {#reset} instead
81
- # @param [Boolean] force to force the reload
82
- def load_config(force=false)
83
- super()
84
- load_layer_config :system, ADMIN_CONFIG_FILENAME, force
85
- load_layer_config :global, script_filename, force
86
- load_layer_config :user, script_filename, force
87
- load_layer_config :specific_file, internal_configs[:command_line][:content][:'config-file'], force
88
- end
89
-
90
- # @see #load_config
91
- def force_reload
92
- load_config true
93
- end
94
-
95
-
96
- # This is the main method that provides the config as a hash.
97
- #
98
- # Every layer is kept untouched (and could accessed independently
99
- # using {#internal_configs}), while this methods provides a merged config.
100
- # @return [Hash] The hash of the merged config.
101
- def to_hash
102
- merged_config = [:system, :global, :user].inject({}) do |temp_config, config_level|
103
- hashes_second_level_merge temp_config, internal_configs[config_level][:content]
104
- end
105
- if command_line_config[:'config-file']
106
- if command_line_config[:'config-override']
107
- override_merge merged_config, internal_configs[:specific_file][:content]
108
- else
109
- hashes_second_level_merge merged_config, internal_configs[:specific_file][:content]
110
- end
111
-
112
- end
113
- hashes_second_level_merge merged_config, command_line_config
114
- hashes_second_level_merge merged_config, internal_configs[:modified][:content]
115
- end
116
-
117
- # @param [Object] key: The key to access the data in the merged_config hash (see {#to_hash})
118
- # @return [String] Value for this key in the merged config.
119
- def [](key)
120
- self.to_hash[key]
121
- end
122
-
123
-
124
- # @return [String] The merged config (see {#to_hash}) rendered as Yaml
125
- def to_yaml
126
- to_hash.to_yaml
127
- end
128
-
129
- #############################################################################
130
- private
131
-
132
- # Command line options specific to config manipulation
133
- def add_cmd_line_options
134
- add_command_line_section('Configuration options') do |slop|
135
- slop.on 'config-file', 'Specify a config file.', :argument => true
136
- slop.on 'config-override', 'If specified override all other config.', :argument => false
137
- end
138
- end
139
-
140
- # Tries to find a config file to be loaded into the config layer cake unless cached.
141
- def load_layer_config(layer, filename_or_pattern, force=false)
142
- unless_cached(layer, filename_or_pattern, force) do |layer, filename_or_pattern|
143
- fetch_config_layer layer, filename_or_pattern
144
- end
145
- end
146
-
147
- # Actual loads
148
- def fetch_config_layer(layer, filename_or_pattern)
149
- if filename_or_pattern.nil?
150
- internal_configs[layer] = {content: {}}
151
- filename = nil
152
- else
153
- if File.exists? filename_or_pattern and !File.directory? filename_or_pattern
154
- filename = filename_or_pattern
155
- else
156
- filename = find_file POSSIBLE_PLACES[layer], filename_or_pattern
157
- end
158
- internal_configs[layer] = {content: load_config_file(filename), source: filename, origin: filename_or_pattern}
159
- end
160
- ensure
161
- logger.info "No config file found for layer #{layer}." if filename.nil?
162
- end
163
-
164
- def unless_cached(layer, filename_or_pattern, forced)
165
- cached = false
166
- if internal_configs[layer]
167
- cached = true unless internal_configs[layer][:origin] == filename_or_pattern
168
- end
169
- if forced or not cached
170
- yield layer, filename_or_pattern
171
- end
172
- end
173
-
174
- # Tries to find config files according to places (array) given and possible extensions
175
- def find_file(places, filename)
176
- return nil if places.nil? or filename.nil? or filename.empty?
177
- places.each do |dir|
178
- CONFIG_FILE_POSSIBLE_EXTENSIONS.each do |ext|
179
- filename_with_path = dir + '/' + filename + '.' + ext
180
- if File.exists? filename_with_path and !File.directory? filename_with_path
181
- return filename_with_path
182
- else
183
- logger.debug "Trying \"#{filename_with_path}\" as config file."
184
- end
185
- end
186
- end
187
- nil
188
- end
189
-
190
- def load_config_file(conf_filename)
191
- conf = {}
192
- return conf if conf_filename.nil?
193
-
194
- begin
195
- logger.debug "Loading config file \"#{conf_filename}\""
196
- conf = Hash[YAML::load(open(conf_filename)).map { |k, v| [k.to_sym, v] }]
197
- rescue => e
198
- logger.error "Invalid config file \"#{conf_filename}\". Skipped as not respecting YAML syntax!\n#{e.message}"
199
- end
200
- conf
201
- end
202
-
203
- end
1
+ ################################################################################
2
+ # EasyAppHelper
3
+ #
4
+ # Copyright (c) 2013 L.Briais under MIT license
5
+ # http://opensource.org/licenses/MIT
6
+ ################################################################################
7
+
8
+ require 'yaml'
9
+ # This is the class that will handle the configuration.
10
+ # Configuration is read from different sources:
11
+ # - config files (system, global, user, specified on the command line)
12
+ # - command line options
13
+ # - any extra config you provide programmatically
14
+ #
15
+ # == Config files:
16
+ # system, global, and user config files are searched in the file system according to
17
+ # complex rules. First the place where to search them depends on the OS
18
+ # (Provided by {EasyAppHelper::Core::Config::Places}), and then multiple file extensions are
19
+ # tested ({EasyAppHelper::Core::Config::CONFIG_FILE_POSSIBLE_EXTENSIONS}). This is basically
20
+ # performed by the private method {#find_file}. The config specified on command line (if any)
21
+ # is loaded the same way.
22
+ #
23
+ # == Command line:
24
+ # Any option can be declared as being callable from the command line. Modules add already some
25
+ # command line options, but the application can obviously add its own (see
26
+ # {EasyAppHelper::Core::Base#add_command_line_section}).
27
+ #
28
+ # Each of the config sources are kept in a separated "layer" and addressed this way using the
29
+ # #internal_configs attribute reader. But of course the config object provides a "merged" config
30
+ # result of the computation of all the sources. See the {#to_hash} method to see the order for the
31
+ # merge.
32
+ # Any option can be accessed or modified directly using the {#[]} and {#[]=} methods.
33
+ # Any change to the global config should be done using the {#[]=} method and is kept in the last separated
34
+ # layer called "modified". Therefore the config can be easily reset using the {#reset}
35
+ # method.
36
+ class EasyAppHelper::Core::Config < EasyAppHelper::Core::Base
37
+ end
38
+
39
+ require 'easy_app_helper/core/places'
40
+ require 'easy_app_helper/core/merge_policies'
41
+
42
+
43
+ class EasyAppHelper::Core::Config
44
+ include EasyAppHelper::Core::HashesMergePolicies
45
+ include EasyAppHelper::Core::Config::Places.get_OS_module
46
+
47
+ ADMIN_CONFIG_FILENAME = EasyAppHelper.name
48
+
49
+
50
+ # Potential extensions a config file can have
51
+ CONFIG_FILE_POSSIBLE_EXTENSIONS = %w(conf yml cfg yaml CFG YML YAML Yaml)
52
+ ADMIN_CONFIG_FILENAME
53
+
54
+ # @param [EasyAppHelper::Core::Logger] logger
55
+ # The logger passed to this constructor should be a temporary logger until the full config is loaded.
56
+ def initialize(logger)
57
+ super
58
+ add_cmd_line_options
59
+ load_config
60
+ end
61
+
62
+ # After calling the super method, triggers a forced reload of the file based config.
63
+ # @param [String] name of the config file
64
+ # @see Base#script_filename=
65
+ def script_filename=(name)
66
+ super
67
+ force_reload
68
+ end
69
+
70
+ # Sets the Application name and passes it to the logger.
71
+ # @param [String] name
72
+ # @see Base#app_name=
73
+ def app_name=(name)
74
+ super
75
+ logger.progname = name
76
+ end
77
+
78
+ # Loads all config (command line and config files)
79
+ # Do not reload a file if already loaded unless forced too.
80
+ # It *does not flush the "modified" layer*. Use {#reset} instead
81
+ # @param [Boolean] force to force the reload
82
+ def load_config(force=false)
83
+ super()
84
+ load_layer_config :system, ADMIN_CONFIG_FILENAME, force
85
+ load_layer_config :global, script_filename, force
86
+ load_layer_config :user, script_filename, force
87
+ load_layer_config :specific_file, internal_configs[:command_line][:content][:'config-file'], force
88
+ end
89
+
90
+ # @see #load_config
91
+ def force_reload
92
+ load_config true
93
+ end
94
+
95
+
96
+ # This is the main method that provides the config as a hash.
97
+ #
98
+ # Every layer is kept untouched (and could accessed independently
99
+ # using {#internal_configs}), while this methods provides a merged config.
100
+ # @return [Hash] The hash of the merged config.
101
+ def to_hash
102
+ merged_config = [:system, :global, :user].inject({}) do |temp_config, config_level|
103
+ hashes_second_level_merge temp_config, internal_configs[config_level][:content]
104
+ end
105
+ if internal_configs[:command_line][:content][:'config-file']
106
+ if internal_configs[:command_line][:content][:'config-override']
107
+ override_merge merged_config, internal_configs[:specific_file][:content]
108
+ else
109
+ hashes_second_level_merge merged_config, internal_configs[:specific_file][:content]
110
+ end
111
+
112
+ end
113
+ hashes_second_level_merge merged_config, internal_configs[:command_line][:content]
114
+ hashes_second_level_merge merged_config, internal_configs[:modified][:content]
115
+ end
116
+
117
+ # @param [Object] key: The key to access the data in the merged_config hash (see {#to_hash})
118
+ # @return [String] Value for this key in the merged config.
119
+ def [](key = nil)
120
+ key.nil? ? self.to_hash : self.to_hash[key]
121
+ end
122
+
123
+
124
+ # @return [String] The merged config (see {#to_hash}) rendered as Yaml
125
+ def to_yaml
126
+ to_hash.to_yaml
127
+ end
128
+
129
+ def find_layer(key)
130
+ layer = super
131
+ return layer unless layer.nil?
132
+ [:specific_file, :user, :global, :system].each do |layer|
133
+ return layer if internal_configs[layer][:content][key]
134
+ end
135
+ nil
136
+ end
137
+
138
+
139
+ #############################################################################
140
+ private
141
+
142
+ # Command line options specific to config manipulation
143
+ def add_cmd_line_options
144
+ add_command_line_section('Configuration options') do |slop|
145
+ slop.on 'config-file', 'Specify a config file.', :argument => true
146
+ slop.on 'config-override', 'If specified override all other config.', :argument => false
147
+ end
148
+ end
149
+
150
+ # Tries to find a config file to be loaded into the config layer cake unless cached.
151
+ def load_layer_config(layer, filename_or_pattern, force=false)
152
+ unless_cached(layer, filename_or_pattern, force) do |layer, filename_or_pattern|
153
+ fetch_config_layer layer, filename_or_pattern
154
+ end
155
+ end
156
+
157
+ # Actual loads
158
+ def fetch_config_layer(layer, filename_or_pattern)
159
+ if filename_or_pattern.nil?
160
+ internal_configs[layer] = {content: {}}
161
+ filename = nil
162
+ else
163
+ if File.exists? filename_or_pattern and !File.directory? filename_or_pattern
164
+ filename = filename_or_pattern
165
+ else
166
+ filename = find_file POSSIBLE_PLACES[layer], filename_or_pattern
167
+ end
168
+ internal_configs[layer] = {content: load_config_file(filename), source: filename, origin: filename_or_pattern}
169
+ end
170
+ ensure
171
+ logger.info "No config file found for layer #{layer}." if filename.nil?
172
+ end
173
+
174
+ def unless_cached(layer, filename_or_pattern, forced)
175
+ cached = false
176
+ if internal_configs[layer]
177
+ cached = true unless internal_configs[layer][:origin] == filename_or_pattern
178
+ end
179
+ if forced or not cached
180
+ yield layer, filename_or_pattern
181
+ end
182
+ end
183
+
184
+ # Tries to find config files according to places (array) given and possible extensions
185
+ def find_file(places, filename)
186
+ return nil if places.nil? or filename.nil? or filename.empty?
187
+ places.each do |dir|
188
+ CONFIG_FILE_POSSIBLE_EXTENSIONS.each do |ext|
189
+ filename_with_path = dir + '/' + filename + '.' + ext
190
+ if File.exists? filename_with_path and !File.directory? filename_with_path
191
+ return filename_with_path
192
+ else
193
+ logger.debug "Trying \"#{filename_with_path}\" as config file."
194
+ end
195
+ end
196
+ end
197
+ nil
198
+ end
199
+
200
+ def load_config_file(conf_filename)
201
+ conf = {}
202
+ return conf if conf_filename.nil?
203
+
204
+ begin
205
+ logger.debug "Loading config file \"#{conf_filename}\""
206
+ conf = Hash[YAML::load(open(conf_filename)).map { |k, v| [k.to_sym, v] }]
207
+ rescue => e
208
+ logger.error "Invalid config file \"#{conf_filename}\". Skipped as not respecting YAML syntax!\n#{e.message}"
209
+ end
210
+ conf
211
+ end
212
+
213
+ end