o 1.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +207 -46
- data/lib/o.rb +193 -107
- data/lib/o/hash_method_fix.rb +13 -0
- data/lib/o/parser.rb +88 -0
- data/lib/o/semantics.rb +12 -0
- data/{version.rb → lib/o/version.rb} +3 -3
- data/lib/o1.rb +189 -0
- data/o.gemspec +2 -2
- data/spec/data/rc.rb +0 -0
- data/spec/o/parser_spec.rb +120 -0
- data/spec/o_spec.rb +245 -67
- data/spec/spec_helper.rb +1 -0
- metadata +9 -3
data/README.md
CHANGED
@@ -7,71 +7,229 @@ O, a configuration libraray for Ruby
|
|
7
7
|
**Documentation**: [http://rubydoc.info/gems/o/frames](http://rubydoc.info/gems/o/frames) <br/>
|
8
8
|
**Issue Tracker**: [https://github.com/GutenYe/o/issues](https://github.com/GutenYe/o/issues) <br/>
|
9
9
|
|
10
|
+
Features
|
11
|
+
--------
|
12
|
+
|
13
|
+
* support variable, computed attribute
|
14
|
+
* DSL syntax in pure ruby
|
15
|
+
* tree way to do configration.
|
16
|
+
* hash compatibility
|
17
|
+
|
10
18
|
Introduction
|
11
19
|
-------------
|
12
20
|
|
13
|
-
|
21
|
+
do configuration at three levels: system, user, cmdline
|
22
|
+
|
23
|
+
lib/guten/rc.rb # system level
|
24
|
+
~/.gutenrc # user level
|
25
|
+
$ guten --list # cmdline level
|
26
|
+
|
27
|
+
module Guten
|
28
|
+
Rc = O.require("guten/rc") + O.require("~/.gutenrc")
|
29
|
+
Rc.list = true
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
### a completed example ###
|
34
|
+
|
35
|
+
Rc = O do
|
36
|
+
host "localhost"
|
37
|
+
port 8080
|
38
|
+
mail.stmp.address "stmp.gmail.com"
|
39
|
+
|
40
|
+
my.development do # namespace
|
41
|
+
adapter "mysql2"
|
42
|
+
database "hello"
|
43
|
+
username "guten"
|
44
|
+
end
|
45
|
+
|
46
|
+
time proc{|offset| Time.now} # computed attribute
|
47
|
+
end
|
48
|
+
|
49
|
+
alternative syntax
|
50
|
+
|
51
|
+
Rc = O do |c|
|
52
|
+
c.host = "localhost"
|
53
|
+
c.port = 8080
|
54
|
+
c.mail.stmp.address "stmp.gmail.com"
|
55
|
+
|
56
|
+
my.development do |c|
|
57
|
+
c.adapter = "mysql2"
|
58
|
+
c.database = "hello"
|
59
|
+
c.username = "guten"
|
60
|
+
end
|
61
|
+
|
62
|
+
c.time = proc{|offset| Time.now}
|
63
|
+
end
|
64
|
+
|
65
|
+
### initialize ###
|
66
|
+
|
67
|
+
either way is fine
|
68
|
+
|
69
|
+
Rc = O.new
|
70
|
+
Rc = O.require "guten/rc" # from file
|
71
|
+
Rc = O do
|
72
|
+
a 1
|
73
|
+
end
|
74
|
+
Rc = O[a:1] # from hash
|
75
|
+
Rc._merge! O_or_Hash
|
76
|
+
|
77
|
+
file: "guten/rc.rb"
|
78
|
+
|
79
|
+
a 1
|
14
80
|
|
15
|
-
# assigment
|
16
|
-
option["a"] = 1
|
17
|
-
option[:a] = 1
|
18
|
-
option.a = 1
|
19
81
|
|
20
|
-
|
21
|
-
option["a"]
|
22
|
-
option[:a]
|
23
|
-
option.a
|
24
|
-
option.a? #=> true
|
82
|
+
### assignment & access ###
|
25
83
|
|
26
|
-
|
27
|
-
option._keys #=> [:a]
|
84
|
+
either way is fine
|
28
85
|
|
29
|
-
|
86
|
+
Rc.age 1
|
87
|
+
Rc.age = 1
|
88
|
+
Rc[:age] = 1
|
89
|
+
Rc["age"] = 1
|
90
|
+
---
|
91
|
+
Rc.age #=> 1
|
92
|
+
Rc.age? #=> true
|
93
|
+
Rc[:age] #=> 1
|
94
|
+
Rc["age"] #=> 1
|
95
|
+
---
|
96
|
+
O do |c|
|
97
|
+
age 2
|
98
|
+
c.age = 2
|
99
|
+
c[:age] = 2
|
100
|
+
end
|
30
101
|
|
31
|
-
|
32
|
-
option.a #=> nil
|
102
|
+
### node ###
|
33
103
|
|
34
|
-
|
35
|
-
|
104
|
+
Rc.a.b.c 1
|
105
|
+
p Rc.a.b.c #=> <#Fixnum 1>
|
106
|
+
p Rc.a.b #=> <#O>
|
107
|
+
p Rc.a #=> <#O>
|
108
|
+
p Rc.i.dont.exists #=> <#O> #check use #_empty?
|
36
109
|
|
37
|
-
|
110
|
+
### variable & path ###
|
38
111
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
112
|
+
O do
|
113
|
+
age 1
|
114
|
+
p age #=> 1
|
115
|
+
my do
|
116
|
+
age 2
|
117
|
+
friend do
|
118
|
+
age 3
|
119
|
+
p age #=> 3
|
120
|
+
p __.age #=> 2 relative
|
121
|
+
p ___.age #=> 1
|
122
|
+
p _.age #=> 1 root
|
123
|
+
end
|
124
|
+
end
|
43
125
|
end
|
44
|
-
option.a #=> 1
|
45
|
-
option.b #=> 2
|
46
126
|
|
127
|
+
### namespace ###
|
47
128
|
|
48
|
-
|
129
|
+
either way is fine
|
49
130
|
|
50
|
-
|
51
|
-
|
52
|
-
|
131
|
+
O do
|
132
|
+
mail.stmp.address "stmp.gmail.com"
|
133
|
+
mail.stmp do
|
134
|
+
address "stmp.gmail.com"
|
135
|
+
end
|
136
|
+
end
|
53
137
|
|
54
|
-
|
55
|
-
require "pathname"
|
56
|
-
option = O.load("~/.gutenrc")
|
57
|
-
option.a #=> 1
|
138
|
+
another example
|
58
139
|
|
59
|
-
|
60
|
-
|
140
|
+
O do
|
141
|
+
age 1
|
61
142
|
|
62
|
-
|
143
|
+
my do
|
144
|
+
age 2
|
145
|
+
end
|
63
146
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
p @a #=> nil # instance variable can't pass into block
|
68
|
-
p base #=> 1 # local variable can pass into block
|
69
|
-
@a = base + 1
|
147
|
+
my.friend do
|
148
|
+
age 3
|
149
|
+
end
|
70
150
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
151
|
+
|
152
|
+
|
153
|
+
### group ###
|
154
|
+
|
155
|
+
use namespace or use some seperate files like rails.
|
156
|
+
|
157
|
+
config/
|
158
|
+
applications.rb
|
159
|
+
environments/
|
160
|
+
development.rb
|
161
|
+
test.rb
|
162
|
+
production.rb
|
163
|
+
|
164
|
+
### computed attribute ###
|
165
|
+
|
166
|
+
Rc = O do
|
167
|
+
time proc{|n| Time.now}
|
168
|
+
end
|
169
|
+
p Rc.time # print current time. no need Rc.time.call()
|
170
|
+
p Rc.time(2) # call time
|
171
|
+
Rc.time = 2 # assign new value
|
172
|
+
p Rc[:time] #=> <#Proc>
|
173
|
+
|
174
|
+
### semantic ###
|
175
|
+
|
176
|
+
O do
|
177
|
+
is_started no # yes ...
|
178
|
+
end
|
179
|
+
|
180
|
+
for a list of semantic methods, see O::Semantics
|
181
|
+
|
182
|
+
### hash compatibility ###
|
183
|
+
|
184
|
+
Rc._keys # access hash method via `_method`
|
185
|
+
|
186
|
+
### temporarily change ###
|
187
|
+
|
188
|
+
Rc.a = 1
|
189
|
+
Rc._temp do
|
190
|
+
Rc.a = 2
|
191
|
+
end
|
192
|
+
p Rc.a #=> 1
|
193
|
+
|
194
|
+
|
195
|
+
### access builtin method inside block ###
|
196
|
+
|
197
|
+
Rc = O do
|
198
|
+
sleep 10 # is a data. Rc.sleep #=> 10
|
199
|
+
O.sleep 10 # call builtin 'sleep' method
|
200
|
+
end
|
201
|
+
|
202
|
+
a list of blocked methods is in O::BUILTIN_METHODS
|
203
|
+
|
204
|
+
### another sugar syntax ###
|
205
|
+
|
206
|
+
it likes yaml-style. this way is experiment. used in file syntax only
|
207
|
+
|
208
|
+
# file: guten/rc.rb
|
209
|
+
a:
|
210
|
+
b 1
|
211
|
+
c:
|
212
|
+
d 1
|
213
|
+
|
214
|
+
#=>
|
215
|
+
|
216
|
+
a do
|
217
|
+
b 1
|
218
|
+
c do
|
219
|
+
d 1
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
**WARNNING**: must use \t to indent
|
224
|
+
|
225
|
+
### some other examples ###
|
226
|
+
|
227
|
+
name do
|
228
|
+
first "Guten"
|
229
|
+
last "Ye"
|
230
|
+
is "#{first} #{last}"
|
231
|
+
end
|
232
|
+
|
75
233
|
|
76
234
|
Contributing
|
77
235
|
-------------
|
@@ -90,7 +248,10 @@ Install
|
|
90
248
|
Resources
|
91
249
|
---------
|
92
250
|
|
93
|
-
[
|
251
|
+
* [konfigurator](https://github.com/nu7hatch/konfigurator) Small and flexible configuration toolkit inspired i.a. by Sinatra settings
|
252
|
+
* [configatron](https://github.com/markbates/configatron) A super cool, simple, and feature rich configuration system for Ruby apps
|
253
|
+
* [simpleconfig](https://github.com/lukeredpath/simpleconfig) make application-wide configuration settings easy to set and access in an object-oriented fashion
|
254
|
+
* [configuration](https://github.com/ahoward/configuration) pure ruby scoped configuration files
|
94
255
|
|
95
256
|
Copyright
|
96
257
|
---------
|
data/lib/o.rb
CHANGED
@@ -1,40 +1,55 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
class O
|
7
|
-
|
8
|
-
|
9
|
-
Error
|
10
|
-
LoadError =
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
libdir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
|
3
|
+
|
4
|
+
%w(semantics hash_method_fix parser).each{|n| require "o/#{n}"}
|
5
|
+
|
6
|
+
class O
|
7
|
+
autoload :VERSION, "o/version"
|
8
|
+
|
9
|
+
Error = Class.new Exception
|
10
|
+
LoadError = Class.new Error
|
11
|
+
|
12
|
+
BUILTIN_METHODS = [ :p, :pd, :raise, :sleep, :rand, :srand, :exit, :require, :at_exit, :autoload, :open]
|
13
|
+
|
14
|
+
class << self
|
15
|
+
public *BUILTIN_METHODS
|
16
|
+
|
17
|
+
# @params [String] content
|
18
|
+
def eval content=nil, &blk
|
19
|
+
o = O.new nil
|
20
|
+
content ? o.instance_eval(Parser.compile(content)) : o.instance_eval(&blk)
|
21
|
+
o._root
|
22
|
+
end
|
23
|
+
|
24
|
+
# convert hash, O to O
|
25
|
+
# @param [O,Hash] data
|
26
|
+
def [] data
|
27
|
+
case data
|
28
|
+
when O
|
29
|
+
data
|
30
|
+
when Hash
|
31
|
+
o = O.new
|
32
|
+
o._child = data
|
33
|
+
o
|
19
34
|
end
|
20
|
-
_data
|
21
35
|
end
|
22
|
-
end
|
23
36
|
|
24
|
-
|
25
|
-
#
|
37
|
+
# get hash data from obj
|
38
|
+
#
|
39
|
+
# @param [O, Hash] obj
|
26
40
|
#
|
27
|
-
# @
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
41
|
+
# @return [Hash]
|
42
|
+
def get obj
|
43
|
+
case obj
|
44
|
+
when Hash
|
45
|
+
obj
|
46
|
+
when O
|
47
|
+
obj._child
|
48
|
+
end
|
32
49
|
end
|
33
50
|
|
34
51
|
# load a configuration file,
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# first try name.rb, then use name
|
52
|
+
# use $: and support '~/.gutenrc'
|
38
53
|
#
|
39
54
|
# @example
|
40
55
|
# option = O.load("~/.gutenrc")
|
@@ -42,21 +57,27 @@ class O < Hash
|
|
42
57
|
# option = O.load("/absolute/path/a.rb")
|
43
58
|
#
|
44
59
|
# O::Path << "/home"
|
45
|
-
# option = O.load("guten") #=> try guten.rb; then try guten
|
60
|
+
# option = O.load("guten") #=> try "guten.rb"; then try "guten"
|
46
61
|
# option = O.load("guten.rb")
|
47
62
|
#
|
48
63
|
# @param [String] name
|
49
64
|
# @return [O]
|
50
|
-
def
|
65
|
+
def require name
|
51
66
|
path = nil
|
67
|
+
|
68
|
+
# ~/.gutenrc
|
52
69
|
if name =~ /^~/
|
53
70
|
file = File.expand_path(name)
|
54
71
|
path = file if File.exists?(file)
|
72
|
+
|
73
|
+
# /absolute/path/to/rc
|
55
74
|
elsif File.absolute_path(name) == name
|
56
75
|
path = name if File.exists?(name)
|
76
|
+
|
77
|
+
# relative/rc
|
57
78
|
else
|
58
79
|
catch :break do
|
59
|
-
|
80
|
+
$:.each do |p|
|
60
81
|
['.rb', ''].each {|ext|
|
61
82
|
file = File.join(p, name+ext)
|
62
83
|
if File.exists? file
|
@@ -70,118 +91,183 @@ class O < Hash
|
|
70
91
|
|
71
92
|
raise LoadError, "can't find file -- #{name}" unless path
|
72
93
|
|
73
|
-
|
94
|
+
O.eval File.read(path)
|
74
95
|
end
|
96
|
+
end
|
75
97
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# @param [String] name
|
80
|
-
# @return [O] option
|
81
|
-
def relative_load name
|
82
|
-
pd caller if $TEST
|
83
|
-
a,file, line, method = caller[0].match(/^(.*):(\d+):.*`(.*)'$/).to_a
|
84
|
-
raise LoadError, "#{type} is called in #{file}" if file=~/\(.*\)/ # eval, etc.
|
98
|
+
undef_method *BUILTIN_METHODS
|
99
|
+
include Semantics
|
100
|
+
include HashMethodFix
|
85
101
|
|
86
|
-
|
102
|
+
attr_accessor :_parent, :_child, :_root
|
87
103
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if File.exists?(f)
|
92
|
-
path = f
|
93
|
-
break
|
94
|
-
end
|
95
|
-
end
|
104
|
+
def initialize default=nil, options={}, &blk
|
105
|
+
@_root = options[:_root]
|
106
|
+
@_child = Hash.new(default)
|
96
107
|
|
97
|
-
|
98
|
-
|
99
|
-
|
108
|
+
if blk
|
109
|
+
method = _blk2method(&blk)
|
110
|
+
if blk.arity == 0
|
111
|
+
method.call
|
112
|
+
else
|
113
|
+
method.call self
|
114
|
+
end
|
100
115
|
end
|
116
|
+
end
|
101
117
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
O.from_hash(o_eval._data)
|
108
|
-
end
|
118
|
+
def _temp &blk
|
119
|
+
data = _child.dup
|
120
|
+
blk.call
|
121
|
+
self._child = data
|
122
|
+
end
|
109
123
|
|
124
|
+
def _parent
|
125
|
+
@_parent || nil
|
110
126
|
end
|
111
127
|
|
112
|
-
|
128
|
+
def _root
|
129
|
+
@_root || self
|
130
|
+
end
|
113
131
|
|
114
|
-
def
|
115
|
-
@
|
116
|
-
if blk
|
117
|
-
o_eval = O_Eval.new
|
118
|
-
o_eval.instance_eval &blk
|
119
|
-
@_data.merge!(o_eval._data)
|
120
|
-
end
|
132
|
+
def _child= obj
|
133
|
+
@_child = O.get(obj)
|
121
134
|
end
|
122
135
|
|
123
136
|
def []= key, value
|
124
|
-
|
137
|
+
key = key.respond_to?(:to_sym) ? key.to_sym : key
|
138
|
+
@_child[key] = value
|
125
139
|
end
|
126
140
|
|
127
141
|
def [] key
|
128
|
-
|
142
|
+
key = key.respond_to?(:to_sym) ? key.to_sym : key
|
143
|
+
@_child[key]
|
129
144
|
end
|
130
145
|
|
131
|
-
def
|
132
|
-
|
146
|
+
def == other
|
147
|
+
_child == other._child
|
133
148
|
end
|
134
149
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
150
|
+
def _dup
|
151
|
+
o = O.new
|
152
|
+
o._child = _child.dup
|
153
|
+
o
|
154
|
+
end
|
155
|
+
|
156
|
+
def _replace obj
|
157
|
+
self._child = O.get(obj)
|
142
158
|
self
|
143
159
|
end
|
144
160
|
|
145
|
-
def
|
161
|
+
def + other
|
162
|
+
raise Error, "not support type for + -- #{other.inspect}" unless O === other
|
163
|
+
O.new(_child, other._child)
|
164
|
+
end
|
165
|
+
|
166
|
+
# convert block to method.
|
167
|
+
#
|
168
|
+
# you can call a block with arguments
|
169
|
+
#
|
170
|
+
# @example USAGE
|
171
|
+
# instance_eval(&blk)
|
172
|
+
# blk2method(&blk).call *args
|
173
|
+
#
|
174
|
+
def _blk2method &blk
|
175
|
+
self.class.class_eval do
|
176
|
+
define_method(:__blk2method, &blk)
|
177
|
+
end
|
178
|
+
method(:__blk2method)
|
146
179
|
end
|
147
180
|
|
148
181
|
|
149
182
|
#
|
150
|
-
#
|
151
|
-
#
|
152
|
-
#
|
153
|
-
#
|
183
|
+
# .name?
|
184
|
+
# .name= value
|
185
|
+
# .name value
|
186
|
+
# ._name
|
154
187
|
#
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
188
|
+
# .c
|
189
|
+
# .a.b.c
|
190
|
+
#
|
191
|
+
def method_missing name, *args, &blk
|
192
|
+
#O.pd 'missing', name, args, blk
|
193
|
+
|
194
|
+
# path: root
|
195
|
+
if name == :_
|
196
|
+
return _root
|
197
|
+
|
198
|
+
# relative path.
|
199
|
+
elsif name =~ /^__+$/
|
200
|
+
num = name.to_s.count('_') - 1
|
201
|
+
node = self
|
202
|
+
num.times {
|
203
|
+
return unless node
|
204
|
+
node = node._parent
|
205
|
+
}
|
206
|
+
return node
|
207
|
+
|
208
|
+
# .name=
|
209
|
+
elsif name =~ /(.*)=$/
|
210
|
+
return @_child[$1.to_sym] = args[0]
|
211
|
+
|
212
|
+
# .name?
|
213
|
+
elsif name =~ /(.*)\?$/
|
214
|
+
return !! @_child[$1.to_sym]
|
215
|
+
|
216
|
+
# ._name
|
217
|
+
elsif name =~ /^_(.*)/
|
218
|
+
name = $1.to_sym
|
219
|
+
args.map!{|arg| O===arg ? arg._child : arg}
|
220
|
+
return @_child.send(name, *args, &blk)
|
221
|
+
|
222
|
+
elsif Proc === @_child[name]
|
223
|
+
return @_child[name].call *args
|
224
|
+
|
225
|
+
# a.c # return data if has :c
|
226
|
+
# a.c # create new <#O> if no :c
|
227
|
+
#
|
228
|
+
elsif args.empty?
|
229
|
+
|
230
|
+
# a.b.c 1
|
231
|
+
# a.b do
|
232
|
+
# c 2
|
233
|
+
# end
|
234
|
+
if @_child.has_key?(name)
|
235
|
+
o = @_child[name]
|
236
|
+
o.instance_eval(&blk) if blk
|
237
|
+
return o
|
238
|
+
|
239
|
+
else
|
240
|
+
next_o = O.new(nil, {_root: _root})
|
241
|
+
next_o._parent = self
|
242
|
+
self._child[name] = next_o
|
243
|
+
next_o.instance_eval(&blk) if blk
|
244
|
+
return next_o
|
169
245
|
end
|
246
|
+
|
247
|
+
# .name value
|
170
248
|
else
|
171
|
-
@
|
249
|
+
@_child[name] = args[0]
|
250
|
+
return args[0]
|
172
251
|
end
|
173
252
|
end
|
174
253
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
254
|
+
#
|
255
|
+
# <#O
|
256
|
+
# :b => 1
|
257
|
+
# :c => 2
|
258
|
+
# :d => <#O
|
259
|
+
# :c => 2>>
|
260
|
+
def inspect(indent=" ")
|
261
|
+
o={rst: ""}
|
262
|
+
o[:rst] << "<#O\n"
|
263
|
+
_child.each do |k,v|
|
264
|
+
o[:rst] << "#{indent}#{k.inspect} => "
|
265
|
+
o[:rst] << (O === v ? "#{v.inspect(indent+" ")}\n" : "#{v.inspect}\n")
|
179
266
|
end
|
180
|
-
rst << "
|
267
|
+
o[:rst].rstrip! << ">"
|
181
268
|
end
|
182
269
|
|
183
270
|
alias to_s inspect
|
184
|
-
|
185
271
|
end
|
186
272
|
|
187
273
|
module Kernel
|