easy_app_helper 1.0.2 → 1.0.3

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.
@@ -1,203 +1,203 @@
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 < 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,103 +1,111 @@
1
- ################################################################################
2
- # EasyAppHelper
3
- #
4
- # Copyright (c) 2013 L.Briais under MIT license
5
- # http://opensource.org/licenses/MIT
6
- ################################################################################
7
-
8
- require 'logger'
9
- require 'singleton'
10
-
11
- # Official Ruby Logger re-opened to introduce a method to hand-over the temporary history from a temporary logger
12
- # to the definitive one.
13
- # TODO: Ensure only the messages that are above the current level are displayed when handing over to the definitive logger.
14
- class Logger
15
- def handing_over_to(log)
16
- history = []
17
- history = @logdev.dev.history if @logdev.dev.respond_to? :history
18
- @logdev.close
19
- @logdev = LogDevice.new log
20
- history.each do |msg|
21
- @logdev.write msg if ENV['DEBUG_EASY_MODULES'] or (msg =~ /^[WE]/)
22
- end
23
- end
24
- end
25
-
26
- # This is the logger that will be used by the application and any class that include {EasyAppHelper} module. It is
27
- # configured by the {EasyAppHelper::Core::Config Config} object, and provides a temporary logger until the config
28
- # is fully loaded.
29
- class EasyAppHelper::Core::Logger < Logger
30
- include Singleton
31
-
32
-
33
- def initialize
34
- @config = {}
35
- super(TempLogger.new)
36
- self.level = Severity::DEBUG
37
- debug "Temporary initialisation logger created..."
38
- end
39
-
40
- # Change the log level while keeping the config in sync.
41
- def level=(level)
42
- super
43
- @config[:'log-level'] = level
44
- end
45
-
46
- # Displays the message according to application verbosity and logs it as info.
47
- def puts_and_logs(msg)
48
- puts msg if @config[:verbose]
49
- info(msg)
50
- end
51
-
52
- # Reset the logger regarding the config provided
53
- def set_app_config(config)
54
- @config = config
55
- add_cmd_line_options
56
- @config.load_config
57
- debug "Config layers:\n#{@config.internal_configs.to_yaml}"
58
- debug "Merged config:\n#{@config.to_yaml}"
59
- if config[:'log-file']
60
- handing_over_to config[:'log-file']
61
- elsif config[:"debug-on-err"]
62
- handing_over_to STDERR
63
- else
64
- handing_over_to STDOUT
65
- end
66
- self.level = config[:'log-level'] ? config[:'log-level'] : Severity::WARN
67
- self
68
- end
69
-
70
- private
71
-
72
-
73
- def add_cmd_line_options
74
- @config.add_command_line_section('Debug and logging options') do |slop|
75
- slop.on :debug, 'Run in debug mode.', :argument => false
76
- slop.on 'debug-on-err', 'Run in debug mode with output to stderr.', :argument => false
77
- slop.on 'log-level', "Log level from 0 to 5, default #{Severity::WARN}.", :argument => true, :as => Integer
78
- slop.on 'log-file', 'File to log to.', :argument => true
79
- end
80
- end
81
-
82
- # This class will act as a temporary logger, actually just keeping the history until the real
83
- # configuration for the logger is known. Then the history is displayed or not regarding the
84
- # definitive logger configuration.
85
- class TempLogger
86
- attr_reader :history
87
-
88
- def initialize
89
- @history = []
90
- end
91
-
92
- def write(data)
93
- @history << data if @history
94
- end
95
-
96
- def close
97
-
98
- end
99
- end
100
-
101
- end
102
-
103
-
1
+ ################################################################################
2
+ # EasyAppHelper
3
+ #
4
+ # Copyright (c) 2013 L.Briais under MIT license
5
+ # http://opensource.org/licenses/MIT
6
+ ################################################################################
7
+
8
+ require 'logger'
9
+ require 'singleton'
10
+
11
+ # Official Ruby Logger re-opened to introduce a method to hand-over the temporary history from a temporary logger
12
+ # to the definitive one.
13
+ # TODO: Ensure only the messages that are above the current level are displayed when handing over to the definitive logger.
14
+ class Logger
15
+ def handing_over_to(log)
16
+ history = []
17
+ history = @logdev.dev.history if @logdev.dev.respond_to? :history
18
+ @logdev.close
19
+ @logdev = LogDevice.new log
20
+ history.each do |msg|
21
+ @logdev.write msg if ENV['DEBUG_EASY_MODULES'] or (msg =~ /^[WE]/)
22
+ end
23
+ end
24
+ end
25
+
26
+ # This is the logger that will be used by the application and any class that include {EasyAppHelper} module. It is
27
+ # configured by the {EasyAppHelper::Core::Config Config} object, and provides a temporary logger until the config
28
+ # is fully loaded.
29
+ class EasyAppHelper::Core::Logger < Logger
30
+ include Singleton
31
+
32
+
33
+ def initialize
34
+ @config = {}
35
+ super(TempLogger.new)
36
+ self.level = Severity::DEBUG
37
+ debug "Temporary initialisation logger created..."
38
+ end
39
+
40
+ # Change the log level while keeping the config in sync.
41
+ def level=(level)
42
+ super
43
+ @config[:'log-level'] = level
44
+ end
45
+
46
+ # Displays the message according to application verbosity and logs it as info.
47
+ def puts_and_logs(msg)
48
+ puts msg if @config[:verbose]
49
+ info(msg)
50
+ end
51
+
52
+ # Reset the logger regarding the config provided
53
+ def set_app_config(config)
54
+ @config = config
55
+ add_cmd_line_options
56
+ @config.load_config
57
+ debug "Config layers:\n#{@config.internal_configs.to_yaml}"
58
+ debug "Merged config:\n#{@config.to_yaml}"
59
+ if config[:debug]
60
+ if config[:'log-file']
61
+ handing_over_to config[:'log-file']
62
+ elsif config[:"debug-on-err"]
63
+ handing_over_to STDERR
64
+ else
65
+ handing_over_to STDOUT
66
+ end
67
+ else
68
+ close
69
+ end
70
+ self.level = config[:'log-level'] ? config[:'log-level'] : Severity::WARN
71
+ self
72
+ end
73
+
74
+ private
75
+
76
+
77
+ def add_cmd_line_options
78
+ @config.add_command_line_section('Debug and logging options') do |slop|
79
+ slop.on :debug, 'Run in debug mode.', :argument => false
80
+ slop.on 'debug-on-err', 'Run in debug mode with output to stderr.', :argument => false
81
+ slop.on 'log-level', "Log level from 0 to 5, default #{Severity::WARN}.", :argument => true, :as => Integer
82
+ slop.on 'log-file', 'File to log to.', :argument => true
83
+ end
84
+ end
85
+
86
+ # This class will act as a temporary logger, actually just keeping the history until the real
87
+ # configuration for the logger is known. Then the history is displayed or not regarding the
88
+ # definitive logger configuration.
89
+ class TempLogger
90
+ attr_reader :history
91
+
92
+ def initialize
93
+ @history = []
94
+ end
95
+
96
+ def write(data)
97
+ return if closed?
98
+ @history << data if @history
99
+ end
100
+
101
+ def close
102
+ @closed = true
103
+ end
104
+
105
+ def opened?() not @closed ; end
106
+ def closed?() @closed ; end
107
+ end
108
+
109
+ end
110
+
111
+