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 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
- option = O.new
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
- # access
21
- option["a"]
22
- option[:a]
23
- option.a
24
- option.a? #=> true
82
+ ### assignment & access ###
25
83
 
26
- #access Hash methods.
27
- option._keys #=> [:a]
84
+ either way is fine
28
85
 
29
- assign default value
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
- option = O.new
32
- option.a #=> nil
102
+ ### node ###
33
103
 
34
- option = O.new 0
35
- option.a #=> 0
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
- another syntax
110
+ ### variable & path ###
38
111
 
39
- option = O do
40
- base = 1
41
- @a = base
42
- @b = base + 1
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
- read option from a file
129
+ either way is fine
49
130
 
50
- # ~/.gutenrc
51
- @a = 1
52
- @path = Pathname('/home')
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
- # a.rb
55
- require "pathname"
56
- option = O.load("~/.gutenrc")
57
- option.a #=> 1
138
+ another example
58
139
 
59
- configuration file
60
- ------------------
140
+ O do
141
+ age 1
61
142
 
62
- use instance variable to export field.
143
+ my do
144
+ age 2
145
+ end
63
146
 
64
- base = 1
65
- @a = base
66
- @b = O do
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
- # after O.load(file)
73
- option.a #=> 1
74
- option.b.a #=> 2
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
- [configuration](https://github.com/ahoward/configuration)
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
- # internal: store data in a Hash, the key of the Hash is always converted to symbol.
3
- #
4
- #
5
- #
6
- class O < Hash
7
- # PATH for O.load
8
- PATH = []
9
- Error = Exception.new
10
- LoadError = Exception.new(Error)
11
-
12
- class O_Eval
13
- def _data
14
- _data = {}
15
- self.instance_variables.each do |k|
16
- key = k[1..-1].to_sym
17
- value = self.instance_variable_get(k)
18
- _data[key] = value
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
- class << self
25
- # convert <#Hash> to <#O>
37
+ # get hash data from obj
38
+ #
39
+ # @param [O, Hash] obj
26
40
  #
27
- # @param [hash] hash
28
- # @return O
29
- def from_hash hash
30
- o = O.new
31
- o._replace hash
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
- # support PATH, and '~/.gutenrc'
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 load name
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
- PATH.each do |p|
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
- eval_file path
94
+ O.eval File.read(path)
74
95
  end
96
+ end
75
97
 
76
- # relative load a configuration file
77
- # @see load
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
- file = File.readlink(file) if File.symlink?(file)
102
+ attr_accessor :_parent, :_child, :_root
87
103
 
88
- path = nil
89
- [".rb", ""].each do |ext|
90
- f = File.absolute_path(File.join(File.dirname(file), name+ext))
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
- raise LoadError, "can't find file -- #{name}" unless path
98
-
99
- eval_file path
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
- private
103
- def eval_file path
104
- content = File.open(path){|f| f.read}
105
- o_eval = O_Eval.new
106
- o_eval.instance_eval(content)
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
- attr_reader :_data
128
+ def _root
129
+ @_root || self
130
+ end
113
131
 
114
- def initialize default=nil, &blk
115
- @_data = Hash.new(default)
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
- @_data[key.to_sym] = value
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
- @_data[key.to_sym]
142
+ key = key.respond_to?(:to_sym) ? key.to_sym : key
143
+ @_child[key]
129
144
  end
130
145
 
131
- def + other
132
- O.new(@_data, other._data)
146
+ def == other
147
+ _child == other._child
133
148
  end
134
149
 
135
- def _replace data
136
- case data
137
- when Hash
138
- @_data = data
139
- when O
140
- @_data = data._data
141
- end
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 _merge! data
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
- # _method goes to @_data.send(_method, ..)
151
- # method? #=> !! @_data[:method]
152
- # method #=> @_data[:method]
153
- # method=value #=> @_data[:method]=value
183
+ # .name?
184
+ # .name= value
185
+ # .name value
186
+ # ._name
154
187
  #
155
- def method_missing method, *args, &blk
156
- if method =~ /(.*)=$/
157
- @_data[$1.to_sym] = args[0]
158
- elsif method =~ /(.*)\?$/
159
- !! @_data[$1.to_sym]
160
- elsif method =~ /^_(.*)/
161
- method = $1.to_sym
162
- args.map!{|arg| O===arg ? arg._data : arg}
163
- rst = @_data.send(method, *args, &blk)
164
-
165
- if [:merge!].include method
166
- self
167
- elsif [:merge].include method
168
- O.new(rst)
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
- @_data[method]
249
+ @_child[name] = args[0]
250
+ return args[0]
172
251
  end
173
252
  end
174
253
 
175
- def inspect
176
- rst = "<#O "
177
- @_data.each do |k,v|
178
- rst << "#{k}:#{v.inspect} "
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