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.
- checksums.yaml +4 -4
- data/.gitignore +48 -47
- data/Gemfile +4 -4
- data/LICENSE.txt +22 -22
- data/README.md +498 -498
- data/Rakefile +8 -1
- data/easy_app_helper.gemspec +27 -26
- data/lib/easy_app_helper.rb +20 -20
- data/lib/easy_app_helper/core/base.rb +168 -141
- data/lib/easy_app_helper/core/config.rb +213 -203
- data/lib/easy_app_helper/core/logger.rb +111 -111
- data/lib/easy_app_helper/core/merge_policies.rb +37 -37
- data/lib/easy_app_helper/core/places.rb +51 -51
- data/lib/easy_app_helper/module_manager.rb +67 -67
- data/lib/easy_app_helper/version.rb +10 -10
- data/spec/config_spec.rb +128 -0
- data/spec/logger_spec.rb +33 -0
- data/test/test.yml +7 -7
- data/test/test2_app.rb +33 -33
- data/test/test3_app.rb +90 -90
- data/test/test4_app.rb +35 -35
- data/test/test_app.rb +55 -55
- metadata +20 -3
data/Rakefile
CHANGED
data/easy_app_helper.gemspec
CHANGED
@@ -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|
|
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
|
-
|
26
|
-
|
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
|
data/lib/easy_app_helper.rb
CHANGED
@@ -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
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
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
|
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
|
106
|
-
if
|
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,
|
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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
end
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
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
|