drain 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +6 -2
- data/.travis.yml +10 -0
- data/.yardopts +6 -1
- data/Gemfile +7 -0
- data/LICENSE.txt +1 -1
- data/README.md +11 -5
- data/Rakefile +7 -12
- data/drain.gemspec +15 -5
- data/gemspec.yml +4 -3
- data/lib/dr.rb +1 -0
- data/lib/{drain → dr}/base.rb +0 -0
- data/lib/{drain → dr}/base/bool.rb +0 -0
- data/lib/dr/base/converter.rb +33 -0
- data/lib/{drain → dr}/base/encoding.rb +0 -0
- data/lib/dr/base/eruby.rb +284 -0
- data/lib/{drain → dr}/base/functional.rb +2 -2
- data/lib/dr/base/graph.rb +378 -0
- data/lib/dr/base/utils.rb +28 -0
- data/lib/dr/parse.rb +1 -0
- data/lib/dr/parse/simple_parser.rb +70 -0
- data/lib/{drain → dr}/parse/time_parse.rb +0 -0
- data/lib/dr/ruby_ext.rb +1 -0
- data/lib/dr/ruby_ext/core_ext.rb +7 -0
- data/lib/{drain/ruby_ext/core_ext.rb → dr/ruby_ext/core_modules.rb} +67 -27
- data/lib/{drain → dr}/ruby_ext/meta_ext.rb +57 -30
- data/lib/dr/tools.rb +1 -0
- data/lib/{drain → dr}/tools/gtk.rb +0 -0
- data/lib/dr/version.rb +4 -0
- data/lib/drain.rb +2 -1
- data/test/helper.rb +12 -1
- data/test/test_converter.rb +42 -0
- data/test/test_core_ext.rb +116 -0
- data/test/test_graph.rb +126 -0
- data/test/test_meta.rb +65 -0
- data/test/test_simple_parser.rb +41 -0
- metadata +45 -21
- data/.document +0 -3
- data/lib/drain/base/eruby.rb +0 -28
- data/lib/drain/base/graph.rb +0 -213
- data/lib/drain/parse.rb +0 -5
- data/lib/drain/parse/simple_parser.rb +0 -61
- data/lib/drain/ruby_ext.rb +0 -5
- data/lib/drain/tools.rb +0 -5
- data/lib/drain/tools/git.rb +0 -116
- data/lib/drain/version.rb +0 -4
@@ -1,8 +1,8 @@
|
|
1
1
|
module DR
|
2
2
|
module Lambda
|
3
3
|
extend self
|
4
|
-
#standard ruby: col.map(&f)
|
5
|
-
#Here: Lambda.map(f,col1,col2,...)
|
4
|
+
#standard ruby: col.map(&f) for one variable
|
5
|
+
#Here: Lambda.map(f,col1,col2,...) for several variables
|
6
6
|
#Other implementation:
|
7
7
|
#(shift cols).zip(cols).map {|a| f.call(*a)}
|
8
8
|
#but our implementation stops as soon as a collection is empty
|
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'set'
|
2
|
+
#Originally inspired by depgraph: https://github.com/dcadenas/depgraph
|
3
|
+
|
4
|
+
module DR
|
5
|
+
class Node
|
6
|
+
include Enumerable
|
7
|
+
attr_reader :graph
|
8
|
+
attr_accessor :name, :attributes, :parents, :children
|
9
|
+
def initialize(name, attributes: {}, graph: nil)
|
10
|
+
@name = name.to_s
|
11
|
+
@children = []
|
12
|
+
@parents = []
|
13
|
+
@attributes = attributes
|
14
|
+
@graph=graph
|
15
|
+
graph.nodes << self if @graph
|
16
|
+
end
|
17
|
+
def each(&b)
|
18
|
+
@children.each(&b)
|
19
|
+
end
|
20
|
+
def <=>(other)
|
21
|
+
return @name <=> other.name
|
22
|
+
end
|
23
|
+
|
24
|
+
def ancestors
|
25
|
+
self.graph.ancestors(self, ourselves: false)
|
26
|
+
end
|
27
|
+
def descendants
|
28
|
+
self.graph.descendants(self, ourselves: false)
|
29
|
+
end
|
30
|
+
|
31
|
+
NodeError=Class.new(RuntimeError)
|
32
|
+
def check_node(node)
|
33
|
+
raise NodeError.new("wrong class: #{node.class}") unless node.is_a?(Node)
|
34
|
+
raise NodeError.new("wrong graph: #{node.graph}") if self.graph != node.graph
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_attributes(new_attr)
|
38
|
+
@attributes.merge!(new_attr)
|
39
|
+
end
|
40
|
+
|
41
|
+
#self.add_child(ploum) marks ploum as a child of self (ie ploum depends on self)
|
42
|
+
def add_child(*nodes)
|
43
|
+
nodes.each do |node|
|
44
|
+
check_node(node)
|
45
|
+
if not @children.include?(node)
|
46
|
+
@children << node
|
47
|
+
node.parents << self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
def rm_child(*nodes)
|
52
|
+
nodes.each do |node|
|
53
|
+
check_node(node)
|
54
|
+
if @children.include?(node)
|
55
|
+
@children.delete(node)
|
56
|
+
node.parents.delete(self)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
def add_parent(*nodes)
|
61
|
+
nodes.each do |node|
|
62
|
+
check_node(node)
|
63
|
+
if not @parents.include?(node)
|
64
|
+
@parents << node
|
65
|
+
node.children << self
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def rm_parent(*nodes)
|
70
|
+
nodes.each do |node|
|
71
|
+
check_node(node)
|
72
|
+
if @parents.include?(node)
|
73
|
+
@parents.delete(node)
|
74
|
+
node.children.delete(self)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
STEP = 4
|
80
|
+
def to_s(show_attr: true)
|
81
|
+
@name + (show_attr && ! attributes.empty? ? " #{attributes}" : "")
|
82
|
+
end
|
83
|
+
def inspect
|
84
|
+
"#{self.class}: #{to_s(show_attr: true)}"+(graph.nil? ? "" : " (#{graph})")
|
85
|
+
end
|
86
|
+
def to_graph(indent_level: 0, show_attr: true)
|
87
|
+
sout = ""
|
88
|
+
margin = ''
|
89
|
+
0.upto(indent_level/STEP-1) { |p| margin += (p==0 ? ' ' : '|') + ' '*(STEP - 1) }
|
90
|
+
margin += '|' + '-'*(STEP - 2)
|
91
|
+
sout += margin + "#{to_s(show_attr: show_attr)}\n"
|
92
|
+
@children.each do |child|
|
93
|
+
sout += child.to_graph(indent_level: indent_level+STEP, show_attr: show_attr)
|
94
|
+
end
|
95
|
+
return sout
|
96
|
+
end
|
97
|
+
def to_dot
|
98
|
+
sout=["\""+name+"\""]
|
99
|
+
@children.each do |child|
|
100
|
+
sout.push "\"#{@name}\" -> \"#{child.name}\""
|
101
|
+
sout += child.to_dot
|
102
|
+
end
|
103
|
+
return sout
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Graph
|
108
|
+
attr_accessor :nodes
|
109
|
+
include Enumerable
|
110
|
+
def initialize(*nodes, attributes: {}, infos: nil)
|
111
|
+
@nodes=[]
|
112
|
+
build(*nodes, attributes: {}, infos: infos)
|
113
|
+
end
|
114
|
+
def each(&b)
|
115
|
+
@nodes.each(&b)
|
116
|
+
end
|
117
|
+
|
118
|
+
def clone
|
119
|
+
Graph.new.build(*all, recursive: false)
|
120
|
+
end
|
121
|
+
|
122
|
+
def to_a
|
123
|
+
return @nodes
|
124
|
+
end
|
125
|
+
def to_hash(methods: [:children,:parents,:attributes], compact: true, recursive: true)
|
126
|
+
require 'dr/base/converter'
|
127
|
+
Converter.to_hash(@nodes, methods: methods, recursive: recursive, compact: compact)
|
128
|
+
end
|
129
|
+
|
130
|
+
def [](node)
|
131
|
+
if node.is_a?(Node) and node.graph == self
|
132
|
+
return node
|
133
|
+
elsif node.is_a?(Node)
|
134
|
+
name=node.name
|
135
|
+
else
|
136
|
+
name=node
|
137
|
+
end
|
138
|
+
@nodes.find {|n| n.name == name}
|
139
|
+
end
|
140
|
+
def to_h
|
141
|
+
h=to_hash(methods: [:children])
|
142
|
+
Hash[h.map {|k,v| [k.name, v.map(&:name)]}]
|
143
|
+
end
|
144
|
+
alias to_children to_h
|
145
|
+
|
146
|
+
def to_children
|
147
|
+
require 'dr/base/converter'
|
148
|
+
Converter.to_hash(@nodes, methods:[:children], recursive: true, compact: true).map { |k,v| [k.name, v.map(&:name)]}.to_h
|
149
|
+
end
|
150
|
+
|
151
|
+
def inspect
|
152
|
+
"#{self.class}: #{map {|x| x.to_s}}"
|
153
|
+
end
|
154
|
+
|
155
|
+
#construct a node (without edges)
|
156
|
+
def new_node(node,**attributes)
|
157
|
+
n=case node
|
158
|
+
when Node
|
159
|
+
node.graph == self ? node : new_node(node.name, **node.attributes)
|
160
|
+
else
|
161
|
+
@nodes.find {|n| n.name == node} || Node.new(node, graph: self)
|
162
|
+
end
|
163
|
+
n.update_attributes(attributes)
|
164
|
+
n
|
165
|
+
end
|
166
|
+
|
167
|
+
# add a node (and its edges, recursively by default)
|
168
|
+
# TODO in case of a loop this is currently non terminating when recursive
|
169
|
+
# we would need to keep track of handled nodes
|
170
|
+
def add_node(node, children: [], parents: [], attributes: {}, infos: nil, recursive: true)
|
171
|
+
if infos.respond_to?(:call)
|
172
|
+
info=infos.call(node)||{}
|
173
|
+
children.concat([*info[:children]])
|
174
|
+
parents.concat([*info[:parents]])
|
175
|
+
attributes.merge!(info[:attributes]||{})
|
176
|
+
end
|
177
|
+
if node.is_a?(Node) and node.graph != self
|
178
|
+
children.concat(node.children)
|
179
|
+
parents.concat(node.parents)
|
180
|
+
end
|
181
|
+
graph_node=new_node(node,**attributes)
|
182
|
+
if recursive
|
183
|
+
graph_node.add_child(*children.map { |child| add_node(child) })
|
184
|
+
graph_node.add_parent(*parents.map { |parent| add_node(parent) })
|
185
|
+
else
|
186
|
+
#just add the children
|
187
|
+
graph_node.add_child(*children.map { |child| new_node(child) })
|
188
|
+
end
|
189
|
+
graph_node
|
190
|
+
end
|
191
|
+
|
192
|
+
#build from a list of nodes or hash
|
193
|
+
def build(*nodes, attributes: {}, infos: nil, recursive: true)
|
194
|
+
nodes.each do |node|
|
195
|
+
case node
|
196
|
+
when Hash
|
197
|
+
node.each do |name,children|
|
198
|
+
add_node(name,children: [*children], attributes: attributes, infos: infos, recursive: recursive)
|
199
|
+
end
|
200
|
+
else
|
201
|
+
add_node(node, attributes: attributes, infos: infos, recursive: recursive)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
self
|
205
|
+
end
|
206
|
+
|
207
|
+
def all
|
208
|
+
@nodes.sort
|
209
|
+
end
|
210
|
+
def roots
|
211
|
+
@nodes.select{ |n| n.parents.length == 0}.sort
|
212
|
+
end
|
213
|
+
def bottom
|
214
|
+
@nodes.select{ |n| n.children.length == 0}.sort
|
215
|
+
end
|
216
|
+
|
217
|
+
def |(graph)
|
218
|
+
build(*graph.all, recursive: false)
|
219
|
+
end
|
220
|
+
def +(graph)
|
221
|
+
clone.|(graph)
|
222
|
+
end
|
223
|
+
|
224
|
+
def dump(mode: :graph, nodes_list: :roots, show_attr: true, **unused)
|
225
|
+
n=case nodes_list
|
226
|
+
when :roots; roots
|
227
|
+
when :all; all
|
228
|
+
when Symbol; nodes.select {|n| n.attributes[:nodes_list]}
|
229
|
+
else nodes_list.to_a
|
230
|
+
end
|
231
|
+
sout = ""
|
232
|
+
case mode
|
233
|
+
when :graph; n.each do |node| sout+=node.to_graph(show_attr: show_attr) end
|
234
|
+
when :list; n.each do |i| sout+="- #{i}\n" end
|
235
|
+
when :dot;
|
236
|
+
sout+="digraph gems {\n"
|
237
|
+
sout+=n.map {|node| node.to_dot}.inject(:+).uniq!.join("\n")
|
238
|
+
sout+="}\n"
|
239
|
+
end
|
240
|
+
return sout
|
241
|
+
end
|
242
|
+
|
243
|
+
def to_nodes(*nodes)
|
244
|
+
nodes.map {|n| self[n]}.compact
|
245
|
+
end
|
246
|
+
|
247
|
+
#return the connected set containing nodes (following the direction
|
248
|
+
#given)
|
249
|
+
def connected(*nodes, down:true, up:true, ourselves: true)
|
250
|
+
nodes=to_nodes(*nodes)
|
251
|
+
onodes=nodes.dup
|
252
|
+
found=[]
|
253
|
+
while !nodes.empty?
|
254
|
+
node=nodes.shift
|
255
|
+
found<<node
|
256
|
+
new_nodes=Set[node]
|
257
|
+
new_nodes.merge(node.children) if down
|
258
|
+
new_nodes.merge(node.parents) if up
|
259
|
+
new_nodes-=(found+nodes)
|
260
|
+
nodes.concat(new_nodes.to_a)
|
261
|
+
end
|
262
|
+
found-=onodes if !ourselves
|
263
|
+
return found
|
264
|
+
end
|
265
|
+
#return all parents
|
266
|
+
def ancestors(*nodes, ourselves: true)
|
267
|
+
nodes=to_nodes(*nodes)
|
268
|
+
connected(*nodes, up:true, down:false, ourselves: ourselves)
|
269
|
+
end
|
270
|
+
#return all childern
|
271
|
+
def descendants(*nodes, ourselves: true)
|
272
|
+
nodes=to_nodes(*nodes)
|
273
|
+
connected(*nodes, up:false, down:true, ourselves: ourselves)
|
274
|
+
end
|
275
|
+
|
276
|
+
#from a list of nodes, return all nodes that are not descendants of
|
277
|
+
#other nodes in the graph
|
278
|
+
#needed: the nodes whose descendants we keep, by default the complement
|
279
|
+
#of nodes
|
280
|
+
def unneeded(*nodes, needed: nil)
|
281
|
+
nodes=to_nodes(*nodes)
|
282
|
+
if needed
|
283
|
+
needed=to_nodes(needed)
|
284
|
+
else
|
285
|
+
needed=@nodes-nodes
|
286
|
+
end
|
287
|
+
unneeded=[]
|
288
|
+
nodes.each do |node|
|
289
|
+
unneeded << node if (ancestors(node) & needed).empty?
|
290
|
+
end
|
291
|
+
unneeded
|
292
|
+
end
|
293
|
+
#like unneeded(descendants(*nodes))
|
294
|
+
#return all dependencies that are not needed by any more other nodes (except
|
295
|
+
#the ones we are removing)
|
296
|
+
#If some dependencies should be kept (think manual install), add them
|
297
|
+
#to the needed parameter
|
298
|
+
def unneeded_descendants(*nodes, needed:[])
|
299
|
+
nodes=to_nodes(*nodes)
|
300
|
+
needed=to_nodes(*needed)
|
301
|
+
needed-=nodes #nodes to delete are in priority
|
302
|
+
deps=descendants(*nodes)
|
303
|
+
deps-=needed #but for children nodes, needed nodes are in priority
|
304
|
+
unneeded(*deps)
|
305
|
+
end
|
306
|
+
#So to implement the equivalent of pacman -Rc packages
|
307
|
+
#it suffices to add the ancestors of packages
|
308
|
+
#For pacman -Rs, this is exactly unneeded_descendants
|
309
|
+
#and pacman -Rcs would be unneeded_descendants(ancestors)
|
310
|
+
#finally to clean all unneeded packages (provided we have a list of
|
311
|
+
#packages 'tokeep' to keep), either use unneeded(@nodes-tokeep)
|
312
|
+
#or unneeded_descendants(roots, needed:tokeep)
|
313
|
+
|
314
|
+
#return the subgraph containing all the nodes passed as parameters,
|
315
|
+
#and the complementary graph. The union of both may not be the full
|
316
|
+
#graph [missing edges] in case the components are connected
|
317
|
+
def subgraph(*nodes, complement: false)
|
318
|
+
nodes=to_nodes(*nodes)
|
319
|
+
subgraph=Graph.new()
|
320
|
+
compgraph=Graph.new() if complement
|
321
|
+
@nodes.each do |node|
|
322
|
+
if nodes.include?(node)
|
323
|
+
n=subgraph.new_node(node)
|
324
|
+
node.children.each do |c|
|
325
|
+
n.add_child(subgraph.new_node(c)) if nodes.include?(c)
|
326
|
+
end
|
327
|
+
else
|
328
|
+
if complement
|
329
|
+
n=compgraph.new_node(node)
|
330
|
+
node.children.each do |c|
|
331
|
+
n.add_child(compgraph.new_node(c)) unless nodes.include?(c)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
complement ? (return subgraph, compgraph) : (return subgraph)
|
337
|
+
end
|
338
|
+
|
339
|
+
def -(other)
|
340
|
+
if other.is_a? Graph
|
341
|
+
#in this case we want to remove the edges
|
342
|
+
other.each do |n|
|
343
|
+
self[n].rm_child(*n.children)
|
344
|
+
end
|
345
|
+
else
|
346
|
+
#we remove a list of nodes
|
347
|
+
nodes=@nodes-to_nodes(*other)
|
348
|
+
subgraph(*nodes)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
#Graph.build(nodes,&b) allows to build a graph using &b
|
353
|
+
#if recursive is true each time we get new nodes we add them to the graph
|
354
|
+
#otherwise just run once
|
355
|
+
#if recursive=0 we even restrict the graph to the current nodes
|
356
|
+
#Note: to construct a graph from one node to a list it suffice to call
|
357
|
+
#nodes.map(&b).reduce(:|)
|
358
|
+
def self.build(nodes, recursive: true)
|
359
|
+
g=yield(*nodes)
|
360
|
+
g=Graph.new(g) unless g.is_a?(Graph)
|
361
|
+
new_nodes=g.nodes.map(&:name)-nodes
|
362
|
+
if recursive==0 and !new_nodes.empty?
|
363
|
+
g-(new_nodes)
|
364
|
+
elsif recursive
|
365
|
+
while !new_nodes.empty?
|
366
|
+
g2=yield(*new_nodes)
|
367
|
+
g2=Graph.new(g2) unless g2.is_a?(Graph)
|
368
|
+
g|g2
|
369
|
+
nodes=nodes.concat(new_nodes)
|
370
|
+
new_nodesg.nodes.map(&:name)-nodes
|
371
|
+
end
|
372
|
+
g
|
373
|
+
else
|
374
|
+
g
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DR
|
2
|
+
module Utils
|
3
|
+
extend self
|
4
|
+
def pretty_print(string, format: nil, pretty: false)
|
5
|
+
case format.to_s
|
6
|
+
when "json"
|
7
|
+
require 'json'
|
8
|
+
return pretty_print(string.to_json)
|
9
|
+
when "yaml"
|
10
|
+
require "yaml"
|
11
|
+
return pretty_print(string.to_yaml)
|
12
|
+
end
|
13
|
+
if pretty.to_s=="color"
|
14
|
+
begin
|
15
|
+
require 'ap'
|
16
|
+
ap string
|
17
|
+
rescue LoadError,NameError
|
18
|
+
pretty_print(string,pretty:true)
|
19
|
+
end
|
20
|
+
elsif pretty
|
21
|
+
require 'pp'
|
22
|
+
pp string
|
23
|
+
else
|
24
|
+
puts string
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/dr/parse.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lib/dr/base.rb
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module DR
|
2
|
+
#utilities to parse some strings into name values
|
3
|
+
module SimpleParser
|
4
|
+
extend self
|
5
|
+
|
6
|
+
#takes a string 'name:value' and return name and value
|
7
|
+
#can specify a default value; if the default is true we match
|
8
|
+
#no-name as name:false
|
9
|
+
def parse_namevalue(nameval, sep: ':', default: nil, symbolize: true)
|
10
|
+
name,*val=nameval.split(sep)
|
11
|
+
if val.empty?
|
12
|
+
value=default
|
13
|
+
if default == true
|
14
|
+
#special case where if name begins by no- we return false
|
15
|
+
name.to_s.match(/^no-(.*)$/) do |m|
|
16
|
+
name=m[1]
|
17
|
+
value=false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
else
|
21
|
+
value=val.join(sep)
|
22
|
+
end
|
23
|
+
name=name.to_sym if symbolize
|
24
|
+
return name,value
|
25
|
+
end
|
26
|
+
|
27
|
+
#takes a string as "name:value!option1=ploum!option2=plam,name2:value2!!globalopt=plim,globalopt2=plam!!globalopt3=plom,globalopt4=plim"
|
28
|
+
#and return the hash
|
29
|
+
#{values: {name: value, name2: value2},
|
30
|
+
# local_opts: {name: {option1:ploum,option2:plam}},
|
31
|
+
# global_opts: {globalopt: plim, globalopt2: plam},
|
32
|
+
# opts: {name: {option1:ploum,option2:plam,globalopt: plim, globalopt2: plam}, name2:{{globalopt: plim, globalopt2: plam}}}
|
33
|
+
#
|
34
|
+
#Algo: split on 'args!!globopts'
|
35
|
+
# globopts are split on ',' or '!!'
|
36
|
+
# args are split on ',' into parameters
|
37
|
+
# parameters are split on 'args!local_opts'
|
38
|
+
# args are split on 'name:value' using parse_namevalue
|
39
|
+
# local_opts are splits on 'opt=value" using parse_namevalue
|
40
|
+
def parse_string(s, arg_split:',', valuesep: ':', default: nil,
|
41
|
+
opt_valuesep: '=', opt_default: true, opts_split: '!',
|
42
|
+
globalopts_separator: '!!', globopts_split: arg_split,
|
43
|
+
globalopts_valuesep: opt_valuesep, globalopts_default: opt_default)
|
44
|
+
r={values: {}, local_opts: {}, global_opts: {}, opts: {}}
|
45
|
+
args,*globopts=s.split(globalopts_separator)
|
46
|
+
globopts.map {|g| g.split(globopts_split)}.flatten.each do |g|
|
47
|
+
name,value=parse_namevalue(g, sep: globalopts_valuesep, default: globalopts_default)
|
48
|
+
r[:global_opts][name]=value
|
49
|
+
end
|
50
|
+
args.split(arg_split).each do |arg|
|
51
|
+
arg,*localopts=arg.split(opts_split)
|
52
|
+
name,value=parse_namevalue(arg, sep: valuesep, default: default)
|
53
|
+
r[:values][name]=value
|
54
|
+
r[:local_opts][name]={}
|
55
|
+
localopts.each do |o|
|
56
|
+
oname,ovalue=parse_namevalue(o, sep: opt_valuesep, default: opt_default)
|
57
|
+
r[:local_opts][name][oname]=ovalue
|
58
|
+
end
|
59
|
+
r[:local_opts].each do |name,hash|
|
60
|
+
r[:opts][name]=r[:local_opts][name].dup
|
61
|
+
r[:global_opts].each do |k,v|
|
62
|
+
r[:opts][name][k]||=v
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
return r
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|