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/lib/o/parser.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
class O
|
2
|
+
class Parser
|
3
|
+
attr_reader :content
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def compile content
|
7
|
+
parser = Parser.new content
|
8
|
+
parser.compile
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize content
|
13
|
+
@content = content
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile
|
17
|
+
script = ""
|
18
|
+
indent_counts = 0
|
19
|
+
block_start = false
|
20
|
+
|
21
|
+
scan do |token, statement|
|
22
|
+
case token
|
23
|
+
when :block_start
|
24
|
+
block_start = true
|
25
|
+
statement = statement.sub(":", " do")
|
26
|
+
script << statement << "\n"
|
27
|
+
when :statement
|
28
|
+
script << statement << "\n"
|
29
|
+
when :indent
|
30
|
+
indent_counts += 1
|
31
|
+
script << "\t"*indent_counts
|
32
|
+
when :undent
|
33
|
+
script << "\t"*indent_counts
|
34
|
+
when :dedent
|
35
|
+
if block_start
|
36
|
+
block_start = false
|
37
|
+
script << "\t"*(indent_counts-1) + "end\n"
|
38
|
+
else
|
39
|
+
script << "\t"*(indent_counts-1)
|
40
|
+
end
|
41
|
+
indent_counts -= 1
|
42
|
+
end
|
43
|
+
end
|
44
|
+
script
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def scan
|
50
|
+
last_indent = 0
|
51
|
+
|
52
|
+
content.scan(/(.*?)(\n+|\Z)/).each do |line, newline|
|
53
|
+
|
54
|
+
#pd line.match(/^(\t*)(.*)/)
|
55
|
+
_, indents, statement = line.match(/^(\t*)(.*)/).to_a
|
56
|
+
|
57
|
+
# indent
|
58
|
+
# a:
|
59
|
+
# b 1
|
60
|
+
# c:
|
61
|
+
# d 1
|
62
|
+
# e:
|
63
|
+
# f 1
|
64
|
+
# g 1
|
65
|
+
indent = indents.count("\t")
|
66
|
+
counts = indent - last_indent
|
67
|
+
last_indent = indent
|
68
|
+
|
69
|
+
if counts == 0
|
70
|
+
yield :undent
|
71
|
+
else
|
72
|
+
counts.abs.times {
|
73
|
+
yield (counts>0 ? :indent : :dedent)
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# statement
|
78
|
+
if statement =~ /:\s*$/
|
79
|
+
yield :block_start, statement.gsub(/\s*:\s*$/, ':')
|
80
|
+
else
|
81
|
+
yield :statement, statement
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
data/lib/o/semantics.rb
ADDED
data/lib/o1.rb
ADDED
@@ -0,0 +1,189 @@
|
|
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
|
+
autoload :VERSION, "o/version"
|
8
|
+
|
9
|
+
# PATH for O.load
|
10
|
+
PATH = []
|
11
|
+
Error = Exception.new
|
12
|
+
LoadError = Exception.new(Error)
|
13
|
+
|
14
|
+
class O_Eval
|
15
|
+
def _data
|
16
|
+
_data = {}
|
17
|
+
self.instance_variables.each do |k|
|
18
|
+
key = k[1..-1].to_sym
|
19
|
+
value = self.instance_variable_get(k)
|
20
|
+
_data[key] = value
|
21
|
+
end
|
22
|
+
_data
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class << self
|
27
|
+
# convert <#Hash> to <#O>
|
28
|
+
#
|
29
|
+
# @param [hash] hash
|
30
|
+
# @return O
|
31
|
+
def from_hash hash
|
32
|
+
o = O.new
|
33
|
+
o._replace hash
|
34
|
+
end
|
35
|
+
|
36
|
+
# load a configuration file,
|
37
|
+
# support PATH, and '~/.gutenrc'
|
38
|
+
#
|
39
|
+
# first try name.rb, then use name
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# option = O.load("~/.gutenrc")
|
43
|
+
#
|
44
|
+
# option = O.load("/absolute/path/a.rb")
|
45
|
+
#
|
46
|
+
# O::Path << "/home"
|
47
|
+
# option = O.load("guten") #=> try guten.rb; then try guten
|
48
|
+
# option = O.load("guten.rb")
|
49
|
+
#
|
50
|
+
# @param [String] name
|
51
|
+
# @return [O]
|
52
|
+
def load name
|
53
|
+
path = nil
|
54
|
+
if name =~ /^~/
|
55
|
+
file = File.expand_path(name)
|
56
|
+
path = file if File.exists?(file)
|
57
|
+
elsif File.absolute_path(name) == name
|
58
|
+
path = name if File.exists?(name)
|
59
|
+
else
|
60
|
+
catch :break do
|
61
|
+
PATH.each do |p|
|
62
|
+
['.rb', ''].each {|ext|
|
63
|
+
file = File.join(p, name+ext)
|
64
|
+
if File.exists? file
|
65
|
+
path = file
|
66
|
+
throw :break
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
raise LoadError, "can't find file -- #{name}" unless path
|
74
|
+
|
75
|
+
eval_file path
|
76
|
+
end
|
77
|
+
|
78
|
+
# relative load a configuration file
|
79
|
+
# @see load
|
80
|
+
#
|
81
|
+
# @param [String] name
|
82
|
+
# @return [O] option
|
83
|
+
def relative_load name
|
84
|
+
pd caller if $TEST
|
85
|
+
a,file, line, method = caller[0].match(/^(.*):(\d+):.*`(.*)'$/).to_a
|
86
|
+
raise LoadError, "#{type} is called in #{file}" if file=~/\(.*\)/ # eval, etc.
|
87
|
+
|
88
|
+
file = File.readlink(file) if File.symlink?(file)
|
89
|
+
|
90
|
+
path = nil
|
91
|
+
[".rb", ""].each do |ext|
|
92
|
+
f = File.absolute_path(File.join(File.dirname(file), name+ext))
|
93
|
+
if File.exists?(f)
|
94
|
+
path = f
|
95
|
+
break
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
raise LoadError, "can't find file -- #{name}" unless path
|
100
|
+
|
101
|
+
eval_file path
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def eval_file path
|
106
|
+
content = File.open(path){|f| f.read}
|
107
|
+
o_eval = O_Eval.new
|
108
|
+
o_eval.instance_eval(content)
|
109
|
+
O.from_hash(o_eval._data)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
attr_reader :_data
|
115
|
+
|
116
|
+
def initialize default=nil, &blk
|
117
|
+
@_data = Hash.new(default)
|
118
|
+
if blk
|
119
|
+
o_eval = O_Eval.new
|
120
|
+
o_eval.instance_eval &blk
|
121
|
+
@_data.merge!(o_eval._data)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def []= key, value
|
126
|
+
@_data[key.to_sym] = value
|
127
|
+
end
|
128
|
+
|
129
|
+
def [] key
|
130
|
+
@_data[key.to_sym]
|
131
|
+
end
|
132
|
+
|
133
|
+
def + other
|
134
|
+
O.new(@_data, other._data)
|
135
|
+
end
|
136
|
+
|
137
|
+
def _replace data
|
138
|
+
case data
|
139
|
+
when Hash
|
140
|
+
@_data = data
|
141
|
+
when O
|
142
|
+
@_data = data._data
|
143
|
+
end
|
144
|
+
self
|
145
|
+
end
|
146
|
+
|
147
|
+
#
|
148
|
+
# _method goes to @_data.send(_method, ..)
|
149
|
+
# method? #=> !! @_data[:method]
|
150
|
+
# method #=> @_data[:method]
|
151
|
+
# method=value #=> @_data[:method]=value
|
152
|
+
#
|
153
|
+
def method_missing method, *args, &blk
|
154
|
+
if method =~ /(.*)=$/
|
155
|
+
@_data[$1.to_sym] = args[0]
|
156
|
+
elsif method =~ /(.*)\?$/
|
157
|
+
!! @_data[$1.to_sym]
|
158
|
+
elsif method =~ /^_(.*)/
|
159
|
+
method = $1.to_sym
|
160
|
+
args.map!{|arg| O===arg ? arg._data : arg}
|
161
|
+
rst = @_data.send(method, *args, &blk)
|
162
|
+
|
163
|
+
if [:merge!].include method
|
164
|
+
self
|
165
|
+
elsif [:merge].include method
|
166
|
+
O.new(rst)
|
167
|
+
end
|
168
|
+
else
|
169
|
+
@_data[method]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def inspect
|
174
|
+
rst = "<#O "
|
175
|
+
@_data.each do |k,v|
|
176
|
+
rst << "#{k}:#{v.inspect} "
|
177
|
+
end
|
178
|
+
rst << " >"
|
179
|
+
end
|
180
|
+
|
181
|
+
alias to_s inspect
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
module Kernel
|
186
|
+
def O default=nil, &blk
|
187
|
+
O.new(default, &blk)
|
188
|
+
end
|
189
|
+
end
|
data/o.gemspec
CHANGED
data/spec/data/rc.rb
ADDED
File without changes
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
class O::Parser
|
4
|
+
public :compile, :scan
|
5
|
+
end
|
6
|
+
|
7
|
+
describe O::Parser do
|
8
|
+
describe "#token" do
|
9
|
+
it "tests" do
|
10
|
+
content = <<EOF
|
11
|
+
a 1
|
12
|
+
b:
|
13
|
+
c {d: 1}
|
14
|
+
d 1
|
15
|
+
e:
|
16
|
+
f 1
|
17
|
+
EOF
|
18
|
+
content = <<EOF
|
19
|
+
a 1
|
20
|
+
b:
|
21
|
+
c {d: 1}
|
22
|
+
d 1
|
23
|
+
e:
|
24
|
+
f 1
|
25
|
+
g 1
|
26
|
+
EOF
|
27
|
+
|
28
|
+
parser = O::Parser.new content
|
29
|
+
parser.scan do |token, statement|
|
30
|
+
#pd token, statement
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#compile" do
|
36
|
+
it "tests" do
|
37
|
+
content = <<EOF
|
38
|
+
a:
|
39
|
+
b 1
|
40
|
+
EOF
|
41
|
+
|
42
|
+
content1 = <<EOF
|
43
|
+
a 1
|
44
|
+
b:
|
45
|
+
c {d: 1}
|
46
|
+
d 1
|
47
|
+
e:
|
48
|
+
f 1
|
49
|
+
g 1
|
50
|
+
EOF
|
51
|
+
parser = O::Parser.new content
|
52
|
+
parser.scan do |token, statement|
|
53
|
+
#pd token, statement
|
54
|
+
end
|
55
|
+
|
56
|
+
parser = O::Parser.new content
|
57
|
+
#puts parser.compile
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
it "has no effects to normal ruby code" do
|
62
|
+
content = <<EOF
|
63
|
+
a 1
|
64
|
+
b do
|
65
|
+
c {d: 1}
|
66
|
+
d 1
|
67
|
+
e do
|
68
|
+
f 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
EOF
|
72
|
+
parser = O::Parser.new content
|
73
|
+
parser.scan do |token, statement|
|
74
|
+
#pd token, statement
|
75
|
+
end
|
76
|
+
|
77
|
+
parser = O::Parser.new content
|
78
|
+
#puts parser.compile
|
79
|
+
end
|
80
|
+
|
81
|
+
it "has both yaml-style and ruby-style" do
|
82
|
+
content = <<EOF
|
83
|
+
a 1
|
84
|
+
b:
|
85
|
+
c {d: 1}
|
86
|
+
d 1
|
87
|
+
e do
|
88
|
+
f 1
|
89
|
+
end
|
90
|
+
EOF
|
91
|
+
parser = O::Parser.new content
|
92
|
+
parser.scan do |token, statement|
|
93
|
+
#pd token, statement
|
94
|
+
end
|
95
|
+
|
96
|
+
parser = O::Parser.new content
|
97
|
+
#puts parser.compile
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
describe ".compile" do
|
103
|
+
it "works" do
|
104
|
+
content = <<EOF
|
105
|
+
a:
|
106
|
+
b 1
|
107
|
+
EOF
|
108
|
+
right = <<EOF
|
109
|
+
a do
|
110
|
+
b 1
|
111
|
+
end\n
|
112
|
+
EOF
|
113
|
+
|
114
|
+
O::Parser.compile(content).should == right
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
end
|