drain 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +6 -2
  3. data/.travis.yml +10 -0
  4. data/.yardopts +6 -1
  5. data/Gemfile +7 -0
  6. data/LICENSE.txt +1 -1
  7. data/README.md +11 -5
  8. data/Rakefile +7 -12
  9. data/drain.gemspec +15 -5
  10. data/gemspec.yml +4 -3
  11. data/lib/dr.rb +1 -0
  12. data/lib/{drain → dr}/base.rb +0 -0
  13. data/lib/{drain → dr}/base/bool.rb +0 -0
  14. data/lib/dr/base/converter.rb +33 -0
  15. data/lib/{drain → dr}/base/encoding.rb +0 -0
  16. data/lib/dr/base/eruby.rb +284 -0
  17. data/lib/{drain → dr}/base/functional.rb +2 -2
  18. data/lib/dr/base/graph.rb +378 -0
  19. data/lib/dr/base/utils.rb +28 -0
  20. data/lib/dr/parse.rb +1 -0
  21. data/lib/dr/parse/simple_parser.rb +70 -0
  22. data/lib/{drain → dr}/parse/time_parse.rb +0 -0
  23. data/lib/dr/ruby_ext.rb +1 -0
  24. data/lib/dr/ruby_ext/core_ext.rb +7 -0
  25. data/lib/{drain/ruby_ext/core_ext.rb → dr/ruby_ext/core_modules.rb} +67 -27
  26. data/lib/{drain → dr}/ruby_ext/meta_ext.rb +57 -30
  27. data/lib/dr/tools.rb +1 -0
  28. data/lib/{drain → dr}/tools/gtk.rb +0 -0
  29. data/lib/dr/version.rb +4 -0
  30. data/lib/drain.rb +2 -1
  31. data/test/helper.rb +12 -1
  32. data/test/test_converter.rb +42 -0
  33. data/test/test_core_ext.rb +116 -0
  34. data/test/test_graph.rb +126 -0
  35. data/test/test_meta.rb +65 -0
  36. data/test/test_simple_parser.rb +41 -0
  37. metadata +45 -21
  38. data/.document +0 -3
  39. data/lib/drain/base/eruby.rb +0 -28
  40. data/lib/drain/base/graph.rb +0 -213
  41. data/lib/drain/parse.rb +0 -5
  42. data/lib/drain/parse/simple_parser.rb +0 -61
  43. data/lib/drain/ruby_ext.rb +0 -5
  44. data/lib/drain/tools.rb +0 -5
  45. data/lib/drain/tools/git.rb +0 -116
  46. 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
@@ -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