comp_tree 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +153 -0
- data/Rakefile +152 -0
- data/comp_tree.gemspec +38 -0
- data/contrib/quix/Rakefile +16 -0
- data/contrib/quix/install.rb +3 -0
- data/contrib/quix/lib/quix/builtin/dir/casefold_brackets.rb +7 -0
- data/contrib/quix/lib/quix/builtin/kernel/tap.rb +9 -0
- data/contrib/quix/lib/quix/builtin/module/include.rb +21 -0
- data/contrib/quix/lib/quix/builtin/module/private.rb +41 -0
- data/contrib/quix/lib/quix/config.rb +37 -0
- data/contrib/quix/lib/quix/cygwin.rb +60 -0
- data/contrib/quix/lib/quix/diagnostic.rb +44 -0
- data/contrib/quix/lib/quix/enumerable.rb +33 -0
- data/contrib/quix/lib/quix/fileutils.rb +37 -0
- data/contrib/quix/lib/quix/hash_struct.rb +27 -0
- data/contrib/quix/lib/quix/kernel.rb +61 -0
- data/contrib/quix/lib/quix/lazy_struct.rb +55 -0
- data/contrib/quix/lib/quix/simple_installer.rb +87 -0
- data/contrib/quix/lib/quix/string.rb +38 -0
- data/contrib/quix/lib/quix/subpackager.rb +52 -0
- data/contrib/quix/lib/quix/thread_local.rb +32 -0
- data/contrib/quix/lib/quix/vars.rb +138 -0
- data/contrib/quix/lib/quix.rb +32 -0
- data/contrib/quix/test/all.rb +12 -0
- data/contrib/quix/test/test_deps.rb +25 -0
- data/contrib/quix/test/test_include.rb +47 -0
- data/contrib/quix/test/test_private.rb +86 -0
- data/contrib/quix/test/test_root.rb +19 -0
- data/contrib/quix/test/test_struct.rb +48 -0
- data/contrib/quix/test/test_vars.rb +187 -0
- data/install.rb +3 -0
- data/lib/comp_tree/algorithm.rb +210 -0
- data/lib/comp_tree/bucket_ipc.rb +151 -0
- data/lib/comp_tree/driver.rb +267 -0
- data/lib/comp_tree/error.rb +27 -0
- data/lib/comp_tree/node.rb +165 -0
- data/lib/comp_tree/quix/builtin/kernel/tap.rb +33 -0
- data/lib/comp_tree/quix/diagnostic.rb +68 -0
- data/lib/comp_tree/quix/kernel.rb +85 -0
- data/lib/comp_tree/retriable_fork.rb +42 -0
- data/lib/comp_tree/task_node.rb +22 -0
- data/lib/comp_tree.rb +23 -0
- data/test/all.rb +12 -0
- data/test/test_bucketipc.rb +72 -0
- data/test/test_circular.rb +36 -0
- data/test/test_comp_tree.rb +364 -0
- data/test/test_exception.rb +97 -0
- metadata +120 -0
data/README
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
|
2
|
+
= CompTree -- Parallel Computation Tree
|
3
|
+
|
4
|
+
== Synopsis
|
5
|
+
|
6
|
+
require 'comp_tree'
|
7
|
+
|
8
|
+
CompTree::Driver.new { |driver|
|
9
|
+
|
10
|
+
# Define a function named 'area' taking these three arguments.
|
11
|
+
driver.define_area(:width, :height, :offset) { |width, height, offset|
|
12
|
+
width*height - offset
|
13
|
+
}
|
14
|
+
|
15
|
+
# Define a function 'width' which takes a 'border' argument.
|
16
|
+
driver.define_width(:border) { |border|
|
17
|
+
2 + border
|
18
|
+
}
|
19
|
+
|
20
|
+
# Ditto for 'height'.
|
21
|
+
driver.define_height(:border) { |border|
|
22
|
+
3 + border
|
23
|
+
}
|
24
|
+
|
25
|
+
# Define a constant function 'border'.
|
26
|
+
driver.define_border {
|
27
|
+
5
|
28
|
+
}
|
29
|
+
|
30
|
+
# Ditto for 'offset'.
|
31
|
+
driver.define_offset {
|
32
|
+
7
|
33
|
+
}
|
34
|
+
|
35
|
+
# Compute the area using four parallel threads.
|
36
|
+
area = driver.compute(:area, :threads => 4)
|
37
|
+
|
38
|
+
# We've done this computation.
|
39
|
+
if area == (2 + 5)*(3 + 5) - 7
|
40
|
+
puts "It worked!"
|
41
|
+
else
|
42
|
+
puts "Send bug report to ..."
|
43
|
+
end
|
44
|
+
}
|
45
|
+
|
46
|
+
=== Alternative Forms for Function Definitions
|
47
|
+
|
48
|
+
This form evals a lambda, saving you the repeat parameter list:
|
49
|
+
|
50
|
+
driver.define_area :width, :height, :offset, %{
|
51
|
+
width*height - offset
|
52
|
+
}
|
53
|
+
|
54
|
+
driver.define_width :border, %{
|
55
|
+
2 + border
|
56
|
+
}
|
57
|
+
|
58
|
+
Notice the '<code>%</code>' before the brace. The lambda is created
|
59
|
+
just once, during the time of definition.
|
60
|
+
|
61
|
+
Finally there is the raw form which uses no +eval+ or
|
62
|
+
+method_missing+ tricks:
|
63
|
+
|
64
|
+
driver.define(:area, :width, :height, :offset) { |width, height, offset|
|
65
|
+
width*height - offset
|
66
|
+
}
|
67
|
+
|
68
|
+
driver.define(:width, :border) { |border|
|
69
|
+
2 + border
|
70
|
+
}
|
71
|
+
|
72
|
+
== Important Notes
|
73
|
+
|
74
|
+
The user should have a basic understanding of <em>functional
|
75
|
+
programming</em> (see for example
|
76
|
+
http://en.wikipedia.org/wiki/Functional_programming) and the meaning
|
77
|
+
of <em>side effects</em>.
|
78
|
+
|
79
|
+
CompTree requires the user to adopt a functional style. Every
|
80
|
+
function you define must explicitly depend on the data it uses.
|
81
|
+
|
82
|
+
#
|
83
|
+
# BAD example: depending on state -- offset not listed as a parameter
|
84
|
+
#
|
85
|
+
driver.define_area(:width, :height) { |width, height|
|
86
|
+
width*height - offset
|
87
|
+
}
|
88
|
+
|
89
|
+
Unless <em>offset</em> is really a constant, the result of
|
90
|
+
<tt>driver.compute(:area, :num_threads => n)</tt> is not well-defined
|
91
|
+
for _n_ > 1.
|
92
|
+
|
93
|
+
Just as depending on some changeable state is bad, it is likewise bad
|
94
|
+
to affect a state (to produce a <em>side effect</em>).
|
95
|
+
|
96
|
+
#
|
97
|
+
# BAD example: affecting state
|
98
|
+
#
|
99
|
+
driver.define_area(:width, :height, :offset) { |width, height, offset|
|
100
|
+
ACCUMULATOR.add "more data"
|
101
|
+
width*height - offset
|
102
|
+
}
|
103
|
+
|
104
|
+
Given a tree where nodes are modifying _ACCUMULATOR_, the end state of
|
105
|
+
_ACCUMULATOR_ is not well-defined. Moreover if _ACCUMULATOR_ is not
|
106
|
+
thread-safe, the result will be even worse.
|
107
|
+
|
108
|
+
Note however it is OK affect a state as long as <em>no other function
|
109
|
+
depends on that state</em>. This is the principle under which
|
110
|
+
+comp_tree+ parallelizes Rake tasks (http://drake.rubyforge.org).
|
111
|
+
|
112
|
+
== Install
|
113
|
+
|
114
|
+
% gem install comp_tree
|
115
|
+
|
116
|
+
Or for the regular (non-gem) .tgz package,
|
117
|
+
|
118
|
+
% ruby install.rb [--uninstall]
|
119
|
+
|
120
|
+
== Links
|
121
|
+
|
122
|
+
* Download: http://rubyforge.org/frs/?group_id=6917
|
123
|
+
* Rubyforge home: http://rubyforge.org/projects/comptree
|
124
|
+
* Repository: http://github.com/quix/comp_tree
|
125
|
+
|
126
|
+
== Author
|
127
|
+
|
128
|
+
* James M. Lawrence <quixoticsycophant@gmail.com>
|
129
|
+
|
130
|
+
== License
|
131
|
+
|
132
|
+
Copyright (c) 2008 James M. Lawrence. All rights reserved.
|
133
|
+
|
134
|
+
Permission is hereby granted, free of charge, to any person
|
135
|
+
obtaining a copy of this software and associated documentation files
|
136
|
+
(the "Software"), to deal in the Software without restriction,
|
137
|
+
including without limitation the rights to use, copy, modify, merge,
|
138
|
+
publish, distribute, sublicense, and/or sell copies of the Software,
|
139
|
+
and to permit persons to whom the Software is furnished to do so,
|
140
|
+
subject to the following conditions:
|
141
|
+
|
142
|
+
The above copyright notice and this permission notice shall be
|
143
|
+
included in all copies or substantial portions of the Software.
|
144
|
+
|
145
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
146
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
147
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
148
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
149
|
+
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
150
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
151
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
152
|
+
SOFTWARE.
|
153
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
$LOAD_PATH.unshift "contrib/quix/lib"
|
2
|
+
|
3
|
+
require 'rake/gempackagetask'
|
4
|
+
require 'rake/contrib/rubyforgepublisher'
|
5
|
+
require 'quix/subpackager'
|
6
|
+
|
7
|
+
$VERBOSE = nil
|
8
|
+
require 'rdoc/rdoc'
|
9
|
+
$VERBOSE = true
|
10
|
+
|
11
|
+
require 'fileutils'
|
12
|
+
include FileUtils
|
13
|
+
|
14
|
+
gemspec = eval(File.read("comp_tree.gemspec"))
|
15
|
+
package_name = gemspec.name
|
16
|
+
package_name_in_ruby = "CompTree"
|
17
|
+
|
18
|
+
# I would prefer "doc", but "html" is hard-coded for rubyforgepublisher
|
19
|
+
doc_dir = "html"
|
20
|
+
|
21
|
+
######################################################################
|
22
|
+
# clean
|
23
|
+
|
24
|
+
task :clean => [:clobber, :clean_doc] do
|
25
|
+
end
|
26
|
+
|
27
|
+
task :clean_doc do
|
28
|
+
rm_rf(doc_dir)
|
29
|
+
end
|
30
|
+
|
31
|
+
######################################################################
|
32
|
+
# test
|
33
|
+
|
34
|
+
task :test do
|
35
|
+
require 'test/all'
|
36
|
+
end
|
37
|
+
|
38
|
+
######################################################################
|
39
|
+
# package
|
40
|
+
|
41
|
+
task :package => :clean
|
42
|
+
|
43
|
+
Rake::GemPackageTask.new(gemspec) { |t|
|
44
|
+
t.need_tar = true
|
45
|
+
}
|
46
|
+
|
47
|
+
######################################################################
|
48
|
+
# doc
|
49
|
+
|
50
|
+
task :doc => :clean_doc do
|
51
|
+
files = %w(README) + %w(driver error node task_node).map { |name|
|
52
|
+
"lib/comp_tree/#{name}.rb"
|
53
|
+
}
|
54
|
+
|
55
|
+
options = [
|
56
|
+
"-o", doc_dir,
|
57
|
+
"--title", "comp_tree: #{gemspec.summary}",
|
58
|
+
"--main", "README"
|
59
|
+
]
|
60
|
+
|
61
|
+
RDoc::RDoc.new.document(files + options)
|
62
|
+
end
|
63
|
+
|
64
|
+
######################################################################
|
65
|
+
# repackage files from contrib/
|
66
|
+
|
67
|
+
task :generate_rb do
|
68
|
+
packages = {
|
69
|
+
:comp_tree => {
|
70
|
+
:name_in_ruby => "CompTree",
|
71
|
+
:lib_dir => "./lib",
|
72
|
+
:subpackages => {
|
73
|
+
:quix => {
|
74
|
+
:name_in_ruby => "Quix",
|
75
|
+
:sources => [
|
76
|
+
"diagnostic",
|
77
|
+
"kernel",
|
78
|
+
"builtin/kernel/tap",
|
79
|
+
],
|
80
|
+
:lib_dir => "./contrib/quix/lib",
|
81
|
+
:ignore_root_rb => true,
|
82
|
+
},
|
83
|
+
},
|
84
|
+
},
|
85
|
+
}
|
86
|
+
Quix::Subpackager.run(packages)
|
87
|
+
end
|
88
|
+
|
89
|
+
######################################################################
|
90
|
+
# git
|
91
|
+
|
92
|
+
def git(*args)
|
93
|
+
cmd = ["git"] + args
|
94
|
+
sh(*cmd)
|
95
|
+
end
|
96
|
+
|
97
|
+
task :init_contrib do
|
98
|
+
unless `git remote`.split.include? "quix"
|
99
|
+
git(*%w!remote add -f quix git@github.com:quix/quix.git!)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
task :add_contrib_first_time => :init_contrib do
|
104
|
+
git(*%w!merge --squash -s ours --no-commit quix/master!)
|
105
|
+
git(*%w!read-tree --prefix=contrib/quix -u quix/master!)
|
106
|
+
git("commit", "-m", "add quix utils")
|
107
|
+
end
|
108
|
+
|
109
|
+
task :run_pull_contrib do
|
110
|
+
git(*%w!pull --no-commit -s subtree quix master!)
|
111
|
+
end
|
112
|
+
|
113
|
+
task :pull_contrib => [ :init_contrib, :run_pull_contrib, :generate_rb ]
|
114
|
+
|
115
|
+
######################################################################
|
116
|
+
# publisher
|
117
|
+
|
118
|
+
task :publish => :doc do
|
119
|
+
Rake::RubyForgePublisher.new('comptree', 'quix').upload
|
120
|
+
end
|
121
|
+
|
122
|
+
######################################################################
|
123
|
+
# release
|
124
|
+
|
125
|
+
task :prerelease => :clean do
|
126
|
+
rm_rf(doc_dir)
|
127
|
+
rm_rf("pkg")
|
128
|
+
unless `git status` =~ %r!nothing to commit \(working directory clean\)!
|
129
|
+
raise "Directory not clean"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
task :finish_release do
|
134
|
+
%w(gem tgz).each_with_index { |ext, index|
|
135
|
+
sh("rubyforge",
|
136
|
+
(index == 0 ? "add_release" : "add_file"),
|
137
|
+
gemspec.rubyforge_project,
|
138
|
+
gemspec.rubyforge_project,
|
139
|
+
gemspec.version.to_s,
|
140
|
+
"pkg/#{gemspec.name}-#{gemspec.version}.#{ext}")
|
141
|
+
}
|
142
|
+
git("tag", gemspec.version.to_s)
|
143
|
+
git("push")
|
144
|
+
end
|
145
|
+
|
146
|
+
task :release =>
|
147
|
+
[
|
148
|
+
:prerelease,
|
149
|
+
:package,
|
150
|
+
:publish,
|
151
|
+
:finish_release,
|
152
|
+
]
|
data/comp_tree.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new { |t|
|
3
|
+
t.author = "James M. Lawrence"
|
4
|
+
t.email = "quixoticsycophant@gmail.com"
|
5
|
+
t.summary = "Parallel Computation Tree"
|
6
|
+
t.name = "comp_tree"
|
7
|
+
t.rubyforge_project = "comptree"
|
8
|
+
t.homepage = "comptree.rubyforge.org"
|
9
|
+
t.version = "0.5.0"
|
10
|
+
t.description = "Build a computation tree and execute it with N " +
|
11
|
+
"parallel threads. Optionally fork computation nodes into new processes."
|
12
|
+
|
13
|
+
t.files = %w{README comp_tree.gemspec} +
|
14
|
+
Dir["./**/*.rb"] +
|
15
|
+
Dir["./**/Rakefile"]
|
16
|
+
|
17
|
+
rdoc_exclude = %w{
|
18
|
+
test
|
19
|
+
contrib
|
20
|
+
install
|
21
|
+
quix
|
22
|
+
fork
|
23
|
+
diagnostic
|
24
|
+
algorithm
|
25
|
+
bucket
|
26
|
+
comp_tree\.rb
|
27
|
+
}
|
28
|
+
t.has_rdoc = true
|
29
|
+
t.extra_rdoc_files = %w{README}
|
30
|
+
t.rdoc_options += [
|
31
|
+
"--main",
|
32
|
+
"README",
|
33
|
+
"--title",
|
34
|
+
"comp_tree: #{t.summary}",
|
35
|
+
] + rdoc_exclude.inject(Array.new) { |acc, pattern|
|
36
|
+
acc + ["--exclude", pattern]
|
37
|
+
}
|
38
|
+
}
|
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift "./lib"
|
2
|
+
|
3
|
+
require 'quix/simple_installer'
|
4
|
+
require 'quix/config'
|
5
|
+
|
6
|
+
task :test do
|
7
|
+
load './test/all.rb'
|
8
|
+
end
|
9
|
+
|
10
|
+
task :install do
|
11
|
+
Quix::SimpleInstaller.new.install
|
12
|
+
end
|
13
|
+
|
14
|
+
task :uninstall do
|
15
|
+
Quix::SimpleInstaller.new.uninstall
|
16
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
if $DEBUG and !(defined?($NO_DEBUG_INCLUDE) and $NO_DEBUG_INCLUDE)
|
3
|
+
class Module
|
4
|
+
orig_include = instance_method(:include)
|
5
|
+
remove_method(:include)
|
6
|
+
define_method(:include) { |*mods|
|
7
|
+
mods.each { |mod|
|
8
|
+
if mod.class == Module
|
9
|
+
mod.instance_methods(true).each { |name|
|
10
|
+
if self.instance_methods(true).include?(name)
|
11
|
+
STDERR.puts("Note: replacing #{self.inspect}##{name} " +
|
12
|
+
"with #{mod.inspect}##{name}")
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
orig_include.bind(self).call(*mods)
|
17
|
+
}
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
class Module
|
3
|
+
alias_method :private__original, :private
|
4
|
+
def private(*args, &block)
|
5
|
+
private__original(*args)
|
6
|
+
if block
|
7
|
+
singleton_class = (class << self ; self ; end)
|
8
|
+
caller_self = block.binding.eval("self")
|
9
|
+
method_added__original =
|
10
|
+
if (t = method(:method_added)) and t.owner == singleton_class
|
11
|
+
t
|
12
|
+
else
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
begin
|
16
|
+
singleton_class.instance_eval {
|
17
|
+
define_method(:method_added) { |name|
|
18
|
+
caller_self.instance_eval {
|
19
|
+
private__original(name.to_sym)
|
20
|
+
}
|
21
|
+
if t = method_added__original
|
22
|
+
t.call(name)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
}
|
26
|
+
block.call
|
27
|
+
ensure
|
28
|
+
if t = method_added__original
|
29
|
+
t.owner.instance_eval {
|
30
|
+
define_method(:method_added, t)
|
31
|
+
}
|
32
|
+
else
|
33
|
+
singleton_class.instance_eval {
|
34
|
+
remove_method(:method_added)
|
35
|
+
}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
module Quix
|
5
|
+
module Config
|
6
|
+
CONFIG = ::Config::CONFIG
|
7
|
+
|
8
|
+
def ruby_executable
|
9
|
+
File.join(CONFIG["bindir"], CONFIG["RUBY_INSTALL_NAME"])
|
10
|
+
end
|
11
|
+
|
12
|
+
def version_gt(version) ; version_compare( :>, version) ; end
|
13
|
+
def version_lt(version) ; version_compare( :<, version) ; end
|
14
|
+
def version_eq(version) ; version_compare(:==, version) ; end
|
15
|
+
def version_ge(version) ; version_compare(:>=, version) ; end
|
16
|
+
def version_le(version) ; version_compare(:<=, version) ; end
|
17
|
+
def version_ne(version) ; version_compare(:"!=", version) ; end
|
18
|
+
|
19
|
+
def version_compare(op, version)
|
20
|
+
major, minor, teeny =
|
21
|
+
version.split(".").map { |n| n.to_i }
|
22
|
+
|
23
|
+
this_major, this_minor, this_teeny =
|
24
|
+
%w(MAJOR MINOR TEENY).map { |v| CONFIG[v].to_i }
|
25
|
+
|
26
|
+
if this_major == major and this_minor == minor
|
27
|
+
this_teeny.send(op, teeny)
|
28
|
+
elsif this_major == major
|
29
|
+
this_minor.send(op, minor)
|
30
|
+
else
|
31
|
+
this_major.send(op, major)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend self
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
unless RUBY_PLATFORM =~ %r!cygwin!
|
3
|
+
raise NotImplementedError, "cygwin-only module"
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'fileutils'
|
7
|
+
require 'thread'
|
8
|
+
|
9
|
+
module Quix
|
10
|
+
module Cygwin
|
11
|
+
def run_batchfile(file, *args)
|
12
|
+
dos_pwd_env {
|
13
|
+
sh("cmd", "/c", dos_path(file), *args)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize_path(path)
|
18
|
+
path.sub(%r!/+\Z!, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def unix2dos(string)
|
22
|
+
string.
|
23
|
+
gsub("\n", "\r\n").
|
24
|
+
gsub(%r!\r+!, "\r")
|
25
|
+
end
|
26
|
+
|
27
|
+
def dos_path(unix_path)
|
28
|
+
`cygpath -w #{normalize_path(unix_path)}`.chomp
|
29
|
+
end
|
30
|
+
|
31
|
+
def unix_path(dos_path)
|
32
|
+
escaped_path = dos_path.sub(%r!\\+\Z!, "").gsub("\\", "\\\\\\\\")
|
33
|
+
`cygpath #{escaped_path}`.chomp
|
34
|
+
end
|
35
|
+
|
36
|
+
def dos_pwd_env
|
37
|
+
Thread.exclusive {
|
38
|
+
orig = ENV["PWD"]
|
39
|
+
ENV["PWD"] = dos_path(Dir.pwd)
|
40
|
+
begin
|
41
|
+
yield
|
42
|
+
ensure
|
43
|
+
ENV["PWD"] = orig
|
44
|
+
end
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def avoid_dll(file)
|
49
|
+
temp_file = file + ".avoiding-link"
|
50
|
+
FileUtils.mv(file, temp_file)
|
51
|
+
begin
|
52
|
+
yield
|
53
|
+
ensure
|
54
|
+
FileUtils.mv(temp_file, file)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
extend self
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
|
2
|
+
require 'quix/builtin/kernel/tap'
|
3
|
+
|
4
|
+
module Quix
|
5
|
+
module Diagnostic
|
6
|
+
def show(desc = nil, stream = STDOUT, &block)
|
7
|
+
if desc
|
8
|
+
stream.puts(desc)
|
9
|
+
end
|
10
|
+
if block
|
11
|
+
expression = block.call
|
12
|
+
eval(expression, block.binding).tap { |result|
|
13
|
+
stream.printf("%-16s => %s\n", expression, result.inspect)
|
14
|
+
}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if $DEBUG
|
19
|
+
def debug
|
20
|
+
yield
|
21
|
+
end
|
22
|
+
|
23
|
+
def debugging?
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def trace(desc = nil, &block)
|
28
|
+
if desc
|
29
|
+
show("#{desc}.".sub(%r!\.\.+\Z!, ""), STDERR, &block)
|
30
|
+
else
|
31
|
+
show(nil, STDERR, &block)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# non-$DEBUG
|
36
|
+
def debug ; end
|
37
|
+
def debugging? ; end
|
38
|
+
def trace(*args) ; end
|
39
|
+
end
|
40
|
+
|
41
|
+
extend self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require 'quix/builtin/kernel/tap'
|
3
|
+
|
4
|
+
module Quix
|
5
|
+
module Enumerable
|
6
|
+
def inject_with_index(*args)
|
7
|
+
index = 0
|
8
|
+
inject(*args) { |acc, elem|
|
9
|
+
yield(acc, elem, index).tap {
|
10
|
+
index += 1
|
11
|
+
}
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def map_with_index
|
16
|
+
Array.new.tap { |result|
|
17
|
+
each_with_index { |elem, index|
|
18
|
+
result << yield(elem, index)
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def select_with_index
|
24
|
+
Array.new.tap { |result|
|
25
|
+
each_with_index { |elem, index|
|
26
|
+
if yield(elem, index)
|
27
|
+
result << elem
|
28
|
+
end
|
29
|
+
}
|
30
|
+
}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'quix/builtin/kernel/tap'
|
4
|
+
|
5
|
+
module Quix
|
6
|
+
module FileUtils
|
7
|
+
def rename_file(file, new_name)
|
8
|
+
#
|
9
|
+
# For case-insensitive systems, we must move the file elsewhere
|
10
|
+
# before changing case.
|
11
|
+
#
|
12
|
+
temp = File.join(Dir.tmpdir, File.basename(file))
|
13
|
+
::FileUtils.mv(file, temp)
|
14
|
+
begin
|
15
|
+
::FileUtils.mv(temp, new_name)
|
16
|
+
rescue
|
17
|
+
::FileUtils.mv(temp, file)
|
18
|
+
raise
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def replace_file(file)
|
23
|
+
old_contents = File.read(file)
|
24
|
+
yield(old_contents).tap { |new_contents|
|
25
|
+
File.open(file, "w") { |output|
|
26
|
+
output.print(new_contents)
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def stem(file)
|
32
|
+
file.sub(%r!#{File.extname(file)}\Z!, "")
|
33
|
+
end
|
34
|
+
|
35
|
+
extend self
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
require 'quix/builtin/kernel/tap'
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
module Quix
|
6
|
+
class HashStruct < OpenStruct
|
7
|
+
def method_missing(sym, *args, &block)
|
8
|
+
if table.respond_to? sym
|
9
|
+
table.send(sym, *args, &block)
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def recursive_new(hash)
|
17
|
+
new.tap { |s|
|
18
|
+
hash.each_pair { |key, value|
|
19
|
+
s.send(
|
20
|
+
:"#{key}=",
|
21
|
+
value.is_a?(Hash) ? recursive_new(value) : value)
|
22
|
+
}
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|