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