drain 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4801bbbd9081149ae66059ccabf69e10a6002c16
4
+ data.tar.gz: bd24d077d42a458160c61e985a8f3e2e1d14f323
5
+ SHA512:
6
+ metadata.gz: f6c18209c458007657d6bc26e79d34f64a50de127c4db0b4d1dd525f3de950eb3d2cf8543a816aaddb4f322fc146e7bde45645f0af0bc95dd01360afe4fd3015
7
+ data.tar.gz: 0f64e58698354e4b75385aaa8127eedad9d55d6413fe141f3dea621eeacffb94cc531d3f80ea892ab16465134e28d9879c10884a835f43376de8bc609a615947
@@ -0,0 +1,3 @@
1
+ -
2
+ ChangeLog.md
3
+ LICENSE.txt
@@ -0,0 +1,2 @@
1
+ doc/
2
+ pkg/
@@ -0,0 +1 @@
1
+ --markup markdown -M kramdown --title "drain Documentation" --protected
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2015-02-24
2
+
3
+ * Initial release:
4
+
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2015 Damien Robert
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,25 @@
1
+ # drain
2
+
3
+ * [Homepage](https://github.com/DamienRobert/drain#readme)
4
+ * [Gems]("https://rubygems.org/gems/drain)
5
+ * [Issues](https://github.com/DamienRobert/drain/issues)
6
+ * [Documentation](http://rubydoc.info/gems/drain/frames)
7
+ * [Email](mailto:Damien.Olivier.Robert+gems at gmail.com)
8
+
9
+ ## Description
10
+
11
+ Drain is a small set of libraries that I use in my other gems.
12
+ The Api is far from stable yet, so use at your own risk!
13
+
14
+ ## Install
15
+
16
+ $ gem install drain
17
+
18
+ ## Copyright
19
+
20
+ Copyright (c) 2015 Damien Robert
21
+
22
+ MIT License. See {file:LICENSE.txt} for details.
23
+
24
+ Some of the code is inspired by other project, in general I give proper
25
+ Acknowledgement in the corresponding file.
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ gem 'rubygems-tasks', '~> 0.2'
8
+ require 'rubygems/tasks'
9
+
10
+ Gem::Tasks.new
11
+ rescue LoadError => e
12
+ warn e.message
13
+ warn "Run `gem install rubygems-tasks` to install Gem::Tasks."
14
+ end
15
+
16
+ require 'rake/testtask'
17
+
18
+ Rake::TestTask.new do |test|
19
+ test.libs << 'test'
20
+ test.pattern = 'test/**/test_*.rb'
21
+ test.verbose = true
22
+ end
23
+
24
+ begin
25
+ gem 'yard', '~> 0.8'
26
+ require 'yard'
27
+
28
+ YARD::Rake::YardocTask.new
29
+ rescue LoadError => e
30
+ task :yard do
31
+ abort "Please run `gem install yard` to install YARD."
32
+ end
33
+ end
34
+ task :doc => :yard
@@ -0,0 +1,59 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gem|
6
+ gemspec = YAML.load_file('gemspec.yml')
7
+
8
+ gem.name = gemspec.fetch('name')
9
+ gem.version = gemspec.fetch('version') do
10
+ lib_dir = File.join(File.dirname(__FILE__),'lib')
11
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
12
+
13
+ require 'drain/version'
14
+ Drain::VERSION
15
+ end
16
+
17
+ gem.summary = gemspec['summary']
18
+ gem.description = gemspec['description']
19
+ gem.licenses = Array(gemspec['license'])
20
+ gem.authors = Array(gemspec['authors'])
21
+ gem.email = gemspec['email']
22
+ gem.homepage = gemspec['homepage']
23
+
24
+ glob = lambda { |patterns| gem.files & Dir[*patterns] }
25
+
26
+ gem.files = `git ls-files`.split($/)
27
+ gem.files = glob[gemspec['files']] if gemspec['files']
28
+
29
+ gem.executables = gemspec.fetch('executables') do
30
+ glob['bin/*'].map { |path| File.basename(path) }
31
+ end
32
+ gem.default_executable = gem.executables.first if Gem::VERSION < '1.7.'
33
+
34
+ gem.extensions = glob[gemspec['extensions'] || 'ext/**/extconf.rb']
35
+ gem.extra_rdoc_files = glob[gemspec['extra_doc_files'] || '*.{txt,md}']
36
+
37
+ gem.require_paths = Array(gemspec.fetch('require_paths') {
38
+ %w[ext lib].select { |dir| File.directory?(dir) }
39
+ })
40
+
41
+ gem.requirements = gemspec['requirements']
42
+ gem.required_ruby_version = gemspec['required_ruby_version']
43
+ gem.required_rubygems_version = gemspec['required_rubygems_version']
44
+ gem.post_install_message = gemspec['post_install_message']
45
+
46
+ split = lambda { |string| string.split(/,\s*/) }
47
+
48
+ if gemspec['dependencies']
49
+ gemspec['dependencies'].each do |name,versions|
50
+ gem.add_dependency(name,split[versions])
51
+ end
52
+ end
53
+
54
+ if gemspec['development_dependencies']
55
+ gemspec['development_dependencies'].each do |name,versions|
56
+ gem.add_development_dependency(name,split[versions])
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ name: drain
2
+ summary: "Use a drain for a dryer ruby!"
3
+ description: |
4
+ Drain is a small set of libraries that I use in my other gems.
5
+ license: MIT
6
+ authors: Damien Robert
7
+ email: Damien.Olivier.Robert+gems@gmail.com
8
+ homepage: https://github.com/DamienRobert/drain#readme
9
+
10
+ development_dependencies:
11
+ minitest: ~> 5.0
12
+ rubygems-tasks: ~> 0.2
13
+ yard: ~> 0.8
@@ -0,0 +1 @@
1
+ require 'drain/version'
@@ -0,0 +1,5 @@
1
+ #for the filename ploum.rb, load all ploum/*.rb files
2
+ dir=File.expand_path(File.basename(__FILE__).chomp('.rb'), File.dirname(__FILE__))
3
+ Dir.glob(File.expand_path('*.rb',dir)) do |file|
4
+ require file
5
+ end
@@ -0,0 +1,25 @@
1
+ module DR
2
+ module Bool
3
+ extend(self)
4
+ def to_bool(el, default=nil)
5
+ case el
6
+ when String
7
+ string=el.chomp
8
+ return true if string =~ (/(true|t|yes|y|1)$/i)
9
+ return false if string.empty? || string =~ (/(false|f|no|n|0)$/i)
10
+ when Fixnum
11
+ return ! (el == 0)
12
+ when Process::Status
13
+ exitstatus=el.exitstatus
14
+ return exitstatus == 0
15
+ else
16
+ return true if el == true
17
+ return false if el == false
18
+ #we don't return !!el because we don't want nil to be false but to
19
+ #give an error
20
+ end
21
+ return default unless default.nil?
22
+ raise ArgumentError.new("Invalid value for Boolean: \"#{el}\"")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,43 @@
1
+ require 'iconv' unless String.method_defined?(:encode)
2
+ #cf http://stackoverflow.com/questions/2982677/ruby-1-9-invalid-byte-sequence-in-utf-8
3
+ module DR
4
+ module Encoding
5
+ module_function
6
+ #if a mostly utf8 has some mixed in latin1 characters, replace the
7
+ #invalid characters
8
+ def fix_utf8(s=nil)
9
+ s=self if s.nil? #if we are included
10
+ if String.method_defined?(:scrub)
11
+ #Ruby 2.1
12
+ #cf http://ruby-doc.org/core-2.1.0/String.html#method-i-scrub
13
+ return s.scrub {|bytes| '<'+bytes.unpack('H*')[0]+'>' }
14
+ else
15
+ return DR::Encoding.to_utf8(s)
16
+ end
17
+ end
18
+
19
+ def to_utf8(s=nil,from:nil)
20
+ s=self if s.nil? #if we are included
21
+ from=s.encoding if from.nil?
22
+ if String.method_defined?(:encode)
23
+ #Ruby 1.9
24
+ return s.encode('UTF-8',from, :invalid => :replace, :undef => :replace,
25
+ :fallback => Proc.new { |bytes| '<'+bytes.unpack('H*')[0]+'>' }
26
+ )
27
+ else
28
+ #Ruby 1.8
29
+ ic = Iconv.new(from, 'UTF-8//IGNORE')
30
+ return ic.iconv(s)
31
+ end
32
+ end
33
+
34
+ #assume ruby>=1.9 here
35
+ def to_utf8!(s=nil,from:nil)
36
+ s=self if s.nil? #if we are included
37
+ from=s.encoding if from.nil?
38
+ return s.encode!('UTF-8',from, :invalid => :replace, :undef => :replace,
39
+ :fallback => Proc.new { |bytes| '<'+bytes.unpack('H*')[0]+'>' }
40
+ )
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,28 @@
1
+ module DR
2
+ module Eruby
3
+ begin
4
+ require 'erubis'
5
+ Erb=::Erubis::Eruby
6
+ rescue LoadError
7
+ require 'erb'
8
+ Erb=::ERB
9
+ end
10
+ def erb_include(template, opt={})
11
+ opt={bind: binding}.merge(opt)
12
+ file=File.expand_path(template)
13
+ Dir.chdir(File.dirname(file)) do |cwd|
14
+ erb = Erb.new(File.read(file))
15
+ #if context is not empty, then we probably want to evaluate
16
+ if opt[:evaluate] or opt[:context]
17
+ r=erb.evaluate(opt[:context])
18
+ else
19
+ r=erb.result(opt[:bind])
20
+ end
21
+ #if using erubis, it is better to invoke the template in <%= =%> than
22
+ #to use chop=true
23
+ r=r.chomp if opt[:chomp]
24
+ return r
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ module DR
2
+ module Lambda
3
+ extend self
4
+ #standard ruby: col.map(&f)
5
+ #Here: Lambda.map(f,col1,col2,...)
6
+ #Other implementation:
7
+ #(shift cols).zip(cols).map {|a| f.call(*a)}
8
+ #but our implementation stops as soon as a collection is empty
9
+ #whereas the zip implementation use the length of the first collection
10
+ #and pads with nil
11
+ def map(f,*cols)
12
+ cols=cols.map(&:each)
13
+ r=[]
14
+ loop do
15
+ r<<f.call(*cols.map(&:next))
16
+ end
17
+ r
18
+ rescue StopIteration
19
+ end
20
+
21
+ #like map but return an enumerator
22
+ def enum_map(f,*cols)
23
+ cols=cols.map(&:each)
24
+ Enumerator.new do |y|
25
+ loop do
26
+ y<<f.call(*cols.map(&:next))
27
+ end
28
+ end
29
+ end
30
+
31
+ #compose a list of functions
32
+ def compose(*f)
33
+ f.reverse!
34
+ first=f.shift
35
+ return lambda do |*args,&b|
36
+ v=first.call(*args,&b)
37
+ f.reduce(v) {|v,fun| fun.call(v)}
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,213 @@
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: nil, graph: nil)
10
+ @name = name
11
+ @children = []
12
+ @parents = []
13
+ @attributes = attributes
14
+ @graph=graph
15
+ graph.nodes << self if @graph
16
+ end
17
+ def each
18
+ @children.each
19
+ end
20
+ def <=>(other)
21
+ return @name <=> other.name
22
+ end
23
+ #self.add_child(ploum) marks ploum as a child of self (ie ploum depends on self)
24
+ def add_child(*nodes)
25
+ nodes.each do |node|
26
+ if not @children.include?(node)
27
+ @children << node
28
+ node.parents << self
29
+ end
30
+ end
31
+ end
32
+ def rm_child(*nodes)
33
+ nodes.each do |node|
34
+ if @children.include?(node)
35
+ @children.delete(node)
36
+ node.parents.delete(self)
37
+ end
38
+ end
39
+ end
40
+ def add_parent(*nodes)
41
+ nodes.each do |node|
42
+ if not @parents.include?(node)
43
+ @parents << node
44
+ node.children << self
45
+ end
46
+ end
47
+ end
48
+ def rm_parent(*nodes)
49
+ nodes.each do |node|
50
+ if @parents.include?(node)
51
+ @parents.delete(node)
52
+ node.children.delete(self)
53
+ end
54
+ end
55
+ end
56
+
57
+ STEP = 4
58
+ def to_s
59
+ return @name
60
+ end
61
+ def to_graph(indent_level: 0)
62
+ sout = ""
63
+ margin = ''
64
+ 0.upto(indent_level/STEP-1) { |p| margin += (p==0 ? ' ' : '|') + ' '*(STEP - 1) }
65
+ margin += '|' + '-'*(STEP - 2)
66
+ sout += margin + "#{@name}\n"
67
+ @children.each do |child|
68
+ sout += child.to_graph(indent_level: indent_level+STEP)
69
+ end
70
+ return sout
71
+ end
72
+ def to_dot
73
+ sout=["\""+name+"\""]
74
+ @children.each do |child|
75
+ sout.push "\"#{@name}\" -> \"#{child.name}\""
76
+ sout += child.to_dot
77
+ end
78
+ return sout
79
+ end
80
+ end
81
+
82
+ class Graph
83
+ attr_accessor :nodes
84
+ include Enumerable
85
+ def initialize(g=nil)
86
+ @nodes=[]
87
+ if g #convert a hash to a graph
88
+ g.each do |name,children|
89
+ n=build(name)
90
+ n.add_child(*children)
91
+ end
92
+ end
93
+ end
94
+ def build(node, children: [], parents: [], **keywords)
95
+ graph_node=
96
+ case node
97
+ when Node
98
+ match = @nodes.find {|n| n == node} and return match
99
+ Node.new(node.name, graph: self, **keywords.merge({attributes: node.attributes||keywords[:attributes]}))
100
+ node.children.each do |c|
101
+ build(c,**keywords)
102
+ end
103
+ else
104
+ match = @nodes.find {|n| n.name == node}
105
+ match || Node.new(node, graph: self, **keywords)
106
+ end
107
+ graph_node.add_child(*children.map { |child| build(child) })
108
+ graph_node.add_parent(*parents.map { |child| build(child) })
109
+ return graph_node
110
+ end
111
+ def each
112
+ @nodes.each
113
+ end
114
+ def to_a
115
+ return @nodes
116
+ end
117
+ def all
118
+ @nodes.sort
119
+ end
120
+ def roots
121
+ @nodes.select{ |n| n.parents.length == 0}.sort
122
+ end
123
+ def dump(mode: :graph, nodes_list: :roots, **unused)
124
+ n=case nodes_list
125
+ when :roots; roots
126
+ when :all; all
127
+ when Symbol; nodes.select {|n| n.attributes[:nodes_list]}
128
+ else nodes_list.to_a
129
+ end
130
+ sout = ""
131
+ case mode
132
+ when :graph; n.each do |node| sout+=node.to_graph end
133
+ when :list; n.each do |i| sout+="- #{i}\n" end
134
+ when :dot;
135
+ sout+="digraph gems {\n"
136
+ sout+=n.map {|node| node.to_dot}.inject(:+).uniq!.join("\n")
137
+ sout+="}\n"
138
+ end
139
+ return sout
140
+ end
141
+
142
+ #return the connected set containing nodes (following the direction
143
+ #given)
144
+ def connected(*nodes, down:true, up:true)
145
+ r=Set.new()
146
+ nodes.each do |node|
147
+ unless r.include?(node)
148
+ new_nodes=Set.new()
149
+ new_nodes.merge(node.children) if down
150
+ new_nodes.merge(node.parents) if up
151
+ r.merge(connected(*new_nodes, down:down,up:up))
152
+ end
153
+ end
154
+ return r
155
+ end
156
+ #return all parents
157
+ def ancestors(*nodes)
158
+ connected(*nodes, up:true, down:false)
159
+ end
160
+ #return all childern
161
+ def descendants(*nodes)
162
+ connected(*nodes, up:false, down:true)
163
+ end
164
+
165
+ #from a list of nodes, return all nodes that are not descendants of
166
+ #other nodes in the graph
167
+ def unneeded(*nodes)
168
+ tokeep.merge(@nodes-nodes)
169
+ nodes.each do |node|
170
+ unneeded << node unless ancestors(node).any? {|c| tokeep.include?(c)}
171
+ end
172
+ end
173
+ #return all dependencies that are not needed by any more nodes.
174
+ #If some dependencies should be kept (think manual install), add them
175
+ #to the unneeded parameter
176
+ def unneeded_descendants(*nodes, needed:[])
177
+ needed-=nodes #nodes to delete are in priority
178
+ deps=descendants(*nodes)
179
+ deps-=needed #but for children nodes, needed nodes are in priority
180
+ unneeded(*deps)
181
+ end
182
+ #So to implement the equivalent of pacman -Rc packages
183
+ #it suffices to add the ancestors of packages
184
+ #For pacman -Rs, this is exactly unneeded_descendants
185
+ #and pacman -Rcs would be ancestors(unneeded_descendants)
186
+ #finally to clean all unneeded packages (provided we have a list of
187
+ #packages 'tokeep' to keep), either use unneeded(@nodes-tokeep)
188
+ #or unneeded_descendants(roots, needed:tokeep)
189
+
190
+ #return the subgraph containing all the nodes passed as parameters,
191
+ #and the complementary graph. The union of both may not be the full
192
+ #graph [edges] in case the components are not connected
193
+ def subgraph(*nodes)
194
+ subgraph=Graph.new()
195
+ compgraph=Graph.new()
196
+ @nodes.each do |node|
197
+ if nodes.include?(node)
198
+ n=subgraph.build(node.name)
199
+ node.children.each do |c|
200
+ n.add_child(c) if nodes.include?(c)
201
+ end
202
+ else
203
+ n=compgraph.build(node.name)
204
+ node.children.each do |c|
205
+ n.add_child(c) unless nodes.include?(c)
206
+ end
207
+ end
208
+ end
209
+ return subgraph, compgraph
210
+ end
211
+
212
+ end
213
+ end