drain 0.1.0 → 0.2.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.
- 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
|