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