fwissr 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ data.tar.gz: 933e4bc006d16954c87cc4f7b1290ec7fde652a4
4
+ metadata.gz: 2213070acd5d61baf5ebb2e5ae1188547aafb402
5
+ SHA512:
6
+ data.tar.gz: 42223b4c419f6c65e4ff88f35abfd14d787448bc79fb3aebdd1ab40746764e7542a42d10a4730f70aa86212192af708220dd4c6344cbc860afb79e088bd12fae
7
+ metadata.gz: 7f223d8a1d5f91a943b0fb0ac51fcc862a448f4279fa5479db8319a7441d20c36bb558f6eb8b2bd841bb1c6a37d4d3d4c29bdfc603a0cc5cdeea73e94ce052dc
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Fotonauts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,384 @@
1
+ Fwissr
2
+ ======
3
+
4
+ A simple configuration registry tool by Fotonauts.
5
+
6
+
7
+ Install
8
+ =======
9
+
10
+ ```bash
11
+ $ [sudo] gem install fwissr
12
+ ```
13
+
14
+ Or add it to your `Gemfile`.
15
+
16
+
17
+ Usage
18
+ =====
19
+
20
+ Create the main `fwissr.json` configuration file in either `/etc/fwissr/` or `~/.fwissr/` directory:
21
+
22
+ ```json
23
+ {
24
+ "foo" : "bar",
25
+ "horn" : { "loud" : true, "sounds": [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
26
+ }
27
+ ```
28
+
29
+ In your application, you can access `fwissr`'s global registry that way:
30
+
31
+ ```ruby
32
+ require 'fwissr'
33
+
34
+ Fwissr['/foo']
35
+ # => "bar"
36
+
37
+ Fwissr['/horn']
38
+ # => { "loud" => true, "sounds" => [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
39
+
40
+ Fwissr['/horn/loud']
41
+ # => true
42
+
43
+ Fwissr['/horn/sounds']
44
+ # => [ "TUuuUuuuu", "tiiiiiiIIiii" ]
45
+ ```
46
+
47
+ In bash you can call the `fwissr` tool:
48
+
49
+ ```bash
50
+ $ fwissr /foo
51
+ bar
52
+
53
+ # json output
54
+ $ fwissr -j /horn
55
+ { "loud" : true, "sounds": [ "TUuuUuuuu", "tiiiiiiIIiii" ] }
56
+
57
+ # pretty print json output
58
+ $ fwissr -j -p /horn
59
+ {
60
+ "loud": true,
61
+ "sound": [
62
+ "TUuuUuuuu",
63
+ "tiiiiiiIIiii"
64
+ ]
65
+ }
66
+
67
+ # dump registry with pretty print json output
68
+ # NOTE: yes, that's the same as 'fwissr -jp /'
69
+ $ fwissr --dump -jp
70
+ {
71
+ "horn": {
72
+ "loud": true,
73
+ "sound": [
74
+ "TUuuUuuuu",
75
+ "tiiiiiiIIiii"
76
+ ]
77
+ }
78
+ }
79
+ ```
80
+
81
+
82
+ Additional configuration file
83
+ =============================
84
+
85
+ In addition to the main `fwissr.json` configuration file, all files in `/etc/fwissr/` and `~/.fwissr/` directories are automatically loaded. The settings for these additional configurations are prefixed with the file name.
86
+
87
+ You can provide more configuration file locations with the `fwissr_sources` setting in `fwissr.json`:
88
+
89
+ ```json
90
+ {
91
+ "fwissr_sources": [
92
+ { "filepath": "/etc/my_app.json" }
93
+ ]
94
+ }
95
+ ```
96
+
97
+ For example, with that `/etc/my_app.json`:
98
+
99
+ ```json
100
+ { "foo": "bar", "bar": "baz" }
101
+ ```
102
+
103
+ settings are accessed that way:
104
+
105
+ ```ruby
106
+ require 'fwissr'
107
+
108
+ Fwissr['/my_app']
109
+ # => { "foo" => "bar", "bar" => "baz" }
110
+
111
+ Fwissr['/my_app/foo']
112
+ # => "bar"
113
+
114
+ Fwissr['/my_app/bar']
115
+ # => "baz"
116
+ ```
117
+
118
+ You can bypass that behaviour with the `top_level` setting:
119
+
120
+ ```json
121
+ {
122
+ "fwissr_sources": [
123
+ { "filepath": "/etc/my_app.json", "top_level": true }
124
+ ]
125
+ }
126
+ ```
127
+
128
+ With the `top_level` setting activated the configuration settings are added to registry root:
129
+
130
+ ```ruby
131
+ require 'fwissr'
132
+
133
+ Fwissr['/']
134
+ # => { "foo" => "bar", "bar" => "baz" }
135
+
136
+ Fwissr['/foo']
137
+ # => "bar"
138
+
139
+ Fwissr['/bar']
140
+ # => "baz"
141
+ ```
142
+
143
+ Fwissr supports `.json` and `.yaml` configuration files.
144
+
145
+
146
+ Directory of configuration files
147
+ ================================
148
+
149
+ If the `filepath` setting is a directory then all the configuration files in that directory (but NOT in subdirectories) are imported:
150
+
151
+ ```json
152
+ {
153
+ "fwissr_sources": [
154
+ { "filepath": "/mnt/my_app/conf/" },
155
+ ],
156
+ }
157
+ ```
158
+
159
+ With `/mnt/my_app/conf/database.yaml`:
160
+
161
+ ```yaml
162
+ production:
163
+ adapter: mysql2
164
+ encoding: utf8
165
+ database: my_app_db
166
+ username: my_app_user
167
+ password: my_app_pass
168
+ host: db.my_app.com
169
+ ```
170
+
171
+ and `/mnt/my_app/conf/credentials.json`:
172
+
173
+ ```json
174
+ { "key": "i5qw64816c", "code": "448e4wef161" }
175
+ ```
176
+
177
+ settings are accessed that way:
178
+
179
+ ```ruby
180
+ require 'fwissr'
181
+
182
+ Fwissr['/database']
183
+ # => { "production" => { "adapter" => "mysql2", "encoding" => "utf8", "database" => "my_app_db", "username" => "my_app_user", "password" => "my_app_pass", "host" => "db.my_app.com" } }
184
+
185
+ Fwissr['/database/production/host']
186
+ # => "db.my_app.com"
187
+
188
+ Fwissr['/credentials']
189
+ # => { "key" => "i5qw64816c", "code" => "448e4wef161" }
190
+
191
+ Fwissr['/credentials/key']
192
+ # => "i5qw64816c"
193
+ ```
194
+
195
+
196
+ File name mapping to setting path
197
+ =================================
198
+
199
+ Use dots in file name to define a path for configuration settings.
200
+
201
+ For example:
202
+
203
+ ```json
204
+ {
205
+ "fwissr_sources": [
206
+ { "filepath": "/etc/my_app.database.slave.json" }
207
+ ]
208
+ }
209
+ ```
210
+
211
+ with that `/etc/my_app.database.slave.json`:
212
+
213
+ ```json
214
+ { "host": "db.my_app.com", "port": "1337" }
215
+ ```
216
+
217
+ settings are accessed that way:
218
+
219
+ ```ruby
220
+ require 'fwissr'
221
+
222
+ Fwissr['/my_app/database/slave/host']
223
+ # => "db.my_app.com"
224
+
225
+ Fwissr['/my_app/database/slave/port']
226
+ # => "1337"
227
+ ```
228
+
229
+
230
+ Mongodb source
231
+ ==============
232
+
233
+ You can define a mongob collection as a configuration source:
234
+
235
+ ```json
236
+ {
237
+ "fwissr_sources": [
238
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config" }
239
+ ]
240
+ }
241
+ ```
242
+
243
+ Each document in the collection is a setting for that configuration.
244
+
245
+ The `_id` document field is the setting key, and the `value` document field is the setting value.
246
+
247
+ For example:
248
+
249
+ ```
250
+ > db["my_app.stuff"].find()
251
+ { "_id" : "foo", "value" : "bar" }
252
+ { "_id" : "database", "value" : { "host": "db.my_app.com", "port": "1337" } }
253
+ ```
254
+
255
+ ```ruby
256
+ require 'mongo'
257
+ require 'fwissr'
258
+
259
+ Fwissr['/my_app/stuff/foo']
260
+ # => "bar"
261
+
262
+ Fwissr['/my_app/stuff/database']
263
+ # => { "host": "db.my_app.com", "port": "1337" }
264
+
265
+ Fwissr['/my_app/stuff/database/port']
266
+ # => "1337"
267
+ ```
268
+
269
+ As with configuration files you can use dots in collection name to define a path for configuration settings. The `top_level` setting is also supported to bypass that behaviour. Note that the `fwissr` collection is by default a `top_level` configuration.
270
+
271
+ Fwissr supports both the official `mongo` ruby driver and the `mongoid`'s `moped` driver. Don't forget to require one of these gems.
272
+
273
+
274
+ Refreshing registry
275
+ ===================
276
+
277
+ Enable registry auto-update with the `refresh` source setting.
278
+
279
+ For example:
280
+
281
+ ```json
282
+ {
283
+ "fwissr_sources": [
284
+ { "filepath": "/etc/my_app/my_app.json" },
285
+ { "filepath": "/etc/my_app/stuff.json", "refresh": true },
286
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "production" },
287
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config", "refresh": true }
288
+ ]
289
+ }
290
+ ```
291
+
292
+ The `/etc/my_app/my_app.json` configuration file and the `production` mongodb collection are read once, whereas the settings holded by the `/etc/my_app/stuff.json` configuration file and the `config` mongodb collection expire periodically and re-fetched.
293
+
294
+ The default freshness is 30 seconds, but you can change it with the `fwissr_refresh_period` setting:
295
+
296
+ ```json
297
+ {
298
+ "fwissr_sources": [
299
+ { "filepath": "/etc/my_app/my_app.json" },
300
+ { "filepath": "/etc/my_app/stuff.json", "refresh": true },
301
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "production" },
302
+ { "mongodb": "mongodb://db1.example.net/my_app", "collection": "config", "refresh": true }
303
+ ],
304
+ "fwissr_refresh_period": 60
305
+ }
306
+ ```
307
+
308
+ The refresh is done periodically in a thread:
309
+
310
+ ```ruby
311
+ require 'fwissr'
312
+
313
+ Fwissr['/stuff/foo']
314
+ # => "bar"
315
+
316
+ # > Change '/etc/my_app/stuff.json' file by setting: {"foo":"baz"}
317
+
318
+ # Wait 2 minutes
319
+ sleep(120)
320
+
321
+ # The new value is now in the registry
322
+ Fwissr['/stuff/foo']
323
+ # => "baz"
324
+ ```
325
+
326
+
327
+ Create a custom registry
328
+ ========================
329
+
330
+ `fwissr` is intended to be easy to setup: just create a configuration file and that configuration is accessible via the global registry. But if you need to, you can create your own custom registry.
331
+
332
+ ```ruby
333
+ require 'fwissr'
334
+
335
+ # create a custom registry
336
+ registry = Fwissr::Registry.new('refresh_period' => 20)
337
+
338
+ # add configuration sources to registry
339
+ registry.add_source(Fwissr::Source.from_settings({ 'filepath': '/etc/my_app/my_app.json' }))
340
+ registry.add_source(Fwissr::Source.from_settings({ 'filepath': '/etc/my_app/stuff.json', 'refresh': true }))
341
+ registry.add_source(Fwissr::Source.from_settings({ 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'production' }))
342
+ registry.add_source(Fwissr::Source.from_settings({ 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'config', 'refresh': true }))
343
+
344
+ registry['/stuff/foo']
345
+ # => 'bar'
346
+ ```
347
+
348
+
349
+ Create a custom source
350
+ ======================
351
+
352
+ Currently `Fwissr::Source::File` and `Fwissr::Source::Mongodb` are the two kinds of possible registry sources, but you can define your own source:
353
+
354
+
355
+ ```ruby
356
+ class MyFwissrSource < Fwissr::Source
357
+
358
+ def initialize(db_handler, options = { })
359
+ super(options)
360
+
361
+ @db_handler = db_handler
362
+ end
363
+
364
+ def fetch_conf
365
+ @db_handler.find('my_conf').to_hash
366
+ # => { 'foo' => [ 'bar', 'baz' ] }
367
+ end
368
+
369
+ end # class MyFwissrSource
370
+
371
+ registry = Fwissr::Registry.new('refresh_period' => 20)
372
+ registry.add_source(MyFwissrSource.new(my_db_handler, 'refresh' => true))
373
+
374
+ registry['/foo']
375
+ # => [ 'bar', 'baz' ]
376
+ ```
377
+
378
+
379
+ Credits
380
+ =======
381
+
382
+ The Fotonauts team: http://www.fotopedia.com
383
+
384
+ Copyright (c) 2013 Fotonauts released under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rake'
2
+
3
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/./lib")
4
+ require 'fwissr'
5
+
6
+ task :default => :list
7
+ task :list do
8
+ system 'rake -T'
9
+ end
10
+
11
+ ##############################################################################
12
+ # SYNTAX CHECKING
13
+ ##############################################################################
14
+ desc 'Check code syntax'
15
+ task :check_syntax do
16
+ `find . -name "*.rb" |xargs -n1 ruby -c |grep -v "Syntax OK"`
17
+ puts "* Done"
18
+ end
19
+
20
+ ##############################################################################
21
+ # Stats
22
+ ##############################################################################
23
+ desc 'Show some stats about the code'
24
+ task :stats do
25
+ line_count = proc do |path|
26
+ Dir[path].collect { |f| File.open(f).readlines.reject { |l| l =~ /(^\s*(\#|\/\*))|^\s*$/ }.size }.inject(0){ |sum,n| sum += n }
27
+ end
28
+ comment_count = proc do |path|
29
+ Dir[path].collect { |f| File.open(f).readlines.select { |l| l =~ /^\s*\#/ }.size }.inject(0) { |sum,n| sum += n }
30
+ end
31
+ lib = line_count['lib/**/*.rb']
32
+ comment = comment_count['lib/**/*.rb']
33
+ ext = line_count['ext/**/*.{c,h}']
34
+ spec = line_count['spec/**/*.rb']
35
+
36
+ comment_ratio = '%1.2f' % (comment.to_f / lib.to_f)
37
+ spec_ratio = '%1.2f' % (spec.to_f / lib.to_f)
38
+
39
+ puts '/======================\\'
40
+ puts '| Part LOC |'
41
+ puts '|======================|'
42
+ puts "| lib #{lib.to_s.ljust(5)}|"
43
+ puts "| lib comments #{comment.to_s.ljust(5)}|"
44
+ puts "| ext #{ext.to_s.ljust(5)}|"
45
+ puts "| spec #{spec.to_s.ljust(5)}|"
46
+ puts '| ratios: |'
47
+ puts "| lib/comment #{comment_ratio.to_s.ljust(5)}|"
48
+ puts "| lib/spec #{spec_ratio.to_s.ljust(5)}|"
49
+ puts '\======================/'
50
+ end
data/bin/fwissr ADDED
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'pp'
5
+
6
+ $:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
7
+ require 'fwissr'
8
+
9
+ begin
10
+ # parse arguments
11
+ args = Fwissr.parse_args!(ARGV)
12
+
13
+ # get value
14
+ result = if args[:dump]
15
+ Fwissr.dump
16
+ else
17
+ Fwissr.get(args[:key])
18
+ end
19
+
20
+ # display result
21
+ if args[:json]
22
+ if FWISSR_USE_YAJL
23
+ yajl_options = args[:pretty] ? { :pretty => true, :indent => " " } : { }
24
+ puts Yajl::Encoder.encode(result, yajl_options)
25
+ else
26
+ puts args[:pretty] ? JSON.pretty_generate(result) : result.to_json
27
+ end
28
+ elsif args[:inspect]
29
+ if args[:pretty]
30
+ pp result
31
+ else
32
+ puts result.inspect
33
+ end
34
+ else
35
+ puts result
36
+ end
37
+ rescue => ex
38
+ puts "#{ex.message}"
39
+ raise ex
40
+ end
data/lib/fwissr.rb ADDED
@@ -0,0 +1,267 @@
1
+ require 'rubygems'
2
+ require 'optparse'
3
+
4
+ require 'yaml'
5
+
6
+ FWISSR_USE_YAJL = true
7
+
8
+ if FWISSR_USE_YAJL
9
+ require 'yajl'
10
+ else
11
+ require 'json'
12
+ end
13
+
14
+
15
+ require 'fwissr/version'
16
+ require 'fwissr/source'
17
+ require 'fwissr/registry'
18
+
19
+ module Fwissr
20
+
21
+ # default path where main conf file is located
22
+ DEFAULT_MAIN_CONF_PATH = "/etc/fwissr"
23
+
24
+ # default directory (relative to current user's home) where user's main conf file is located
25
+ DEFAULT_MAIN_USER_CONF_DIR = ".fwissr"
26
+
27
+ # main conf file
28
+ MAIN_CONF_FILE = "fwissr.json"
29
+
30
+ class << self
31
+ attr_writer :main_conf_path, :main_user_conf_path
32
+
33
+ # Get config files directory
34
+ def main_conf_path
35
+ @main_conf_path ||= DEFAULT_MAIN_CONF_PATH
36
+ end
37
+
38
+ # Get user's specific config files directory
39
+ def main_user_conf_path
40
+ @main_user_conf_path ||= File.join(Fwissr.find_home, DEFAULT_MAIN_USER_CONF_DIR)
41
+ end
42
+
43
+ # finds the user's home directory
44
+ #
45
+ # Borrowed from rubygems
46
+ def find_home
47
+ ['HOME', 'USERPROFILE'].each do |homekey|
48
+ return ENV[homekey] if ENV[homekey]
49
+ end
50
+
51
+ if ENV['HOMEDRIVE'] && ENV['HOMEPATH'] then
52
+ return "#{ENV['HOMEDRIVE']}:#{ENV['HOMEPATH']}"
53
+ end
54
+
55
+ begin
56
+ File.expand_path("~")
57
+ rescue
58
+ if File::ALT_SEPARATOR then
59
+ "C:/"
60
+ else
61
+ "/"
62
+ end
63
+ end
64
+ end
65
+
66
+ # Parse command line arguments
67
+ def parse_args!(argv)
68
+ args = {
69
+ :inspect => false,
70
+ :json => false,
71
+ :dump => false,
72
+ :pretty => false,
73
+ }
74
+
75
+ # define parser
76
+ opt_parser = OptionParser.new do |opts|
77
+ opts.banner = "Usage: fwissr [-ijph] <key>\nWith key:\n\t#{Fwissr.keys.sort.join("\n\t")}\n\n"
78
+
79
+ opts.define_head "The configuration registry."
80
+
81
+ opts.on("-i", "--inspect", "Returns 'inspected' result") do
82
+ args[:inspect] = true
83
+ end
84
+
85
+ opts.on("-j", "--json", "Returns result in json") do
86
+ args[:json] = true
87
+ end
88
+
89
+ opts.on("--dump", "Dump all keys and values") do
90
+ args[:dump] = true
91
+ end
92
+
93
+ opts.on("-p", "--pretty", "Pretty output") do
94
+ args[:pretty] = true
95
+ end
96
+
97
+ opts.on("-?", "-h", "--help", "Show this help message") do
98
+ puts opts
99
+ exit
100
+ end
101
+
102
+ opts.on_tail("--version", "Show version") do
103
+ puts Fwissr::VERSION
104
+ exit
105
+ end
106
+
107
+ end
108
+
109
+ # parse what we have on the command line
110
+ opt_parser.parse!(argv)
111
+
112
+ # get key
113
+ if argv.empty? && !args[:dump]
114
+ puts "Please specify the key, e.g. 'fwissr /fqdn'"
115
+ puts opt_parser
116
+ exit
117
+ end
118
+
119
+ args[:key] = argv.first unless argv.empty?
120
+
121
+ args
122
+ end
123
+
124
+
125
+ #
126
+ # Global Registry
127
+ #
128
+ #
129
+ # NOTE: Parses main conf files (/etc/fwissr/fwissr.json and ~/.fwissr/fwissr.json) then uses 'fwissr_sources' setting to setup additional sources
130
+ #
131
+ # Example of /etc/fwissr/fwissr.json file:
132
+ #
133
+ # {
134
+ # 'fwissr_sources': [
135
+ # { 'filepath': '/mnt/my_app/conf/' },
136
+ # { 'filepath': '/etc/my_app.json' },
137
+ # { 'mongodb': 'mongodb://db1.example.net/my_app', 'collection': 'config', 'refresh': true },
138
+ # ],
139
+ # 'fwissr_refresh_period': 30,
140
+ # }
141
+ #
142
+
143
+ # access global registry with Fwissr['/foo/bar']
144
+ def global_registry
145
+ @global_registry ||= begin
146
+ result = Fwissr::Registry.new('refresh_period' => self.main_conf['fwissr_refresh_period'])
147
+
148
+ # check main conf files
149
+ if File.exists?(self.main_conf_path) || File.exists?(self.main_user_conf_path)
150
+ # setup main conf files sources
151
+ if File.exists?(self.main_conf_path)
152
+ result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_conf_path }))
153
+ end
154
+
155
+ if File.exists?(self.main_user_conf_path)
156
+ result.add_source(Fwissr::Source.from_settings({ 'filepath' => self.main_user_conf_path }))
157
+ end
158
+
159
+ # setup additional sources
160
+ if !self.main_conf['fwissr_sources'].nil?
161
+ self.main_conf['fwissr_sources'].each do |source_setting|
162
+ result.add_source(Fwissr::Source.from_settings(source_setting))
163
+ end
164
+ end
165
+ end
166
+
167
+ result
168
+ end
169
+ end
170
+
171
+ # fetch main fwissr conf
172
+ def main_conf
173
+ @main_conf ||= begin
174
+ result = { }
175
+
176
+ if File.exists?(self.main_conf_file)
177
+ result = self.merge_conf!(result, self.parse_conf_file(self.main_conf_file))
178
+ end
179
+
180
+ if File.exists?(self.main_user_conf_file)
181
+ result = self.merge_conf!(result, self.parse_conf_file(self.main_user_conf_file))
182
+ end
183
+
184
+ result
185
+ end
186
+ end
187
+
188
+ def main_conf_file
189
+ @main_conf_file ||= File.join(self.main_conf_path, MAIN_CONF_FILE)
190
+ end
191
+
192
+ def main_user_conf_file
193
+ @main_user_conf_file ||= File.join(self.main_user_conf_path, MAIN_CONF_FILE)
194
+ end
195
+
196
+ # delegate to global registry
197
+ [ :[], :get ].each do |meth_name|
198
+ class_eval <<-EOS, __FILE__, __LINE__
199
+ def #{meth_name}(key)
200
+ self.global_registry[key]
201
+ end
202
+ EOS
203
+ end
204
+
205
+ [ :keys, :dump ].each do |meth_name|
206
+ class_eval <<-EOS, __FILE__, __LINE__
207
+ def #{meth_name}
208
+ self.global_registry.__send__('#{meth_name}')
209
+ end
210
+ EOS
211
+ end
212
+
213
+
214
+ #
215
+ # Utils
216
+ #
217
+
218
+ def parse_conf_file(conf_file_path)
219
+ conf_file_ext = File.extname(conf_file_path)
220
+
221
+ case conf_file_ext
222
+ when ".json"
223
+ # json file
224
+ if FWISSR_USE_YAJL
225
+ Yajl::Parser.parse(File.read(conf_file_path), :check_utf8 => false)
226
+ else
227
+ JSON.parse(File.read(conf_file_path))
228
+ end
229
+ when ".yaml", ".yml"
230
+ # yaml file
231
+ YAML.load_file(conf_file_path)
232
+ else
233
+ raise "Unsupported conf file kind: #{conf_file_path}"
234
+ end
235
+ end
236
+
237
+ # borrowed from rails
238
+ def merge_conf(to_hash, other_hash)
239
+ self.merge_conf!(to_hash.dup, other_hash)
240
+ end
241
+
242
+ # borrowed from rails
243
+ def merge_conf!(to_hash, other_hash)
244
+ other_hash.each_pair do |k,v|
245
+ tv = to_hash[k]
246
+ to_hash[k] = (tv.is_a?(Hash) && v.is_a?(Hash)) ? self.merge_conf(tv, v) : v
247
+ end
248
+ to_hash
249
+ end
250
+
251
+ # simple deep freezer
252
+ def deep_freeze(obj)
253
+ if obj.is_a?(Hash)
254
+ obj.each do |k, v|
255
+ self.deep_freeze(v)
256
+ end
257
+ elsif obj.is_a?(Array)
258
+ obj.each do |v|
259
+ self.deep_freeze(v)
260
+ end
261
+ end
262
+
263
+ obj.freeze
264
+ end
265
+ end # class << self
266
+
267
+ end # module Fwissr
@@ -0,0 +1,154 @@
1
+ require 'thread'
2
+
3
+ module Fwissr
4
+
5
+ class Registry
6
+
7
+ # refresh period in seconds
8
+ DEFAULT_REFRESH_PERIOD = 30
9
+
10
+ #
11
+ # API
12
+ #
13
+
14
+ attr_reader :refresh_period
15
+
16
+ def initialize(options = { })
17
+ @refresh_period = options['refresh_period'] || DEFAULT_REFRESH_PERIOD
18
+
19
+ @registry = { }
20
+ @sources = [ ]
21
+
22
+ # mutex for @registry and @sources
23
+ @semaphore = Mutex.new
24
+
25
+ @refresh_thread = nil
26
+ end
27
+
28
+ def add_source(source)
29
+ @semaphore.synchronize do
30
+ @sources << source
31
+ end
32
+
33
+ if @registry.frozen?
34
+ # already frozen, must reload everything
35
+ self.reload!
36
+ else
37
+ @semaphore.synchronize do
38
+ Fwissr.merge_conf!(@registry, source.get_conf)
39
+ end
40
+ end
41
+
42
+ self.ensure_refresh_thread
43
+ end
44
+
45
+ def reload!
46
+ self.reset!
47
+ self.load!
48
+ end
49
+
50
+ def get(key)
51
+ # split key
52
+ key_ary = key.split('/')
53
+
54
+ # remove first empty part
55
+ key_ary.shift if (key_ary.first == '')
56
+
57
+ cur_hash = self.registry
58
+ key_ary.each do |key_part|
59
+ cur_hash = cur_hash[key_part]
60
+ return nil if cur_hash.nil?
61
+ end
62
+
63
+ cur_hash
64
+ end
65
+
66
+ alias :[] :get
67
+
68
+ def keys
69
+ result = [ ]
70
+ _keys(result, [ ], self.registry)
71
+ result.sort
72
+ end
73
+
74
+ def dump
75
+ self.registry
76
+ end
77
+
78
+
79
+ #
80
+ # PRIVATE
81
+ #
82
+
83
+ def refresh_thread
84
+ @refresh_thread
85
+ end
86
+
87
+ def have_refreshable_source?
88
+ @semaphore.synchronize do
89
+ !@sources.find { |source| source.can_refresh? }.nil?
90
+ end
91
+ end
92
+
93
+ def ensure_refresh_thread
94
+ # check refresh thread state
95
+ if ((@refresh_period > 0) && self.have_refreshable_source?) && (!@refresh_thread || !@refresh_thread.alive?)
96
+ # (re)start refresh thread
97
+ @refresh_thread = Thread.new do
98
+ while(true) do
99
+ sleep(@refresh_period)
100
+ self.load!
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ def ensure_frozen
107
+ if !@registry.frozen?
108
+ @semaphore.synchronize do
109
+ Fwissr.deep_freeze(@registry)
110
+ end
111
+ end
112
+ end
113
+
114
+ def reset!
115
+ @semaphore.synchronize do
116
+ @registry = { }
117
+
118
+ @sources.each do |source|
119
+ source.reset!
120
+ end
121
+ end
122
+ end
123
+
124
+ def load!
125
+ @semaphore.synchronize do
126
+ @registry = { }
127
+
128
+ @sources.each do |source|
129
+ source_conf = source.get_conf
130
+ Fwissr.merge_conf!(@registry, source_conf)
131
+ end
132
+ end
133
+ end
134
+
135
+ def registry
136
+ self.ensure_refresh_thread
137
+ self.ensure_frozen
138
+
139
+ @registry
140
+ end
141
+
142
+ # helper for #keys
143
+ def _keys(result, key_ary, hash)
144
+ hash.each do |key, value|
145
+ key_ary << key
146
+ result << "/#{key_ary.join('/')}"
147
+ _keys(result, key_ary, value) if value.is_a?(Hash)
148
+ key_ary.pop
149
+ end
150
+ end
151
+
152
+ end # module Registry
153
+
154
+ end # module Fwissr
@@ -0,0 +1,60 @@
1
+ class Fwissr::Source
2
+
3
+ autoload :File, 'fwissr/source/file'
4
+ autoload :Mongodb, 'fwissr/source/mongodb'
5
+
6
+ class << self
7
+ def from_settings(settings)
8
+ raise "Unexpected source settings class: #{settings.inspect}" unless settings.is_a?(Hash)
9
+
10
+ if settings['filepath']
11
+ Fwissr::Source::File.from_settings(settings)
12
+ elsif settings['mongodb']
13
+ Fwissr::Source::Mongodb.from_settings(settings)
14
+ else
15
+ raise "Unexpected source settings kind: #{settings.inspect}"
16
+ end
17
+ end
18
+ end # class << self
19
+
20
+
21
+ #
22
+ # API
23
+ #
24
+
25
+ attr_reader :options
26
+
27
+ def initialize(options = { })
28
+ @options = options
29
+
30
+ @conf = nil
31
+ end
32
+
33
+ # reset source
34
+ def reset!
35
+ @conf = nil
36
+ end
37
+
38
+ # source can be refreshed ?
39
+ def can_refresh?
40
+ @options && (@options['refresh'] == true)
41
+ end
42
+
43
+ # get conf
44
+ def get_conf
45
+ if (@conf && !self.can_refresh?)
46
+ # return already fetched conf if refresh is not allowed
47
+ @conf
48
+ else
49
+ # fetch conf
50
+ @conf = self.fetch_conf
51
+ end
52
+ end
53
+
54
+ # fetch conf from source
55
+ def fetch_conf
56
+ # MUST be implemented by child class
57
+ raise "not implemented"
58
+ end
59
+
60
+ end # class Fwissr::Source
@@ -0,0 +1,88 @@
1
+ class Fwissr::Source::File < Fwissr::Source
2
+
3
+ class << self
4
+
5
+ def from_path(path, options = { })
6
+ if path.nil? || (path == '')
7
+ raise "Unexpected file source path: #{path.inspect}"
8
+ end
9
+
10
+ self.new(path, options)
11
+ end
12
+
13
+ def from_settings(settings)
14
+ options = settings.dup
15
+ options.delete('filepath')
16
+
17
+ self.from_path(settings['filepath'], options)
18
+ end
19
+
20
+ end # class << self
21
+
22
+
23
+ TOP_LEVEL_CONF_FILES = [ 'fwissr' ].freeze
24
+
25
+ attr_reader :path
26
+
27
+ #
28
+ # API
29
+ #
30
+
31
+ def initialize(path, options = { })
32
+ super(options)
33
+
34
+ raise "File not found: #{path}" if !::File.exists?(path)
35
+
36
+ @path = path
37
+ end
38
+
39
+ def fetch_conf
40
+ result = { }
41
+
42
+ conf_files = if ::File.directory?(@path)
43
+ Dir[@path + "/*.{json,yml}"].sort
44
+ else
45
+ [ @path ]
46
+ end
47
+
48
+ conf_files.each do |conf_file_path|
49
+ next unless ::File.file?(conf_file_path)
50
+
51
+ self.merge_conf_file!(result, conf_file_path)
52
+ end
53
+
54
+ result
55
+ end
56
+
57
+
58
+ #
59
+ # PRIVATE
60
+ #
61
+
62
+ def merge_conf_file!(result, conf_file_path)
63
+ # parse conf file
64
+ conf = Fwissr.parse_conf_file(conf_file_path)
65
+ if conf
66
+ conf_file_name = ::File.basename(conf_file_path, ::File.extname(conf_file_path))
67
+
68
+ result_part = result
69
+
70
+ unless TOP_LEVEL_CONF_FILES.include?(conf_file_name) || @options['top_level']
71
+ # merge conf at the correct place in registry
72
+ #
73
+ # eg: my_app.json => /my_app
74
+ # my_app.database.yml => /my_app/database
75
+ # my_app.database.slave.yml => /my_app/database/slave
76
+ key_ary = conf_file_name.split('.')
77
+ key_ary.each do |key_part|
78
+ result_part = (result_part[key_part] ||= { })
79
+ end
80
+ end
81
+
82
+ Fwissr.merge_conf!(result_part, conf)
83
+ else
84
+ raise "Failed to parse conf file: #{conf_file_path}"
85
+ end
86
+ end
87
+
88
+ end # class Fwissr::Source::File
@@ -0,0 +1,180 @@
1
+ require 'uri'
2
+
3
+ begin
4
+ require 'moped'
5
+ rescue LoadError
6
+ begin
7
+ require 'mongo'
8
+ rescue LoadError
9
+ raise "[fwissr] Can't find any suitable mongodb driver: please install 'mongo' or 'moped' gem"
10
+ end
11
+ end
12
+
13
+ class Fwissr::Source::Mongodb < Fwissr::Source
14
+
15
+ class Connection
16
+
17
+ attr_reader :db_name
18
+
19
+ # init
20
+ def initialize(uri)
21
+ raise "URI is missing: #{uri}" if (uri.nil? || uri == '')
22
+
23
+ @uri = uri
24
+ @collections = { }
25
+
26
+ @kind = if defined?(::Moped)
27
+ # moped driver
28
+ :moped
29
+ elsif defined?(::Mongo::MongoClient)
30
+ # mongo ruby driver < 2.0.0
31
+ :mongo
32
+ elsif defined?(::Mongo::Client)
33
+ raise "Sorry, mongo gem >= 2.0 is not supported yet"
34
+ else
35
+ raise "Can't find any suitable mongodb driver: please install 'mongo' or 'moped' gem"
36
+ end
37
+
38
+ # parse URI
39
+ parsed_uri = URI.parse(@uri)
40
+ @db_name = parsed_uri.path[1..-1]
41
+
42
+ if @db_name.nil? || (@db_name == '')
43
+ raise "Missing database in mongodb settings: #{settings['mongodb'].inspect}"
44
+ end
45
+ end
46
+
47
+ def conn
48
+ @conn ||= begin
49
+ case @kind
50
+ when :moped
51
+ ::Moped::Session.connect(@uri)
52
+ when :mongo
53
+ ::Mongo::MongoClient.from_uri(@uri)
54
+ end
55
+ end
56
+ end
57
+
58
+ def collection(col_name)
59
+ @collections[col_name] ||= begin
60
+ case @kind
61
+ when :moped
62
+ self.conn[col_name]
63
+ when :mongo
64
+ self.conn.db(@db_name).collection(col_name)
65
+ end
66
+ end
67
+ end
68
+
69
+ # returns an Enumerator for all documents from given collection
70
+ def fetch(col_name)
71
+ case @kind
72
+ when :moped, :mongo
73
+ self.collection(col_name).find()
74
+ end
75
+ end
76
+
77
+ # insert document in collection
78
+ def insert(col_name, doc)
79
+ case @kind
80
+ when :moped, :mongo
81
+ self.collection(col_name).insert(doc)
82
+ end
83
+ end
84
+
85
+ # create a collection
86
+ def create_collection(col_name)
87
+ case @kind
88
+ when :moped
89
+ # NOOP
90
+ when :mongo
91
+ self.conn.db(@db_name).create_collection(col_name)
92
+ end
93
+ end
94
+
95
+ # drop database
96
+ def drop_database(db_name)
97
+ case @kind
98
+ when :moped
99
+ self.conn.drop
100
+ when :mongo
101
+ self.conn.drop_database(db_name)
102
+ end
103
+ end
104
+ end # class Connection
105
+
106
+ class << self
107
+
108
+ def from_settings(settings)
109
+ if settings['mongodb'].nil? || (settings['mongodb'] == '') || settings['collection'].nil? || (settings['collection'] == '')
110
+ raise "Erroneous mongodb settings: #{settings.inspect}"
111
+ end
112
+
113
+ conn = self.connection_for_uri(settings['mongodb'])
114
+
115
+ options = settings.dup
116
+ options.delete('mongodb')
117
+ options.delete('collection')
118
+
119
+ self.new(conn, settings['collection'], options)
120
+ end
121
+
122
+ def connection_for_uri(uri)
123
+ @connections ||= { }
124
+ @connections[uri] ||= Fwissr::Source::Mongodb::Connection.new(uri)
125
+ end
126
+
127
+ end # class << self
128
+
129
+
130
+ TOP_LEVEL_COLLECTIONS = [ 'fwissr' ].freeze
131
+
132
+ attr_reader :conn, :collection_name
133
+
134
+ #
135
+ # API
136
+ #
137
+
138
+ def initialize(conn, collection_name, options = { })
139
+ super(options)
140
+
141
+ @conn = conn
142
+ @collection_name = collection_name
143
+ end
144
+
145
+ def fetch_conf
146
+ result = { }
147
+ result_part = result
148
+
149
+ unless TOP_LEVEL_COLLECTIONS.include?(@collection_name) || @options['top_level']
150
+ # merge conf at the correct place in registry
151
+ #
152
+ # eg: m_app => /my_app
153
+ # my_app.database => /my_app/database
154
+ # my_app.database.slave => /my_app/database/slave
155
+ key_ary = @collection_name.split('.')
156
+ key_ary.each do |key_part|
157
+ result_part = (result_part[key_part] ||= { })
158
+ end
159
+ end
160
+
161
+ # build conf hash from collection's documents
162
+ conf = { }
163
+ self.conn.fetch(@collection_name).each do |doc|
164
+ key = doc['_id']
165
+ value = if doc['value'].nil?
166
+ doc.delete('_id')
167
+ doc
168
+ else
169
+ doc['value']
170
+ end
171
+
172
+ conf[key] = value
173
+ end
174
+
175
+ Fwissr.merge_conf!(result_part, conf)
176
+
177
+ result
178
+ end
179
+
180
+ end # class Fwissr::Source::Mongodb
@@ -0,0 +1,3 @@
1
+ module Fwissr
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fwissr
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Fotonauts Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-12-03 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: yajl-ruby
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - &id002
20
+ - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: "0"
23
+ type: :runtime
24
+ version_requirements: *id001
25
+ - !ruby/object:Gem::Dependency
26
+ name: rspec
27
+ prerelease: false
28
+ requirement: &id003 !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - *id002
31
+ type: :development
32
+ version_requirements: *id003
33
+ description: " A simple configuration registry tool by Fotonauts.\n"
34
+ email:
35
+ - aymerick@fotonauts.com
36
+ - oct@fotonauts.com
37
+ executables:
38
+ - fwissr
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - LICENSE
45
+ - Rakefile
46
+ - README.md
47
+ - bin/fwissr
48
+ - lib/fwissr/registry.rb
49
+ - lib/fwissr/source/file.rb
50
+ - lib/fwissr/source/mongodb.rb
51
+ - lib/fwissr/source.rb
52
+ - lib/fwissr/version.rb
53
+ - lib/fwissr.rb
54
+ homepage: https://github.com/fotonauts/fwissr
55
+ licenses: []
56
+
57
+ metadata: {}
58
+
59
+ post_install_message:
60
+ rdoc_options: []
61
+
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - *id002
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - *id002
70
+ requirements: []
71
+
72
+ rubyforge_project:
73
+ rubygems_version: 2.1.11
74
+ signing_key:
75
+ specification_version: 4
76
+ summary: Fwissr
77
+ test_files: []
78
+