o 1.0.2 → 2.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.
- 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
|