strada 0.0.1
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 +7 -0
- data/.gitignore +2 -0
- data/.rubocop.yml +306 -0
- data/Gemfile +12 -0
- data/README.md +59 -0
- data/Rakefile +47 -0
- data/lib/strada/adapter/json.rb +28 -0
- data/lib/strada/adapter/toml.rb +29 -0
- data/lib/strada/adapter/yaml.rb +29 -0
- data/lib/strada/config_struct.rb +89 -0
- data/lib/strada/version.rb +5 -0
- data/lib/strada.rb +181 -0
- data/strada.gemspec +17 -0
- metadata +55 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d79487b0f3611692c622efcdb8c4eb7114b313f7f65b5683e4a9a772b7d5294f
|
4
|
+
data.tar.gz: 5a3447b412eea2ab0e8db8520aa0c845a994221445e55ee2af7ce45009927a79
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 19b6eeabb5287a7a260dc821e979e5b10669aac42b2423aa4e23e3ad2ee93b33e0e96db98ae559ac9c9a0ad8d6997a498f1750cc47b9d25ae5fd8acc56212256
|
7
|
+
data.tar.gz: 8d4e2b5e207db3bb4676aca93a7d0e48025aba2a61e5d6e63778ef3d99c8bda74e0a9a2fe90beaccbfa58e55c07c403e81a74b3fdbfccd3a876fa3bec3a52e1b
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,306 @@
|
|
1
|
+
require:
|
2
|
+
- rubocop-minitest
|
3
|
+
- rubocop-packaging
|
4
|
+
- rubocop-performance
|
5
|
+
- rubocop-rails
|
6
|
+
|
7
|
+
AllCops:
|
8
|
+
TargetRubyVersion: 2.7
|
9
|
+
# RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
|
10
|
+
# to ignore them, so only the ones explicitly set in this file are enabled.
|
11
|
+
DisabledByDefault: true
|
12
|
+
SuggestExtensions: false
|
13
|
+
Exclude:
|
14
|
+
- '**/tmp/**/*'
|
15
|
+
- '**/templates/**/*'
|
16
|
+
- '**/vendor/**/*'
|
17
|
+
- 'actionpack/lib/action_dispatch/journey/parser.rb'
|
18
|
+
- 'actionmailbox/test/dummy/**/*'
|
19
|
+
- 'actiontext/test/dummy/**/*'
|
20
|
+
- '**/node_modules/**/*'
|
21
|
+
|
22
|
+
Performance:
|
23
|
+
Exclude:
|
24
|
+
- '**/test/**/*'
|
25
|
+
|
26
|
+
# Prefer assert_not over assert !
|
27
|
+
Rails/AssertNot:
|
28
|
+
Include:
|
29
|
+
- '**/test/**/*'
|
30
|
+
|
31
|
+
# Prefer assert_not_x over refute_x
|
32
|
+
Rails/RefuteMethods:
|
33
|
+
Include:
|
34
|
+
- '**/test/**/*'
|
35
|
+
|
36
|
+
Rails/IndexBy:
|
37
|
+
Enabled: true
|
38
|
+
|
39
|
+
Rails/IndexWith:
|
40
|
+
Enabled: true
|
41
|
+
|
42
|
+
# Prefer &&/|| over and/or.
|
43
|
+
Style/AndOr:
|
44
|
+
Enabled: true
|
45
|
+
|
46
|
+
# Align `when` with `case`.
|
47
|
+
Layout/CaseIndentation:
|
48
|
+
Enabled: true
|
49
|
+
|
50
|
+
Layout/ClosingHeredocIndentation:
|
51
|
+
Enabled: true
|
52
|
+
|
53
|
+
Layout/ClosingParenthesisIndentation:
|
54
|
+
Enabled: true
|
55
|
+
|
56
|
+
# Align comments with method definitions.
|
57
|
+
Layout/CommentIndentation:
|
58
|
+
Enabled: true
|
59
|
+
|
60
|
+
Layout/ElseAlignment:
|
61
|
+
Enabled: true
|
62
|
+
|
63
|
+
# Align `end` with the matching keyword or starting expression except for
|
64
|
+
# assignments, where it should be aligned with the LHS.
|
65
|
+
Layout/EndAlignment:
|
66
|
+
Enabled: true
|
67
|
+
EnforcedStyleAlignWith: variable
|
68
|
+
AutoCorrect: true
|
69
|
+
|
70
|
+
Layout/EndOfLine:
|
71
|
+
Enabled: true
|
72
|
+
|
73
|
+
Layout/EmptyLineAfterMagicComment:
|
74
|
+
Enabled: true
|
75
|
+
|
76
|
+
Layout/EmptyLinesAroundAccessModifier:
|
77
|
+
Enabled: true
|
78
|
+
EnforcedStyle: only_before
|
79
|
+
|
80
|
+
Layout/EmptyLinesAroundBlockBody:
|
81
|
+
Enabled: true
|
82
|
+
|
83
|
+
# In a regular class definition, no empty lines around the body.
|
84
|
+
Layout/EmptyLinesAroundClassBody:
|
85
|
+
Enabled: true
|
86
|
+
|
87
|
+
# In a regular method definition, no empty lines around the body.
|
88
|
+
Layout/EmptyLinesAroundMethodBody:
|
89
|
+
Enabled: true
|
90
|
+
|
91
|
+
# In a regular module definition, no empty lines around the body.
|
92
|
+
Layout/EmptyLinesAroundModuleBody:
|
93
|
+
Enabled: true
|
94
|
+
|
95
|
+
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
|
96
|
+
Style/HashSyntax:
|
97
|
+
Enabled: true
|
98
|
+
|
99
|
+
# Method definitions after `private` or `protected` isolated calls need one
|
100
|
+
# extra level of indentation.
|
101
|
+
Layout/IndentationConsistency:
|
102
|
+
Enabled: true
|
103
|
+
EnforcedStyle: indented_internal_methods
|
104
|
+
|
105
|
+
# Two spaces, no tabs (for indentation).
|
106
|
+
Layout/IndentationWidth:
|
107
|
+
Enabled: true
|
108
|
+
|
109
|
+
Layout/LeadingCommentSpace:
|
110
|
+
Enabled: true
|
111
|
+
|
112
|
+
Layout/SpaceAfterColon:
|
113
|
+
Enabled: true
|
114
|
+
|
115
|
+
Layout/SpaceAfterComma:
|
116
|
+
Enabled: true
|
117
|
+
|
118
|
+
Layout/SpaceAfterSemicolon:
|
119
|
+
Enabled: true
|
120
|
+
|
121
|
+
Layout/SpaceAroundEqualsInParameterDefault:
|
122
|
+
Enabled: true
|
123
|
+
|
124
|
+
Layout/SpaceAroundKeyword:
|
125
|
+
Enabled: true
|
126
|
+
|
127
|
+
Layout/SpaceAroundOperators:
|
128
|
+
Enabled: true
|
129
|
+
|
130
|
+
Layout/SpaceBeforeComma:
|
131
|
+
Enabled: true
|
132
|
+
|
133
|
+
Layout/SpaceBeforeComment:
|
134
|
+
Enabled: true
|
135
|
+
|
136
|
+
Layout/SpaceBeforeFirstArg:
|
137
|
+
Enabled: true
|
138
|
+
|
139
|
+
Style/DefWithParentheses:
|
140
|
+
Enabled: true
|
141
|
+
|
142
|
+
# Defining a method with parameters needs parentheses.
|
143
|
+
Style/MethodDefParentheses:
|
144
|
+
Enabled: true
|
145
|
+
|
146
|
+
Style/ExplicitBlockArgument:
|
147
|
+
Enabled: true
|
148
|
+
|
149
|
+
Style/FrozenStringLiteralComment:
|
150
|
+
Enabled: true
|
151
|
+
EnforcedStyle: always
|
152
|
+
Exclude:
|
153
|
+
- 'actionview/test/**/*.builder'
|
154
|
+
- 'actionview/test/**/*.ruby'
|
155
|
+
- 'actionpack/test/**/*.builder'
|
156
|
+
- 'actionpack/test/**/*.ruby'
|
157
|
+
- 'activestorage/db/migrate/**/*.rb'
|
158
|
+
- 'activestorage/db/update_migrate/**/*.rb'
|
159
|
+
- 'actionmailbox/db/migrate/**/*.rb'
|
160
|
+
- 'actiontext/db/migrate/**/*.rb'
|
161
|
+
|
162
|
+
Style/RedundantFreeze:
|
163
|
+
Enabled: true
|
164
|
+
|
165
|
+
# Use `foo {}` not `foo{}`.
|
166
|
+
Layout/SpaceBeforeBlockBraces:
|
167
|
+
Enabled: true
|
168
|
+
|
169
|
+
# Use `foo { bar }` not `foo {bar}`.
|
170
|
+
Layout/SpaceInsideBlockBraces:
|
171
|
+
Enabled: true
|
172
|
+
EnforcedStyleForEmptyBraces: space
|
173
|
+
|
174
|
+
# Use `{ a: 1 }` not `{a:1}`.
|
175
|
+
Layout/SpaceInsideHashLiteralBraces:
|
176
|
+
Enabled: true
|
177
|
+
|
178
|
+
Layout/SpaceInsideParens:
|
179
|
+
Enabled: true
|
180
|
+
|
181
|
+
# Check quotes usage according to lint rule below.
|
182
|
+
Style/StringLiterals:
|
183
|
+
Enabled: true
|
184
|
+
EnforcedStyle: double_quotes
|
185
|
+
|
186
|
+
# Detect hard tabs, no hard tabs.
|
187
|
+
Layout/IndentationStyle:
|
188
|
+
Enabled: true
|
189
|
+
|
190
|
+
# Empty lines should not have any spaces.
|
191
|
+
Layout/TrailingEmptyLines:
|
192
|
+
Enabled: true
|
193
|
+
|
194
|
+
# No trailing whitespace.
|
195
|
+
Layout/TrailingWhitespace:
|
196
|
+
Enabled: true
|
197
|
+
|
198
|
+
# Use quotes for string literals when they are enough.
|
199
|
+
Style/RedundantPercentQ:
|
200
|
+
Enabled: true
|
201
|
+
|
202
|
+
Lint/AmbiguousOperator:
|
203
|
+
Enabled: true
|
204
|
+
|
205
|
+
Lint/AmbiguousRegexpLiteral:
|
206
|
+
Enabled: true
|
207
|
+
|
208
|
+
Lint/DuplicateRequire:
|
209
|
+
Enabled: true
|
210
|
+
|
211
|
+
Lint/DuplicateMethods:
|
212
|
+
Enabled: true
|
213
|
+
|
214
|
+
Lint/ErbNewArguments:
|
215
|
+
Enabled: true
|
216
|
+
|
217
|
+
Lint/EnsureReturn:
|
218
|
+
Enabled: true
|
219
|
+
|
220
|
+
# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
|
221
|
+
Lint/RequireParentheses:
|
222
|
+
Enabled: true
|
223
|
+
|
224
|
+
Lint/RedundantStringCoercion:
|
225
|
+
Enabled: true
|
226
|
+
|
227
|
+
Lint/UriEscapeUnescape:
|
228
|
+
Enabled: true
|
229
|
+
|
230
|
+
Lint/UselessAssignment:
|
231
|
+
Enabled: true
|
232
|
+
|
233
|
+
Lint/DeprecatedClassMethods:
|
234
|
+
Enabled: true
|
235
|
+
|
236
|
+
Style/ParenthesesAroundCondition:
|
237
|
+
Enabled: true
|
238
|
+
|
239
|
+
Style/HashTransformKeys:
|
240
|
+
Enabled: true
|
241
|
+
|
242
|
+
Style/HashTransformValues:
|
243
|
+
Enabled: true
|
244
|
+
|
245
|
+
Style/RedundantBegin:
|
246
|
+
Enabled: true
|
247
|
+
|
248
|
+
Style/RedundantReturn:
|
249
|
+
Enabled: true
|
250
|
+
AllowMultipleReturnValues: true
|
251
|
+
|
252
|
+
Style/RedundantRegexpEscape:
|
253
|
+
Enabled: true
|
254
|
+
|
255
|
+
Style/Semicolon:
|
256
|
+
Enabled: true
|
257
|
+
AllowAsExpressionSeparator: true
|
258
|
+
|
259
|
+
# Prefer Foo.method over Foo::method
|
260
|
+
Style/ColonMethodCall:
|
261
|
+
Enabled: true
|
262
|
+
|
263
|
+
Style/TrivialAccessors:
|
264
|
+
Enabled: true
|
265
|
+
|
266
|
+
Performance/BindCall:
|
267
|
+
Enabled: true
|
268
|
+
|
269
|
+
Performance/FlatMap:
|
270
|
+
Enabled: true
|
271
|
+
|
272
|
+
Performance/MapCompact:
|
273
|
+
Enabled: true
|
274
|
+
|
275
|
+
Performance/SelectMap:
|
276
|
+
Enabled: true
|
277
|
+
|
278
|
+
Performance/RedundantMerge:
|
279
|
+
Enabled: true
|
280
|
+
|
281
|
+
Performance/StartWith:
|
282
|
+
Enabled: true
|
283
|
+
|
284
|
+
Performance/EndWith:
|
285
|
+
Enabled: true
|
286
|
+
|
287
|
+
Performance/RegexpMatch:
|
288
|
+
Enabled: true
|
289
|
+
|
290
|
+
Performance/ReverseEach:
|
291
|
+
Enabled: true
|
292
|
+
|
293
|
+
Performance/StringReplacement:
|
294
|
+
Enabled: true
|
295
|
+
|
296
|
+
Performance/UnfreezeString:
|
297
|
+
Enabled: true
|
298
|
+
|
299
|
+
Performance/DeletePrefix:
|
300
|
+
Enabled: true
|
301
|
+
|
302
|
+
Performance/DeleteSuffix:
|
303
|
+
Enabled: true
|
304
|
+
|
305
|
+
Minitest/UnreachableAssertion:
|
306
|
+
Enabled: true
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
gemspec
|
3
|
+
|
4
|
+
group :rubocop do
|
5
|
+
gem "rubocop", require: false
|
6
|
+
gem "rubocop-minitest", require: false
|
7
|
+
gem "rubocop-packaging", require: false
|
8
|
+
gem "rubocop-performance", require: false
|
9
|
+
gem "rubocop-rails", require: false
|
10
|
+
end
|
11
|
+
|
12
|
+
gem "rake"
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Strada
|
2
|
+
Configuration library for ruby with YAML/JSON/TOML backends with unified object
|
3
|
+
access
|
4
|
+
|
5
|
+
## Install
|
6
|
+
```
|
7
|
+
% gem install Strada
|
8
|
+
```
|
9
|
+
|
10
|
+
## Use
|
11
|
+
### Simple
|
12
|
+
```
|
13
|
+
require 'Strada'
|
14
|
+
cfg = Strada.cfg
|
15
|
+
port = cfg.server.port
|
16
|
+
user = cfg.auth.user
|
17
|
+
pw = cfg.auth.password
|
18
|
+
```
|
19
|
+
It tried to detect your software name via caller_locations if no ':name'
|
20
|
+
argument was given.
|
21
|
+
It automatically loads /etc/name/config and ~/.config/name/config and merges
|
22
|
+
them together.
|
23
|
+
|
24
|
+
### Advanced
|
25
|
+
```
|
26
|
+
require 'Strada'
|
27
|
+
Strada = Strada.new name: 'mykewlapp',
|
28
|
+
default: {'poop'=>'xyzzy'},
|
29
|
+
adapter: 'yaml',
|
30
|
+
usrdir: '/home/app/config/',
|
31
|
+
sysdir: '/System/config/',
|
32
|
+
load: false
|
33
|
+
Strada.default.poop2 = [1, 2, 3, 4]
|
34
|
+
Strada.default.starship.poopers = 42
|
35
|
+
Strada.load :user
|
36
|
+
if Strada.user.empty?
|
37
|
+
Strada.user = Strada.default
|
38
|
+
Strada.save :user
|
39
|
+
end
|
40
|
+
Strada.load # load+merges cfg, takes argument :default, :system, :user
|
41
|
+
Strada.cfg # merged default + system + user (merged on load)
|
42
|
+
Strada.default # default only
|
43
|
+
Strada.system # system only
|
44
|
+
Strada.user # user only
|
45
|
+
```
|
46
|
+
|
47
|
+
## Reserved methods
|
48
|
+
|
49
|
+
* each - iterate all config keys in current level
|
50
|
+
* has_key?(arg) - check if current level has key arg
|
51
|
+
* [arg] - fetch arg (useful for non-literal retrieval, instead of using #send)
|
52
|
+
* key? - all keys have question mark version reserved, checks if key exists+true (true), exists+false (false), not-exists (nil)
|
53
|
+
+ all object class methods
|
54
|
+
|
55
|
+
## TODO
|
56
|
+
|
57
|
+
* should I add feature to raise on unconfigured/unset?
|
58
|
+
* should I always merge to 'cfg' when default/system/config is set?
|
59
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler'
|
4
|
+
#Bundler.setup
|
5
|
+
rescue LoadError
|
6
|
+
warn 'bunler missing'
|
7
|
+
exit 42
|
8
|
+
end
|
9
|
+
|
10
|
+
gemspec = eval(File.read(Dir['*.gemspec'].first))
|
11
|
+
file = [gemspec.name, gemspec.version].join('-') + '.gem'
|
12
|
+
|
13
|
+
desc 'Validate gemspec'
|
14
|
+
task :gemspec do
|
15
|
+
gemspec.validate
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Run minitest'
|
19
|
+
task :test do
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs.push "lib"
|
22
|
+
t.test_files = FileList['spec/*_spec.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Build gem'
|
28
|
+
task :build do
|
29
|
+
system "gem build #{gemspec.name}.gemspec"
|
30
|
+
FileUtils.mkdir_p 'gems'
|
31
|
+
FileUtils.mv file, 'gems'
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Install gem'
|
35
|
+
task :install => :build do
|
36
|
+
system "sudo -E sh -c \'umask 022; gem install gems/#{file}\'"
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'Remove gems'
|
40
|
+
task :clean do
|
41
|
+
FileUtils.rm_rf 'gems'
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Push to rubygems'
|
45
|
+
task :push do
|
46
|
+
system "gem push gems/#{file}"
|
47
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Strada
|
4
|
+
def to_json(config)
|
5
|
+
Adapter::JSON.to config._config_to_hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_json(json)
|
9
|
+
Adapter::JSON.from json
|
10
|
+
end
|
11
|
+
|
12
|
+
class Adapter
|
13
|
+
class JSON
|
14
|
+
# 定义类方法
|
15
|
+
class << self
|
16
|
+
require "json"
|
17
|
+
# 将 RUBY(HASH) 数据结构转换为 JSON
|
18
|
+
def to(hash)
|
19
|
+
::JSON.pretty_generate hash
|
20
|
+
end
|
21
|
+
# 将 JSON 转换为 RUBY 数据结构
|
22
|
+
def from(json)
|
23
|
+
::JSON.load json
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Strada
|
4
|
+
def to_toml(config)
|
5
|
+
Adapter::TOML.to config._config_to_hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_toml(toml)
|
9
|
+
Adapter::TOML.from toml
|
10
|
+
end
|
11
|
+
|
12
|
+
class Adapter
|
13
|
+
class TOML
|
14
|
+
# 定义类方法
|
15
|
+
class << self
|
16
|
+
require "toml"
|
17
|
+
|
18
|
+
# 将 RUBY(HASH) 数据结构转换为 TOML
|
19
|
+
def to(hash)
|
20
|
+
::TOML::Generator.new(hash).body
|
21
|
+
end
|
22
|
+
# 将 TOML 转换为 RUBY 数据结构
|
23
|
+
def from(toml)
|
24
|
+
::TOML.load toml
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Strada
|
4
|
+
def to_yaml(config)
|
5
|
+
Adapter::YAML.to config._config_to_hash
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_yaml(yaml)
|
9
|
+
Adapter::YAML.from yaml
|
10
|
+
end
|
11
|
+
|
12
|
+
class Adapter
|
13
|
+
class YAML
|
14
|
+
# 定义类方法
|
15
|
+
class << self
|
16
|
+
require "yaml"
|
17
|
+
|
18
|
+
# 将 RUBY(HASH) 数据结构转换为 YAML
|
19
|
+
def to(hash)
|
20
|
+
::YAML.dump hash
|
21
|
+
end
|
22
|
+
# 将 YAML 转换为 RUBY(HASH) 数据结构
|
23
|
+
def from(yaml)
|
24
|
+
::YAML.load yaml
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Strada
|
4
|
+
class ConfigStruct
|
5
|
+
# 将配置信息转换为 HASH 对象
|
6
|
+
def _config_to_hash
|
7
|
+
hash = {}
|
8
|
+
@cfg.each do |key, value|
|
9
|
+
if value.class == ConfigStruct
|
10
|
+
value = value._config_to_hash
|
11
|
+
end
|
12
|
+
key = key.to_s if @key_to_s
|
13
|
+
hash[key] = value
|
14
|
+
end
|
15
|
+
hash
|
16
|
+
end
|
17
|
+
|
18
|
+
# 是否为空配置
|
19
|
+
def empty?
|
20
|
+
@cfg.empty?
|
21
|
+
end
|
22
|
+
|
23
|
+
# 调用配置 each 方法执行代码块
|
24
|
+
def each(&block)
|
25
|
+
@cfg.each(&block)
|
26
|
+
end
|
27
|
+
|
28
|
+
# 配置的相关属性
|
29
|
+
def keys
|
30
|
+
@cfg.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
# 判断配置是否包含某个属性
|
34
|
+
def has_key?(key)
|
35
|
+
@cfg.has_key? key
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def initialize(hash = nil, opts = {})
|
40
|
+
@key_to_s = opts.delete :key_to_s
|
41
|
+
@cfg = hash ? _config_from_hash(hash) : {}
|
42
|
+
end
|
43
|
+
|
44
|
+
# 方法反射
|
45
|
+
def method_missing(name, *args, &block)
|
46
|
+
name = name.to_s
|
47
|
+
name = args.shift if name[0..1] == "[]" # strada.cfg['foo']
|
48
|
+
arg = args.first
|
49
|
+
|
50
|
+
if name[-1..-1] == "?" # strada.cfg.foo.bar?
|
51
|
+
if @cfg.has_key? name[0..-2]
|
52
|
+
@cfg[name[0..-2]]
|
53
|
+
else
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
elsif name[-1..-1] == "=" # strada.cfg.foo.bar = 'quux'
|
57
|
+
_config_set name[0..-2], arg
|
58
|
+
else
|
59
|
+
_config_get name, arg # strada.cfg.foo.bar
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# 设置键值对
|
64
|
+
def _config_set(key, value)
|
65
|
+
@cfg[key] = value
|
66
|
+
end
|
67
|
+
|
68
|
+
# 查询 KEY VALUE
|
69
|
+
def _config_get(key, value)
|
70
|
+
if @cfg.has_key? key
|
71
|
+
@cfg[key]
|
72
|
+
else
|
73
|
+
@cfg[key] = ConfigStruct.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# 转换 HASH 数据为配置对象
|
78
|
+
def _config_from_hash(hash)
|
79
|
+
cfg = {}
|
80
|
+
hash.each do |key, value|
|
81
|
+
if value.class == Hash
|
82
|
+
value = ConfigStruct.new value, key_to_s: @key_to_s
|
83
|
+
end
|
84
|
+
cfg[key] = value
|
85
|
+
end
|
86
|
+
cfg
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/strada.rb
ADDED
@@ -0,0 +1,181 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "strada/config_struct"
|
4
|
+
require_relative "strada/adapter/yaml"
|
5
|
+
require_relative "strada/adapter/json"
|
6
|
+
require_relative "strada/adapter/toml"
|
7
|
+
require "fileutils"
|
8
|
+
|
9
|
+
class Strada < StandardError; end
|
10
|
+
|
11
|
+
class NoName < ConfigError; end
|
12
|
+
|
13
|
+
class UnknownOption < ConfigError; end
|
14
|
+
|
15
|
+
# @example common use case
|
16
|
+
# CFGS = Strada.new :name=>'my_sweet_program' :load=>false # do not load config from filesystem
|
17
|
+
# CFGS.default.ssh.port = 22
|
18
|
+
# CFGS.default.ssh.hosts = %w(host1.example.com host2.example.com)
|
19
|
+
# CFGS.default.auth.user = lana
|
20
|
+
# CFGS.default.auth.password = danger_zone
|
21
|
+
# CFGS.load # load system config and user config from filesystem and merge with defaults to #cfg
|
22
|
+
# raise StandardError, 'edit ~/.config/my_sweet_program/config' if CFGS.create # create user config from default config if no system or user config exists
|
23
|
+
# # use the damn thing
|
24
|
+
# CFG = CFGS.cfg
|
25
|
+
# user = CFG.auth.user
|
26
|
+
# password = CFG.auth.password
|
27
|
+
# ssh_port = CFG.ssh.port
|
28
|
+
# ssh_hosts = CFG.ssh.hosts
|
29
|
+
|
30
|
+
class Strada
|
31
|
+
CONFIG_FILE = "config"
|
32
|
+
# 类对象属性
|
33
|
+
attr_reader :cfg, :default, :file
|
34
|
+
attr_accessor :system, :user
|
35
|
+
|
36
|
+
# 类方法
|
37
|
+
class << self
|
38
|
+
def cfg(*args)
|
39
|
+
new(*args).cfg
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# When this is called, by default :system and :user are loaded from
|
44
|
+
# filesystem and merged with default, so that user overrides system which
|
45
|
+
# overrides default
|
46
|
+
#
|
47
|
+
# @param [Symbol] level which configuration level to load, by default :all
|
48
|
+
# @return [void]
|
49
|
+
def load(level = :all)
|
50
|
+
if (level == :default) || (level == :all)
|
51
|
+
@cfg = merge @cfg, @default
|
52
|
+
end
|
53
|
+
if (level == :system) || (level == :all)
|
54
|
+
@system = load_cfg @sys_dir
|
55
|
+
@cfg = merge @cfg, @system
|
56
|
+
end
|
57
|
+
if (level == :user) || (level == :all)
|
58
|
+
@user = load_cfg @usr_dir
|
59
|
+
@cfg = merge @cfg, @user
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# @param [Symbol] level which configuration level to save, by default :user
|
64
|
+
# @return [void]
|
65
|
+
def save(level = :user)
|
66
|
+
if level == :user
|
67
|
+
save_cfg @usr_dir, @user
|
68
|
+
elsif level == :system
|
69
|
+
save_cfg @sys_dir, @system
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# @example create user config from default config and raise error, if no config was found
|
74
|
+
# raise StandardError, 'edit ~/.config/name/config' if strada.create
|
75
|
+
# @param [Hash] opts options for Strada
|
76
|
+
# @option opts [Symbol] :source source to use for settings to save, by default :default
|
77
|
+
# @option opts [Symbol] :destination destination to use for settings to save, by default :user
|
78
|
+
# @option opts [boolean] :load load config once saved, by default false
|
79
|
+
# @return [boolean] true if config didn't exist and was created, false if config already exists
|
80
|
+
def create(opts = {})
|
81
|
+
src = opts.delete :source || :default
|
82
|
+
dst = opts.delete :destination || :user
|
83
|
+
|
84
|
+
no_config = false
|
85
|
+
no_config = true if @system.empty? && @user.empty?
|
86
|
+
if no_config
|
87
|
+
src = instance_variable_get "@" + src.to_s
|
88
|
+
instance_variable_set("@" + dst.to_s, src.dup)
|
89
|
+
save dst
|
90
|
+
load if opts.delete :load
|
91
|
+
end
|
92
|
+
no_config
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
def initialize(opts = {})
|
97
|
+
# @param [Hash] opts options for Strada.new
|
98
|
+
# @option opts [String] :name name to use for strada (/etc/name/, ~/.config/name/) - autodetected if not defined
|
99
|
+
# @option opts [String] :adapter adapter to use 'yaml', 'json' or 'toml' for now
|
100
|
+
# @option opts [String] :usr_dir directory for storing user config ~/.config/name/ by default
|
101
|
+
# @option opts [String] :sys_dir directory for storing system config /etc/name/ by default
|
102
|
+
# @option opts [String] :cfg_file configuration filename, by default CONFIG_FILE
|
103
|
+
# @option opts [Hash] :default default settings to use
|
104
|
+
# @option opts [boolean] :load automatically load+merge system+user config with defaults in #cfg
|
105
|
+
# @option opts [boolean] :key_to_s convert keys to string by calling #to_s for keys
|
106
|
+
@name = opts.delete(:name) || meta_name
|
107
|
+
@adapter = opts.delete(:adapter) || "yaml"
|
108
|
+
@usr_dir = opts.delete(:usr_dir) || File.join(Dir.home, ".config", @name)
|
109
|
+
@sys_dir = opts.delete(:sys_dir) || File.join("/etc", @name)
|
110
|
+
@cfg_file = opts.delete(:cfg_file) || CONFIG_FILE
|
111
|
+
|
112
|
+
# 配置对象属性
|
113
|
+
@default = ConfigStruct.new opts.delete(:default)
|
114
|
+
@system = ConfigStruct.new
|
115
|
+
@user = ConfigStruct.new
|
116
|
+
@cfg = ConfigStruct.new
|
117
|
+
@load = true
|
118
|
+
@load = opts.delete(:load) if opts.has_key?(:load)
|
119
|
+
@key_to_s = opts.delete(:key_to_s)
|
120
|
+
raise UnknownOption, "option '#{opts}' not recognized" unless opts.empty?
|
121
|
+
load :all if @load
|
122
|
+
end
|
123
|
+
|
124
|
+
# 加载配置文件
|
125
|
+
def load_cfg(dir)
|
126
|
+
@file = File.join dir, @cfg_file
|
127
|
+
file = File.read @file
|
128
|
+
ConfigStruct.new(from(@adapter, file), key_to_s: @key_to_s)
|
129
|
+
rescue Errno::ENOENT
|
130
|
+
ConfigStruct.new
|
131
|
+
end
|
132
|
+
|
133
|
+
# 保存配置文件
|
134
|
+
def save_cfg(dir, config)
|
135
|
+
config = to(@adapter, config)
|
136
|
+
file = File.join dir, @cfg_file
|
137
|
+
FileUtils.mkdir_p dir
|
138
|
+
File.write file, config
|
139
|
+
end
|
140
|
+
|
141
|
+
# 合并配置属性
|
142
|
+
def merge(*configs)
|
143
|
+
hash = {}
|
144
|
+
# 将相关配置依次迭代并转换为 RUBY HASH 对象
|
145
|
+
configs.each do |config|
|
146
|
+
hash = hash._config_deep_merge config._config_to_hash
|
147
|
+
end
|
148
|
+
# 将合并后的 HASH 数据结构转换为配置对象
|
149
|
+
ConfigStruct.new hash
|
150
|
+
end
|
151
|
+
|
152
|
+
# 将 JSON|YAML|TOML 等数据格式转换为 RUBY 数据结构
|
153
|
+
def from(adapter, string)
|
154
|
+
name = "from_" + adapter
|
155
|
+
send name, string
|
156
|
+
end
|
157
|
+
|
158
|
+
# 将 RUBY 数据结构转换为 JSON|YAML|TOML 对象
|
159
|
+
def to(adapter, config)
|
160
|
+
name = "to_" + adapter
|
161
|
+
send name, config
|
162
|
+
end
|
163
|
+
|
164
|
+
# 基础的配置标致名称
|
165
|
+
def meta_name
|
166
|
+
path = caller_locations[-1].path
|
167
|
+
File.basename path, File.extname(path)
|
168
|
+
rescue
|
169
|
+
raise NoName, "can't figure out name, specify explicitly"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# 增加 HASH 方法
|
174
|
+
class Hash
|
175
|
+
def _config_deep_merge(new_hash)
|
176
|
+
merger = proc do |key, old_val, new_val|
|
177
|
+
Hash === old_val && Hash === new_val ? old_val.merge(new_val, &merger) : new_val
|
178
|
+
end
|
179
|
+
merge new_hash, &merger
|
180
|
+
end
|
181
|
+
end
|
data/strada.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/strada/version"
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "strada"
|
6
|
+
s.version = Strada::VERSION
|
7
|
+
s.licenses = ["MIT"]
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["WENWU.YAN"]
|
10
|
+
s.email = %w( 968826@gmail.com )
|
11
|
+
s.homepage = "http://github.com/ciscolive/strada"
|
12
|
+
s.summary = "configuration library"
|
13
|
+
s.description = "configuration library with object access to YAML/JSON/TOML backends"
|
14
|
+
s.files = `git ls-files`.split("\n")
|
15
|
+
s.executables = %w( )
|
16
|
+
s.require_path = "lib"
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: strada
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- WENWU.YAN
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-04-14 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: configuration library with object access to YAML/JSON/TOML backends
|
14
|
+
email:
|
15
|
+
- 968826@gmail.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- ".gitignore"
|
21
|
+
- ".rubocop.yml"
|
22
|
+
- Gemfile
|
23
|
+
- README.md
|
24
|
+
- Rakefile
|
25
|
+
- lib/strada.rb
|
26
|
+
- lib/strada/adapter/json.rb
|
27
|
+
- lib/strada/adapter/toml.rb
|
28
|
+
- lib/strada/adapter/yaml.rb
|
29
|
+
- lib/strada/config_struct.rb
|
30
|
+
- lib/strada/version.rb
|
31
|
+
- strada.gemspec
|
32
|
+
homepage: http://github.com/ciscolive/strada
|
33
|
+
licenses:
|
34
|
+
- MIT
|
35
|
+
metadata: {}
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
requirements: []
|
51
|
+
rubygems_version: 3.3.3
|
52
|
+
signing_key:
|
53
|
+
specification_version: 4
|
54
|
+
summary: configuration library
|
55
|
+
test_files: []
|