drain 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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