jkr 0.0.1
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.
- data/History.txt +6 -0
- data/Manifest.txt +14 -0
- data/README.txt +57 -0
- data/Rakefile +12 -0
- data/bin/jkr +224 -0
- data/lib/jkr/analysis.rb +27 -0
- data/lib/jkr/env.rb +38 -0
- data/lib/jkr/plan.rb +134 -0
- data/lib/jkr/rake.rb +18 -0
- data/lib/jkr/trial.rb +54 -0
- data/lib/jkr/userutils.rb +156 -0
- data/lib/jkr/utils.rb +470 -0
- data/lib/jkr.rb +9 -0
- data/test/test_jkr.rb +8 -0
- metadata +89 -0
data/History.txt
ADDED
data/Manifest.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
= jkr
|
2
|
+
|
3
|
+
* http://github.com/hayamiz/jkr
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
FIX (describe your package)
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* FIX (list of features or problems)
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
FIX (code sample of usage)
|
16
|
+
|
17
|
+
== REQUIREMENTS:
|
18
|
+
|
19
|
+
* FIX (list of requirements)
|
20
|
+
|
21
|
+
== INSTALL:
|
22
|
+
|
23
|
+
* FIX (sudo gem install, anything else)
|
24
|
+
|
25
|
+
== DEVELOPERS:
|
26
|
+
|
27
|
+
After checking out the source, run:
|
28
|
+
|
29
|
+
$ rake newb
|
30
|
+
|
31
|
+
This task will install any missing dependencies, run the tests/specs,
|
32
|
+
and generate the RDoc.
|
33
|
+
|
34
|
+
== LICENSE:
|
35
|
+
|
36
|
+
(The MIT License)
|
37
|
+
|
38
|
+
Copyright (c) 2010 FIX
|
39
|
+
|
40
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
41
|
+
a copy of this software and associated documentation files (the
|
42
|
+
'Software'), to deal in the Software without restriction, including
|
43
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
44
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
45
|
+
permit persons to whom the Software is furnished to do so, subject to
|
46
|
+
the following conditions:
|
47
|
+
|
48
|
+
The above copyright notice and this permission notice shall be
|
49
|
+
included in all copies or substantial portions of the Software.
|
50
|
+
|
51
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
52
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
53
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
54
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
55
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
56
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
57
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/jkr
ADDED
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'optparse'
|
5
|
+
require 'jkr'
|
6
|
+
|
7
|
+
def ul(str)
|
8
|
+
"\033[4m#{str}\033[24m"
|
9
|
+
end
|
10
|
+
|
11
|
+
def identity
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
class JkrCmd
|
16
|
+
def jkr_commands()
|
17
|
+
self.methods.map{|method_sym|
|
18
|
+
if method_sym.to_s =~ /^([a-z_-]+)_cmd$/
|
19
|
+
$~[1]
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
}.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_optparser()
|
27
|
+
@optparser = OptionParser.new
|
28
|
+
|
29
|
+
@optparser.banner = <<EOS
|
30
|
+
Usage: #{$0} [#{ul('command')} [#{ul('options')}]]
|
31
|
+
|
32
|
+
Available commands: #{self.jkr_commands.join(', ')}
|
33
|
+
|
34
|
+
'#{$0} help #{ul('command')}' shows detailed usage of #{ul('command')}
|
35
|
+
|
36
|
+
EOS
|
37
|
+
|
38
|
+
@options[:directory] = Dir.pwd
|
39
|
+
@optparser.on('-C', '--directory DIR',
|
40
|
+
"Change to directory DIR before reading jkr config files."
|
41
|
+
) do |directory|
|
42
|
+
@options[:directory] = directory
|
43
|
+
end
|
44
|
+
|
45
|
+
@options[:jkr_directory] = File.join(@options[:directory], "jkr")
|
46
|
+
@optparser.on('-f', '--jkr-directory DIR',
|
47
|
+
"Jkr specification file."
|
48
|
+
) do |jkr_directory|
|
49
|
+
@options[:jkr_directory] = jkr_directory
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def parse_args(argv)
|
54
|
+
@optparser.parse!(argv)
|
55
|
+
@options
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize()
|
59
|
+
@options = Hash.new
|
60
|
+
self.setup_optparser()
|
61
|
+
@jkr_env = Jkr::Env.new(@options[:directory], @options[:jkr_directory])
|
62
|
+
end
|
63
|
+
|
64
|
+
def dispatch(argv)
|
65
|
+
self.parse_args(argv) # argv updated destructively
|
66
|
+
|
67
|
+
if argv.empty?
|
68
|
+
argv = ["run"]
|
69
|
+
end
|
70
|
+
command = argv.shift
|
71
|
+
unless self.jkr_commands.include?(command)
|
72
|
+
$stderr.puts "No such command: #{command}"
|
73
|
+
puts
|
74
|
+
puts @optparser.help
|
75
|
+
exit 1
|
76
|
+
end
|
77
|
+
|
78
|
+
self.send("#{command}_cmd", argv)
|
79
|
+
end
|
80
|
+
|
81
|
+
## Command definitions
|
82
|
+
|
83
|
+
def analyze_cmd(argv)
|
84
|
+
argv.each do |arg|
|
85
|
+
Jkr::Analysis.analyze(@jkr_env, arg)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
def analyze_cmd_help()
|
89
|
+
{
|
90
|
+
:summary => "analyze results",
|
91
|
+
:desc => "Usage: #{$0} analyze #{ul('RESULT_ID')} ..."
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def list_cmd(argv)
|
96
|
+
puts "Existing plans:"
|
97
|
+
puts
|
98
|
+
plans = @jkr_env.plans.map do |plan_file_path|
|
99
|
+
plan = Jkr::Plan.new(@jkr_env, plan_file_path)
|
100
|
+
[File.basename(plan_file_path, ".plan"), plan.title]
|
101
|
+
end
|
102
|
+
maxlen = plans.map{|plan| plan[0].size}.max
|
103
|
+
plans.each do |plan|
|
104
|
+
printf(" %#{maxlen}s : %s\n", plan[0], plan[1])
|
105
|
+
end
|
106
|
+
puts
|
107
|
+
end
|
108
|
+
def list_cmd_help()
|
109
|
+
{
|
110
|
+
:summary => "list existing plans",
|
111
|
+
:desc => "list existing plans"
|
112
|
+
}
|
113
|
+
end
|
114
|
+
|
115
|
+
def run_cmd(argv)
|
116
|
+
filter = (if argv.empty?
|
117
|
+
lambda{|x| true}
|
118
|
+
else
|
119
|
+
lambda do |plan_file_path|
|
120
|
+
argv.map{|arg|
|
121
|
+
Regexp.compile(arg)
|
122
|
+
}.any?{|regex|
|
123
|
+
File.basename(plan_file_path) =~ regex
|
124
|
+
}
|
125
|
+
end
|
126
|
+
end)
|
127
|
+
@jkr_env.plans.each do |plan_file_path|
|
128
|
+
if filter.call(plan_file_path)
|
129
|
+
plan = Jkr::Plan.new(@jkr_env, plan_file_path)
|
130
|
+
Jkr::Trial.run(@jkr_env, plan)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
def run_cmd_help()
|
135
|
+
{
|
136
|
+
:summary => "run experiments",
|
137
|
+
:desc => ""
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def install_cmd(argv)
|
142
|
+
jkr_dir = File.join(@options[:directory], "jkr")
|
143
|
+
result_dir = File.join(@options[:directory], "jkr", "result")
|
144
|
+
plan_dir = File.join(@options[:directory], "jkr", "plan")
|
145
|
+
[jkr_dir, result_dir, plan_dir].each{|dir| FileUtils.mkdir(dir) }
|
146
|
+
File.open(File.join(plan_dir, "sample.plan"), "w") do |file|
|
147
|
+
file.puts <<EOSS
|
148
|
+
# -*- mode: ruby -*-
|
149
|
+
|
150
|
+
title "Sample experiment plan"
|
151
|
+
description <<EOS
|
152
|
+
This is a sample experiment plan specification file.
|
153
|
+
EOS
|
154
|
+
|
155
|
+
# Discard this file after the experiments conducted.
|
156
|
+
discard_on_finish
|
157
|
+
|
158
|
+
def_experiment_plan do |p|
|
159
|
+
# 'p.variable' for variables
|
160
|
+
# 'p.param' for static parameters
|
161
|
+
|
162
|
+
p.variable :num_xxx => 1..10
|
163
|
+
p.param :use_yyy => false
|
164
|
+
|
165
|
+
if p.param[:use_yyy]
|
166
|
+
p.param[:yyy_zzz] = "foo"
|
167
|
+
else
|
168
|
+
p.param[:yyy_zzz] = "bar"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def_execution do |params|
|
173
|
+
puts "hello world, #\{params.num_xxx\}, #\{params[:num_xxx]\}"
|
174
|
+
end
|
175
|
+
EOSS
|
176
|
+
end
|
177
|
+
end
|
178
|
+
def install_cmd_help()
|
179
|
+
{
|
180
|
+
:summary => "install jkr skelton setting files",
|
181
|
+
:desc => ""
|
182
|
+
}
|
183
|
+
end
|
184
|
+
|
185
|
+
def help_cmd(argv)
|
186
|
+
case argv[0]
|
187
|
+
when /^commands/
|
188
|
+
commands = self.jkr_commands
|
189
|
+
puts "Available commands:"
|
190
|
+
max_len = commands.map(&:size).max
|
191
|
+
commands.each do |command|
|
192
|
+
help = self.send("#{command}_cmd_help")
|
193
|
+
printf " %#{max_len}s : #{help[:summary]}\n", command
|
194
|
+
end
|
195
|
+
puts
|
196
|
+
else
|
197
|
+
commands = argv.select{|arg| self.jkr_commands.include?(arg)}
|
198
|
+
if commands.size > 0
|
199
|
+
commands.each do |cmd|
|
200
|
+
help = self.send("#{cmd}_cmd_help")
|
201
|
+
puts("== help: #{cmd} ==\n#{help[:summary]}\n\n#{help[:desc]}\n")
|
202
|
+
end
|
203
|
+
else
|
204
|
+
puts @optparser.help
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
def help_cmd_help()
|
209
|
+
{
|
210
|
+
:summary => "show helps",
|
211
|
+
:desc => ""
|
212
|
+
}
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
def main(argv)
|
218
|
+
JkrCmd.new.dispatch(argv)
|
219
|
+
end
|
220
|
+
|
221
|
+
if __FILE__ == $0
|
222
|
+
main(ARGV.dup)
|
223
|
+
end
|
224
|
+
|
data/lib/jkr/analysis.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
|
2
|
+
class Jkr
|
3
|
+
class Analysis
|
4
|
+
def self.analyze(env, resultset_num)
|
5
|
+
resultset_num = sprintf "%03d", resultset_num.to_i
|
6
|
+
resultset_dir = Dir.glob(File.join(env.jkr_result_dir, resultset_num)+"*")
|
7
|
+
if resultset_dir.size != 1
|
8
|
+
raise RuntimeError.new "cannot specify resultset dir (#{resultset_dir.join(" ")})"
|
9
|
+
end
|
10
|
+
resultset_dir = resultset_dir.first
|
11
|
+
|
12
|
+
plan_files = Dir.glob(File.join(resultset_dir, "*.plan"))
|
13
|
+
if plan_files.size == 0
|
14
|
+
raise RuntimeError.new "cannot find plan file"
|
15
|
+
elsif plan_files.size > 1
|
16
|
+
raise RuntimeError.new "there are two or more plan files"
|
17
|
+
end
|
18
|
+
plan_file_path = plan_files.first
|
19
|
+
|
20
|
+
plan = Jkr::Plan.new(env, plan_file_path)
|
21
|
+
|
22
|
+
Jkr::AnalysisUtils.define_analysis_utils(resultset_dir, plan)
|
23
|
+
plan.analysis.call(plan)
|
24
|
+
Jkr::AnalysisUtils.undef_analysis_utils(plan)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/jkr/env.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
class Jkr
|
5
|
+
class Env
|
6
|
+
attr_reader :jkr_dir
|
7
|
+
attr_reader :working_dir
|
8
|
+
attr_reader :jkr_result_dir
|
9
|
+
attr_reader :jkr_plan_dir
|
10
|
+
attr_reader :jkr_script_dir
|
11
|
+
|
12
|
+
PLAN_DIR = "plan"
|
13
|
+
RESULT_DIR = "result"
|
14
|
+
SCRIPT_DIR = "script"
|
15
|
+
|
16
|
+
def initialize(working_dir = Dir.pwd, jkr_dir = File.join(Dir.pwd, "jkr"))
|
17
|
+
@jkr_dir = jkr_dir
|
18
|
+
@working_dir = working_dir
|
19
|
+
@jkr_plan_dir = File.join(@jkr_dir, PLAN_DIR)
|
20
|
+
@jkr_result_dir = File.join(@jkr_dir, RESULT_DIR)
|
21
|
+
@jkr_script_dir = File.join(@jkr_dir, SCRIPT_DIR)
|
22
|
+
|
23
|
+
[@jkr_dir, @jkr_result_dir, @jkr_plan_dir, @jkr_script_dir].each do |dir_path|
|
24
|
+
unless Dir.exists?(dir_path)
|
25
|
+
FileUtils.mkdir_p(dir_path)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def next_plan
|
31
|
+
self.plans.first
|
32
|
+
end
|
33
|
+
|
34
|
+
def plans
|
35
|
+
Dir.glob("#{@jkr_plan_dir}#{File::SEPARATOR}*.plan").sort
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/jkr/plan.rb
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
|
2
|
+
require 'jkr/utils'
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
class Jkr
|
6
|
+
class Plan
|
7
|
+
attr_accessor :title
|
8
|
+
attr_accessor :desc
|
9
|
+
|
10
|
+
attr_accessor :params
|
11
|
+
attr_accessor :vars
|
12
|
+
|
13
|
+
# Proc's
|
14
|
+
attr_accessor :prep
|
15
|
+
attr_accessor :cleanup
|
16
|
+
attr_accessor :routine
|
17
|
+
attr_accessor :analysis
|
18
|
+
|
19
|
+
attr_accessor :src
|
20
|
+
|
21
|
+
attr_reader :file_path
|
22
|
+
attr_reader :jkr_env
|
23
|
+
|
24
|
+
def initialize(jkr_env, plan_file_path = nil)
|
25
|
+
@jkr_env = jkr_env
|
26
|
+
@file_path = plan_file_path || jkr_env.next_plan
|
27
|
+
return nil unless @file_path
|
28
|
+
|
29
|
+
@title = "no title"
|
30
|
+
@desc = "no desc"
|
31
|
+
|
32
|
+
@params = {}
|
33
|
+
@vars = {}
|
34
|
+
@routine = lambda do |_|
|
35
|
+
raise NotImplementedError.new("A routine of experiment '#{@title}' is not implemented")
|
36
|
+
end
|
37
|
+
@prep = lambda do |_|
|
38
|
+
raise NotImplementedError.new("A prep of experiment '#{@title}' is not implemented")
|
39
|
+
end
|
40
|
+
@cleanup = lambda do |_|
|
41
|
+
raise NotImplementedError.new("A cleanup of experiment '#{@title}' is not implemented")
|
42
|
+
end
|
43
|
+
|
44
|
+
@src = nil
|
45
|
+
|
46
|
+
PlanLoader.load_plan(self)
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
class PlanLoader
|
51
|
+
class PlanParams
|
52
|
+
attr_reader :vars
|
53
|
+
attr_reader :params
|
54
|
+
|
55
|
+
def initialize()
|
56
|
+
@vars = {}
|
57
|
+
@params = {}
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key)
|
61
|
+
@params[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
def []=(key, val)
|
65
|
+
@params[key] = val
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
include Jkr::PlanUtils
|
70
|
+
|
71
|
+
def initialize(plan)
|
72
|
+
@plan = plan
|
73
|
+
@params = nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.load_plan(plan)
|
77
|
+
plan_loader = self.new(plan)
|
78
|
+
plan.src = File.open(plan.file_path, "r").read
|
79
|
+
plan_loader.instance_eval(plan.src, plan.file_path, 1)
|
80
|
+
plan
|
81
|
+
end
|
82
|
+
|
83
|
+
## Functions for describing plans in '.plan' files below
|
84
|
+
def plan
|
85
|
+
@plan
|
86
|
+
end
|
87
|
+
|
88
|
+
def title(plan_title)
|
89
|
+
@plan.title = plan_title.to_s
|
90
|
+
end
|
91
|
+
|
92
|
+
def description(plan_desc)
|
93
|
+
@plan.desc = plan_desc.to_s
|
94
|
+
end
|
95
|
+
|
96
|
+
def def_parameters(&proc)
|
97
|
+
@params = PlanParams.new
|
98
|
+
proc.call()
|
99
|
+
@plan.params.merge!(@params.params)
|
100
|
+
@plan.vars.merge!(@params.vars)
|
101
|
+
end
|
102
|
+
|
103
|
+
def def_routine(&proc)
|
104
|
+
@plan.routine = proc
|
105
|
+
end
|
106
|
+
def def_prep(&proc)
|
107
|
+
@plan.prep = proc
|
108
|
+
end
|
109
|
+
def def_cleanup(&proc)
|
110
|
+
@plan.cleanup = proc
|
111
|
+
end
|
112
|
+
def def_analysis(&proc)
|
113
|
+
@plan.analysis = proc
|
114
|
+
end
|
115
|
+
|
116
|
+
def parameter(arg = nil)
|
117
|
+
if arg.is_a? Hash
|
118
|
+
# set param
|
119
|
+
@params.params.merge!(arg)
|
120
|
+
else
|
121
|
+
@params
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def variable(arg = nil)
|
126
|
+
if arg.is_a? Hash
|
127
|
+
@params.vars.merge!(arg)
|
128
|
+
else
|
129
|
+
@params
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/jkr/rake.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
desc "Preparation before experiments"
|
4
|
+
task :before
|
5
|
+
|
6
|
+
desc "Running experiments"
|
7
|
+
task :run => FileList['jkr/queue/*.plan'] do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
desc "Wrapping-up data after experiments"
|
13
|
+
task :after
|
14
|
+
|
15
|
+
task :run => [:before]
|
16
|
+
task :after => [:run]
|
17
|
+
|
18
|
+
task :default => [:after]
|
data/lib/jkr/trial.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'jkr/utils'
|
4
|
+
|
5
|
+
class Jkr
|
6
|
+
class Trial
|
7
|
+
attr_reader :params
|
8
|
+
|
9
|
+
def self.make_trials(resultset_dir, plan)
|
10
|
+
var_combs = [{}]
|
11
|
+
plan.vars.each do |key, vals|
|
12
|
+
var_combs = vals.map {|val|
|
13
|
+
var_combs.map do |var_comb|
|
14
|
+
var_comb.dup.merge(key => val)
|
15
|
+
end
|
16
|
+
}.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
var_combs.map do |var_comb|
|
20
|
+
result_dir = Utils.reserve_next_dir(resultset_dir)
|
21
|
+
Trial.new(result_dir, plan, plan.params.merge(var_comb))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(env, plan)
|
26
|
+
plan_suffix = File.basename(plan.file_path, ".plan")
|
27
|
+
resultset_dir = Utils.reserve_next_dir(env.jkr_result_dir, plan_suffix)
|
28
|
+
trials = self.make_trials(resultset_dir, plan)
|
29
|
+
|
30
|
+
FileUtils.copy_file(plan.file_path,
|
31
|
+
File.join(resultset_dir, File.basename(plan.file_path)))
|
32
|
+
params = plan.params.merge(plan.vars)
|
33
|
+
plan.freeze
|
34
|
+
plan.prep.call(plan)
|
35
|
+
trials.each do |trial|
|
36
|
+
trial.run
|
37
|
+
end
|
38
|
+
plan.cleanup.call(plan)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(result_dir, plan, params)
|
42
|
+
@result_dir = result_dir
|
43
|
+
@plan = plan
|
44
|
+
@params = params
|
45
|
+
end
|
46
|
+
private :initialize
|
47
|
+
|
48
|
+
def run()
|
49
|
+
Jkr::TrialUtils.define_routine_utils(@result_dir, @plan, @params)
|
50
|
+
@plan.routine.call(@plan, @params)
|
51
|
+
Jkr::TrialUtils.undef_routine_utils(@plan)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
require 'kconv'
|
3
|
+
require 'time'
|
4
|
+
require 'date'
|
5
|
+
require 'csv'
|
6
|
+
|
7
|
+
class Jkr
|
8
|
+
class SysUtils
|
9
|
+
def self.cpu_cores()
|
10
|
+
self.num_cores()
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.num_cores()
|
14
|
+
`grep "core id" /proc/cpuinfo|wc -l`.to_i
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.num_processors()
|
18
|
+
`grep "physical id" /proc/cpuinfo|sort|uniq|wc -l`.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class DataUtils
|
23
|
+
BLOCKSIZE = 268435456 # 256MB
|
24
|
+
def self.read_blockseq(io_or_filepath, separator = "\n\n", &proc)
|
25
|
+
file = io_or_filepath
|
26
|
+
if ! io_or_filepath.is_a? IO
|
27
|
+
file = File.open(io_or_filepath, "r")
|
28
|
+
end
|
29
|
+
proc ||= lambda do |blockstr|
|
30
|
+
unless blockstr.strip.empty?
|
31
|
+
blockstr.split
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
result = []
|
38
|
+
bufstr = ""
|
39
|
+
while ! file.eof?
|
40
|
+
bufstr += file.read(BLOCKSIZE)
|
41
|
+
blocks = bufstr.split(separator)
|
42
|
+
bufstr = blocks.pop
|
43
|
+
blocks.each do |block|
|
44
|
+
ret = proc.call(block)
|
45
|
+
result.push(ret) if ret
|
46
|
+
end
|
47
|
+
end
|
48
|
+
ret = proc.call(bufstr)
|
49
|
+
result.push(ret) if ret
|
50
|
+
|
51
|
+
result
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.read_rowseq(io_or_filepath, &block)
|
55
|
+
self.read_blockseq(io_or_filepath, "\n", &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.read_mpstat_avg(io_or_filepath)
|
59
|
+
self.read_blockseq(io_or_filepath){|blockstr|
|
60
|
+
if blockstr =~ /^Average:/
|
61
|
+
result = Hash.new
|
62
|
+
rows = blockstr.lines.map(&:strip)
|
63
|
+
header = rows.shift.split
|
64
|
+
header.shift
|
65
|
+
result[:labels] = header
|
66
|
+
result[:data] = rows.map { |row|
|
67
|
+
vals = row.split
|
68
|
+
vals.shift
|
69
|
+
if vals.size != result[:labels].size
|
70
|
+
raise RuntimeError.new("Invalid mpstat data")
|
71
|
+
end
|
72
|
+
vals.map{|val|
|
73
|
+
begin
|
74
|
+
Float(val)
|
75
|
+
rescue ArgumentError
|
76
|
+
val
|
77
|
+
end
|
78
|
+
}
|
79
|
+
}
|
80
|
+
result
|
81
|
+
end
|
82
|
+
}.last
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.read_mpstat(io_or_filepath)
|
86
|
+
hostname = `hostname`.strip
|
87
|
+
|
88
|
+
date = nil
|
89
|
+
last_time = nil
|
90
|
+
self.read_blockseq(io_or_filepath) do |blockstr|
|
91
|
+
if blockstr =~ /^Linux/ && blockstr =~ /(\d{2})\/(\d{2})\/(\d{2})$/
|
92
|
+
# the first line
|
93
|
+
y = $~[3].to_i; m = $~[1].to_i; d = $~[2].to_i
|
94
|
+
date = Date.new(2000 + y, m, d)
|
95
|
+
next
|
96
|
+
else
|
97
|
+
# it's a data block, maybe
|
98
|
+
unless date
|
99
|
+
$stderr.puts "Cannot find date in your mpstat log. It was assumed today."
|
100
|
+
date = Date.today
|
101
|
+
end
|
102
|
+
|
103
|
+
result = Hash.new
|
104
|
+
rows = blockstr.lines.map(&:strip)
|
105
|
+
header = rows.shift.split
|
106
|
+
next if header.shift =~ /Average/
|
107
|
+
result[:labels] = header
|
108
|
+
time = nil
|
109
|
+
result[:data] = rows.map { |row|
|
110
|
+
vals = row.split
|
111
|
+
wallclock = vals.shift
|
112
|
+
unless time
|
113
|
+
unless wallclock =~ /(\d{2}):(\d{2}):(\d{2})/
|
114
|
+
raise RuntimeError.new("Cannot extract wallclock time from mpstat data")
|
115
|
+
end
|
116
|
+
time = Time.local(date.year, date.month, date.day,
|
117
|
+
$~[1].to_i, $~[2].to_i, $~[3].to_i)
|
118
|
+
if last_time && time < last_time
|
119
|
+
date += 1
|
120
|
+
time = Time.local(date.year, date.month, date.day,
|
121
|
+
$~[1].to_i, $~[2].to_i, $~[3].to_i)
|
122
|
+
end
|
123
|
+
result[:time] = time
|
124
|
+
last_time = time
|
125
|
+
end
|
126
|
+
if vals.size != result[:labels].size
|
127
|
+
raise RuntimeError.new("Invalid mpstat data")
|
128
|
+
end
|
129
|
+
vals.map{|val|
|
130
|
+
begin
|
131
|
+
Float(val)
|
132
|
+
rescue ArgumentError
|
133
|
+
val
|
134
|
+
end
|
135
|
+
}
|
136
|
+
}
|
137
|
+
result
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def self.read_csv(io_or_filepath, fs = ",", rs = nil, &proc)
|
143
|
+
if io_or_filepath.is_a?(String) && File.exists?(io_or_filepath)
|
144
|
+
io_or_filepath = File.open(io_or_filepath, "r")
|
145
|
+
end
|
146
|
+
|
147
|
+
result = []
|
148
|
+
proc ||= lambda{|row| row}
|
149
|
+
CSV.parse(io_or_filepath).each do |row|
|
150
|
+
ret = proc.call(row)
|
151
|
+
result.push ret if ret
|
152
|
+
end
|
153
|
+
result
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
data/lib/jkr/utils.rb
ADDED
@@ -0,0 +1,470 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'popen4'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require 'jkr/userutils'
|
7
|
+
|
8
|
+
class Barrier
|
9
|
+
def initialize(num)
|
10
|
+
@mux = Mutex.new
|
11
|
+
@cond = ConditionVariable.new
|
12
|
+
|
13
|
+
@num = num
|
14
|
+
@cur_num = num
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait()
|
18
|
+
@mux.lock
|
19
|
+
@cur_num -= 1
|
20
|
+
if @cur_num == 0
|
21
|
+
@cur_num = @num
|
22
|
+
@cond.broadcast
|
23
|
+
else
|
24
|
+
@cond.wait(@mux)
|
25
|
+
end
|
26
|
+
@mux.unlock
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Jkr
|
31
|
+
class Utils
|
32
|
+
def self.reserve_next_dir(dir, suffix = "")
|
33
|
+
dirs = Dir.glob("#{dir}#{File::SEPARATOR}???*")
|
34
|
+
max_num = -1
|
35
|
+
dirs.each do |dir|
|
36
|
+
if /\A[0-9]+/ =~ File.basename(dir)
|
37
|
+
max_num = [$~[0].to_i, max_num].max
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
num = max_num + 1
|
42
|
+
dir = "#{dir}#{File::SEPARATOR}" + sprintf("%03d%s", num, suffix)
|
43
|
+
FileUtils.mkdir(dir)
|
44
|
+
dir
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module PlanUtils
|
49
|
+
# info about processes spawned by me
|
50
|
+
def procdb
|
51
|
+
@procdb ||= Hash.new
|
52
|
+
end
|
53
|
+
def procdb_spawn(pid, command, owner_thread)
|
54
|
+
@procdb_mutex.synchronize do
|
55
|
+
self.procdb[pid] = {
|
56
|
+
:pid => pid,
|
57
|
+
:command => command,
|
58
|
+
:thread => owner_thread,
|
59
|
+
:status => nil
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
def procdb_waitpid(pid)
|
64
|
+
t = nil
|
65
|
+
@procdb_mutex.synchronize do
|
66
|
+
if self.procdb[pid]
|
67
|
+
t = self.procdb[pid][:thread]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
t.join if t
|
71
|
+
end
|
72
|
+
def procdb_resetpid(pid)
|
73
|
+
@procdb_mutex.synchronize do
|
74
|
+
if self.procdb[pid]
|
75
|
+
self.procdb.delete(pid)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
def procdb_update_status(pid, status)
|
80
|
+
@procdb_mutex.synchronize do
|
81
|
+
if self.procdb[pid]
|
82
|
+
self.procdb[pid][:status] = status
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
def procdb_get(pid)
|
87
|
+
@procdb_mutex.synchronize do
|
88
|
+
self.procdb[pid]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
def procdb_get_status(pid)
|
92
|
+
proc = self.procdb_get(pid)
|
93
|
+
proc && proc[:status]
|
94
|
+
end
|
95
|
+
def procdb_get_command(pid)
|
96
|
+
proc = self.procdb_get(pid)
|
97
|
+
proc && proc[:command]
|
98
|
+
end
|
99
|
+
|
100
|
+
def cmd(*args)
|
101
|
+
@procdb_mutex ||= Mutex.new
|
102
|
+
options = (if args.last.is_a? Hash
|
103
|
+
args.pop
|
104
|
+
else
|
105
|
+
{}
|
106
|
+
end)
|
107
|
+
options = {
|
108
|
+
:wait => true,
|
109
|
+
:timeout => 0,
|
110
|
+
:raise_failure => true,
|
111
|
+
:stdin => nil,
|
112
|
+
:stdout => [$stdout],
|
113
|
+
:stderr => [$stderr]
|
114
|
+
}.merge(options)
|
115
|
+
|
116
|
+
if options[:timeout] > 0 && ! options[:wait]
|
117
|
+
raise ArgumentError.new("cmd: 'wait' must be true if 'timeout' specified.")
|
118
|
+
end
|
119
|
+
|
120
|
+
start_time = Time.now
|
121
|
+
pid = nil
|
122
|
+
status = nil
|
123
|
+
args.flatten!
|
124
|
+
args.map!(&:to_s)
|
125
|
+
command = args.join(" ")
|
126
|
+
barrier = Barrier.new(2)
|
127
|
+
process_exited = false
|
128
|
+
|
129
|
+
t = Thread.new {
|
130
|
+
pipers = []
|
131
|
+
status = POpen4::popen4(command){|p_stdout, p_stderr, p_stdin, p_id|
|
132
|
+
pid = p_id
|
133
|
+
barrier.wait
|
134
|
+
stdouts = if options[:stdout].is_a? Array
|
135
|
+
options[:stdout]
|
136
|
+
else
|
137
|
+
[options[:stdout]]
|
138
|
+
end
|
139
|
+
stderrs = if options[:stderr].is_a? Array
|
140
|
+
options[:stderr]
|
141
|
+
else
|
142
|
+
[options[:stderr]]
|
143
|
+
end
|
144
|
+
pipers << Thread.new{
|
145
|
+
target = p_stdout
|
146
|
+
timeout_count = 0
|
147
|
+
while true
|
148
|
+
begin
|
149
|
+
if (ready = IO.select([target], [], [], 1))
|
150
|
+
ready.first.each do |fd|
|
151
|
+
buf = fd.read_nonblock(4096)
|
152
|
+
stdouts.each{|out| out.print buf}
|
153
|
+
end
|
154
|
+
Thread.exit if target.eof?
|
155
|
+
else
|
156
|
+
if process_exited
|
157
|
+
timeout_count += 1
|
158
|
+
if timeout_count > 5
|
159
|
+
target.close_read
|
160
|
+
Thread.exit
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
rescue IOError => err
|
165
|
+
if target.closed?
|
166
|
+
Thread.exit
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
}
|
171
|
+
pipers << Thread.new{
|
172
|
+
target = p_stderr
|
173
|
+
timeout_count = 0
|
174
|
+
while true
|
175
|
+
begin
|
176
|
+
if (ready = IO.select([target], [], [], 1))
|
177
|
+
ready.first.each do |fd|
|
178
|
+
buf = fd.read_nonblock(4096)
|
179
|
+
stderrs.each{|out| out.print buf}
|
180
|
+
end
|
181
|
+
Thread.exit if target.eof?
|
182
|
+
else
|
183
|
+
if process_exited
|
184
|
+
timeout_count += 1
|
185
|
+
if timeout_count > 5
|
186
|
+
target.close_read
|
187
|
+
Thread.exit
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
rescue IOError => err
|
192
|
+
if target.closed?
|
193
|
+
Thread.exit
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
}
|
198
|
+
if options[:stdin]
|
199
|
+
pipers << Thread.new{
|
200
|
+
target = options[:stdin]
|
201
|
+
timeout_count = 0
|
202
|
+
while true
|
203
|
+
begin
|
204
|
+
if (ready = IO.select([target], [], [], 1))
|
205
|
+
ready.first.each do |fd|
|
206
|
+
buf = fd.read_nonblock(4096)
|
207
|
+
p_stdin.print buf
|
208
|
+
end
|
209
|
+
else
|
210
|
+
if process_exited
|
211
|
+
timeout_count += 1
|
212
|
+
if timeout_count > 5
|
213
|
+
p_stdin.close_write
|
214
|
+
Thread.exit
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
rescue IOError => err
|
219
|
+
if target.closed?
|
220
|
+
Thread.exit
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
}
|
225
|
+
end
|
226
|
+
}
|
227
|
+
pipers.each{|t| t.join}
|
228
|
+
raise ArgumentError.new("Invalid command: #{command}") unless status
|
229
|
+
procdb_update_status(pid, status)
|
230
|
+
}
|
231
|
+
barrier.wait
|
232
|
+
procdb_spawn(pid, command, t)
|
233
|
+
timekeeper = nil
|
234
|
+
|
235
|
+
killed = false
|
236
|
+
timekeeper = nil
|
237
|
+
if options[:timeout] > 0
|
238
|
+
timekeeper = Thread.new do
|
239
|
+
sleep(options[:timeout])
|
240
|
+
begin
|
241
|
+
Process.kill(:INT, pid)
|
242
|
+
killed = true
|
243
|
+
rescue Errno::ESRCH # No such process
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
if options[:wait]
|
248
|
+
timekeeper.join if timekeeper
|
249
|
+
t.join
|
250
|
+
if (! killed) && options[:raise_failure] && status.exitstatus != 0
|
251
|
+
raise RuntimeError.new("'#{command}' failed.")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
while ! pid
|
255
|
+
sleep 0.001 # do nothing
|
256
|
+
end
|
257
|
+
|
258
|
+
pid
|
259
|
+
end
|
260
|
+
|
261
|
+
def with_process2(*args)
|
262
|
+
options = (if args.last.is_a? Hash
|
263
|
+
args.pop
|
264
|
+
else
|
265
|
+
{}
|
266
|
+
end )
|
267
|
+
options = {
|
268
|
+
:kill_on_exit => false
|
269
|
+
}.merge(options)
|
270
|
+
|
271
|
+
command = args.flatten.map(&:to_s).join(" ")
|
272
|
+
pid = Process.spawn(command)
|
273
|
+
|
274
|
+
err = nil
|
275
|
+
begin
|
276
|
+
yield
|
277
|
+
rescue Exception => e
|
278
|
+
err = e
|
279
|
+
end
|
280
|
+
|
281
|
+
if options[:kill_on_exit]
|
282
|
+
Process.kill(:INT, pid)
|
283
|
+
else
|
284
|
+
if err
|
285
|
+
begin
|
286
|
+
Process.kill(:TERM, pid)
|
287
|
+
rescue Exception
|
288
|
+
end
|
289
|
+
else
|
290
|
+
begin
|
291
|
+
status = Process.waitpid(pid)
|
292
|
+
p status
|
293
|
+
rescue Errno::ESRCH
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
raise err if err
|
298
|
+
end
|
299
|
+
|
300
|
+
def with_process(*args)
|
301
|
+
options = (if args.last.is_a? Hash
|
302
|
+
args.pop
|
303
|
+
else
|
304
|
+
{}
|
305
|
+
end )
|
306
|
+
options = {
|
307
|
+
:kill_on_exit => false
|
308
|
+
}.merge(options)
|
309
|
+
options[:wait] = false
|
310
|
+
|
311
|
+
args.push(options)
|
312
|
+
pid = cmd(*args)
|
313
|
+
|
314
|
+
err = nil
|
315
|
+
begin
|
316
|
+
yield
|
317
|
+
rescue Exception => e
|
318
|
+
err = e
|
319
|
+
end
|
320
|
+
|
321
|
+
if options[:kill_on_exit]
|
322
|
+
Process.kill(:INT, pid)
|
323
|
+
else
|
324
|
+
if err
|
325
|
+
begin
|
326
|
+
Process.kill(:TERM, pid)
|
327
|
+
rescue Exception
|
328
|
+
end
|
329
|
+
else
|
330
|
+
procdb_waitpid(pid)
|
331
|
+
status = procdb_get_status(pid)
|
332
|
+
unless status && status.exitstatus == 0
|
333
|
+
command = procdb_get_command(pid) || "Unknown command"
|
334
|
+
raise RuntimeError.new("'#{command}' failed.")
|
335
|
+
end
|
336
|
+
procdb_resetpid(pid)
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
raise err if err
|
341
|
+
end
|
342
|
+
|
343
|
+
def use_script(name)
|
344
|
+
name = name.to_s
|
345
|
+
name = name + ".rb" unless name =~ /\.rb$/
|
346
|
+
dir = @plan.jkr_env.jkr_script_dir
|
347
|
+
path = File.join(dir, name)
|
348
|
+
script = File.open(path, "r").read
|
349
|
+
self.instance_eval(script, path, 1)
|
350
|
+
true
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
class TrialUtils
|
355
|
+
def self.undef_routine_utils(plan)
|
356
|
+
plan.routine.binding.eval <<EOS
|
357
|
+
undef result_file
|
358
|
+
undef result_file_name
|
359
|
+
undef touch_result_file
|
360
|
+
undef with_result_file
|
361
|
+
EOS
|
362
|
+
end
|
363
|
+
|
364
|
+
def self.define_routine_utils(result_dir, plan, params)
|
365
|
+
line = __LINE__; src = <<EOS
|
366
|
+
def result_file_name(basename)
|
367
|
+
File.join(#{result_dir.inspect}, basename)
|
368
|
+
end
|
369
|
+
|
370
|
+
def result_file(basename, mode = "a+")
|
371
|
+
path = result_file_name(basename)
|
372
|
+
File.open(path, mode)
|
373
|
+
end
|
374
|
+
|
375
|
+
def touch_result_file(basename, options = {})
|
376
|
+
path = result_file_name(basename)
|
377
|
+
FileUtils.touch(path, options)
|
378
|
+
path
|
379
|
+
end
|
380
|
+
|
381
|
+
def with_result_file(basename, mode = "a+")
|
382
|
+
file = result_file(basename, mode)
|
383
|
+
err = nil
|
384
|
+
begin
|
385
|
+
yield(file)
|
386
|
+
rescue Exception => e
|
387
|
+
err = e
|
388
|
+
end
|
389
|
+
file.close
|
390
|
+
raise err if err
|
391
|
+
file.path
|
392
|
+
end
|
393
|
+
EOS
|
394
|
+
plan.routine.binding.eval(src, __FILE__, line)
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
class AnalysisUtils
|
399
|
+
def self.undef_analysis_utils(plan)
|
400
|
+
plan.analysis.binding.eval <<EOS
|
401
|
+
undef resultset
|
402
|
+
undef result_file
|
403
|
+
undef result_file_name
|
404
|
+
undef with_result_file
|
405
|
+
undef common_file
|
406
|
+
undef common_file_name
|
407
|
+
undef with_common_file
|
408
|
+
EOS
|
409
|
+
end
|
410
|
+
|
411
|
+
def self.define_analysis_utils(resultset_dir, plan)
|
412
|
+
line = __LINE__; src = <<EOS
|
413
|
+
def resultset()
|
414
|
+
dirs = Dir.glob(File.join(#{resultset_dir.inspect}, "*"))
|
415
|
+
dirs.map{|dir| File.basename dir}.select{|basename|
|
416
|
+
basename =~ /\\A\\d{3,}\\Z/
|
417
|
+
}
|
418
|
+
end
|
419
|
+
|
420
|
+
def result_file_name(num, basename)
|
421
|
+
if num.is_a? Integer
|
422
|
+
num = sprintf "%03d", num
|
423
|
+
end
|
424
|
+
File.join(#{resultset_dir.inspect}, num, basename)
|
425
|
+
end
|
426
|
+
|
427
|
+
def result_file(num, basename, mode = "r")
|
428
|
+
path = result_file_name(num, basename)
|
429
|
+
File.open(path, mode)
|
430
|
+
end
|
431
|
+
|
432
|
+
def common_file_name(basename)
|
433
|
+
File.join(#{resultset_dir.inspect}, basename)
|
434
|
+
end
|
435
|
+
|
436
|
+
def common_file(basename, mode = "r")
|
437
|
+
path = common_file_name(basename)
|
438
|
+
File.open(path, mode)
|
439
|
+
end
|
440
|
+
|
441
|
+
def with_common_file(basename, mode = "r")
|
442
|
+
file = common_file(basename, mode)
|
443
|
+
err = nil
|
444
|
+
begin
|
445
|
+
yield(file)
|
446
|
+
rescue Exception => e
|
447
|
+
err = e
|
448
|
+
end
|
449
|
+
file.close
|
450
|
+
raise err if err
|
451
|
+
file.path
|
452
|
+
end
|
453
|
+
|
454
|
+
def with_result_file(basename, mode = "r")
|
455
|
+
file = result_file(basename, mode)
|
456
|
+
err = nil
|
457
|
+
begin
|
458
|
+
yield(file)
|
459
|
+
rescue Exception => e
|
460
|
+
err = e
|
461
|
+
end
|
462
|
+
file.close
|
463
|
+
raise err if err
|
464
|
+
file.path
|
465
|
+
end
|
466
|
+
EOS
|
467
|
+
plan.routine.binding.eval(src, __FILE__, line)
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|
data/lib/jkr.rb
ADDED
data/test/test_jkr.rb
ADDED
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jkr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yuto HAYAMIZU
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-05-17 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rubyforge
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.0.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: hoe
|
27
|
+
type: :development
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.6.0
|
34
|
+
version:
|
35
|
+
description: FIX (describe your package)
|
36
|
+
email:
|
37
|
+
- y.hayamizu@gmail.com
|
38
|
+
executables:
|
39
|
+
- jkr
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- Manifest.txt
|
45
|
+
- README.txt
|
46
|
+
files:
|
47
|
+
- History.txt
|
48
|
+
- Manifest.txt
|
49
|
+
- README.txt
|
50
|
+
- Rakefile
|
51
|
+
- bin/jkr
|
52
|
+
- lib/jkr.rb
|
53
|
+
- lib/jkr/analysis.rb
|
54
|
+
- lib/jkr/env.rb
|
55
|
+
- lib/jkr/plan.rb
|
56
|
+
- lib/jkr/rake.rb
|
57
|
+
- lib/jkr/trial.rb
|
58
|
+
- lib/jkr/userutils.rb
|
59
|
+
- lib/jkr/utils.rb
|
60
|
+
- test/test_jkr.rb
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: http://github.com/hayamiz/jkr
|
63
|
+
post_install_message:
|
64
|
+
rdoc_options:
|
65
|
+
- --main
|
66
|
+
- README.txt
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0"
|
74
|
+
version:
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: "0"
|
80
|
+
version:
|
81
|
+
requirements: []
|
82
|
+
|
83
|
+
rubyforge_project: jkr
|
84
|
+
rubygems_version: 1.3.1
|
85
|
+
signing_key:
|
86
|
+
specification_version: 2
|
87
|
+
summary: FIX (describe your package)
|
88
|
+
test_files:
|
89
|
+
- test/test_jkr.rb
|