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 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