easy_app_helper 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+