laborantin 0.0.14 → 0.0.21

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. data/INFO +1 -0
  2. data/README +148 -4
  3. data/Rakefile +73 -27
  4. data/TODO +27 -6
  5. data/bin/labor +2 -404
  6. data/lib/laborantin.rb +13 -26
  7. data/lib/laborantin/core/analysis.rb +231 -0
  8. data/lib/laborantin/core/command.rb +234 -0
  9. data/lib/laborantin/core/completeable.rb +30 -0
  10. data/lib/laborantin/core/configurable.rb +28 -0
  11. data/lib/laborantin/core/datable.rb +13 -0
  12. data/lib/laborantin/core/describable.rb +11 -0
  13. data/lib/laborantin/core/environment.rb +90 -52
  14. data/lib/laborantin/core/hookable.rb +20 -0
  15. data/lib/laborantin/core/monkey_patches.rb +0 -1
  16. data/lib/laborantin/core/multi_name.rb +25 -0
  17. data/lib/laborantin/core/parameter.rb +5 -12
  18. data/lib/laborantin/core/parameter_hash.rb +6 -2
  19. data/lib/laborantin/core/scenario.rb +93 -70
  20. data/lib/laborantin/core/table.rb +84 -0
  21. data/lib/laborantin/extra/commands/git.rb +40 -0
  22. data/lib/laborantin/extra/commands/git/check.rb +25 -0
  23. data/lib/laborantin/extra/commands/git/run.rb +100 -0
  24. data/lib/laborantin/extra/vectorial_product.rb +31 -0
  25. data/lib/laborantin/runner.rb +247 -0
  26. data/lib/laborantin/runner/commands/analyze.rb +58 -0
  27. data/lib/laborantin/runner/commands/cleanup.rb +40 -0
  28. data/lib/laborantin/runner/commands/complete.rb +111 -0
  29. data/lib/laborantin/runner/commands/config.rb +169 -0
  30. data/lib/laborantin/runner/commands/create.rb +61 -0
  31. data/lib/laborantin/runner/commands/describe.rb +215 -0
  32. data/lib/laborantin/runner/commands/find.rb +82 -0
  33. data/lib/laborantin/runner/commands/load_classes.rb +75 -0
  34. data/lib/laborantin/runner/commands/load_results.rb +143 -0
  35. data/lib/laborantin/runner/commands/note.rb +35 -0
  36. data/lib/laborantin/runner/commands/replay.rb +89 -0
  37. data/lib/laborantin/runner/commands/rm.rb +107 -0
  38. data/lib/laborantin/runner/commands/run.rb +131 -0
  39. data/lib/laborantin/runner/commands/scan.rb +77 -0
  40. metadata +45 -13
  41. data/bin/files/README.erb +0 -29
  42. data/bin/files/TODO.erb +0 -2
  43. data/bin/files/config/ftp.yaml.erb +0 -6
  44. data/bin/files/config/xmpp.yaml.erb +0 -7
  45. data/bin/files/environments/environment.rb.erb +0 -10
  46. data/bin/files/scenarii/scenario.rb.erb +0 -13
@@ -0,0 +1,84 @@
1
+ module Laborantin
2
+ class Table
3
+ attr_reader :name, :struct, :path
4
+ attr_accessor :separator, :comment, :header
5
+ def initialize(name, struct=nil, path=nil)
6
+ @name = name
7
+ @struct = struct
8
+ @path = path
9
+ @separator = ' '
10
+ @comment = '#'
11
+ @heaer = nil
12
+ yield self if block_given?
13
+ end
14
+
15
+ def fields
16
+ struct.members.map(&:to_s)
17
+ end
18
+
19
+ def header
20
+ @header || [comment, fields].flatten.join(separator)
21
+ end
22
+
23
+ class Filler < Proc
24
+ alias :<< :call
25
+ end
26
+
27
+ def fill(&blk)
28
+ File.open(path, 'w') do |f|
29
+ f.puts header if struct
30
+ filler = Filler.new do |val|
31
+ f.puts dump(val)
32
+ end
33
+ blk.call(filler)
34
+ end
35
+ end
36
+
37
+ def dump(obj, check=true)
38
+ strings = obj.to_a.map(&:to_s)
39
+ if check
40
+ if strings.find{|s| s.include?(separator)}
41
+ raise ArgumentError, "cannot unambiguously dump a value with the separator"
42
+ end
43
+ end
44
+ line = strings.join(separator)
45
+ if check
46
+ if line.start_with?(comment)
47
+ raise ArgumentError, "line starting with comment\n#{line}"
48
+ end
49
+ expected = struct.members.size
50
+ got = line.split(separator).size
51
+ if got != expected
52
+ raise ArgumentError, "ambiguous line: #{got} fields instead of #{expected} in \n#{line}"
53
+ end
54
+ end
55
+ line
56
+ end
57
+
58
+ def read(hash={})
59
+ if block_given?
60
+ File.open(path) do |f|
61
+ f.each_line do |l|
62
+ next if l.start_with?(comment)
63
+ strings = l.chomp.split(separator)
64
+ pairs = [struct.members, strings].transpose
65
+
66
+ atoms = pairs.map do |sym, val|
67
+ int = hash[sym]
68
+ if int
69
+ val.send(int)
70
+ else
71
+ val
72
+ end
73
+ end
74
+
75
+ yield struct.new(*atoms)
76
+ end
77
+ end
78
+ else
79
+ Enumerator.new(self, :read, hash)
80
+ end
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,40 @@
1
+
2
+ require 'git'
3
+ require 'laborantin/extra/commands/git/check'
4
+ require 'laborantin/extra/commands/git/run'
5
+
6
+ module Laborantin
7
+ module Commands
8
+ # A Git set of Commands, to enable it, pass to true the :extra :git flag.
9
+ module Git
10
+
11
+ HERE = '.'
12
+
13
+ # Just get a git object for current working dir (implementation is crappy, but will be refined later)
14
+ def self.git
15
+ @g ||= Object::Git.open(HERE)
16
+ end
17
+
18
+ # Get the current working directory branch.
19
+ def self.branch
20
+ self.git.current_branch
21
+ end
22
+
23
+ # Get the commit-id (long sha1) where current branch is pointing to.
24
+ def self.commit_id
25
+ self.git.revparse(branch)
26
+ end
27
+
28
+ # Returns true if current working dir is master.
29
+ def self.master_branch?
30
+ 'master' == branch
31
+ end
32
+
33
+ # Returns the Diff between current directory and the HEAD of current
34
+ # branch.
35
+ def self.diff
36
+ git.diff(commit_id, HERE)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+
2
+ module Laborantin
3
+ module Commands
4
+ module Git
5
+ class Check < Laborantin::Command
6
+ describe "Check if you're in a git repo, and master"
7
+ plumbery!
8
+
9
+ execute do
10
+ unless Laborantin::Commands::Git.master_branch?
11
+ puts "Cannot run unless you're in the master branch"
12
+ exit
13
+ end
14
+
15
+ if Laborantin::Commands::Git.diff.stats[:total][:files] > 0
16
+ puts "There are uncommited changes"
17
+ exit
18
+ end
19
+
20
+ Laborantin::Commands::Git
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,100 @@
1
+
2
+ module Laborantin
3
+ module Commands
4
+ module Git
5
+ class Run < Laborantin::Command
6
+
7
+ describe "like run, with git integration before"
8
+
9
+ option(:scenarii) do
10
+ describe "comma separated list of scenarios to describe"
11
+ short "-s"
12
+ long "--scenarii=OPTIONAL"
13
+ type Array
14
+ default []
15
+ end
16
+
17
+ option(:environments) do
18
+ describe "comma separated list of environments to describe"
19
+ short "-e"
20
+ long "--envs=OPTIONAL"
21
+ type Array
22
+ default []
23
+ end
24
+
25
+ option(:parameters) do
26
+ describe "filter for parameters (a hash as Ruby syntax code)"
27
+ short '-p'
28
+ long '--parameters=OPTIONAL'
29
+ type String
30
+ default ''
31
+ end
32
+
33
+ option(:analyze) do
34
+ describe "set this flag to analyze as you run"
35
+ short '-a'
36
+ long '--analyze'
37
+ default false
38
+ end
39
+
40
+ option(:force) do
41
+ describe "set this flag to prevent git branch verification"
42
+ short '-f'
43
+ long '--force'
44
+ default false
45
+ end
46
+
47
+ execute do
48
+
49
+ Laborantin::Commands::Git::Check.new().run unless opts[:force]
50
+
51
+ git = Laborantin::Commands::Git
52
+
53
+ puts "Running with #{git.branch} #{git.commit_id}"
54
+
55
+ # Parameters parsing
56
+ params = eval(opts[:parameters]) unless opts[:parameters].empty?
57
+ params.each_key{|k| params[k] = [params[k]].flatten} if params
58
+
59
+ # Environments and Scenarii filtering
60
+ envs = if opts[:environments].empty?
61
+ Laborantin::Environment.all
62
+ else
63
+ opts[:environments].map!{|e| e.camelize}
64
+ Laborantin::Environment.all.select{|e| opts[:environments].include? e.name}
65
+ end
66
+
67
+ scii = if opts[:scenarii].empty?
68
+ Laborantin::Scenario.all
69
+ else
70
+ opts[:scenarii].map!{|e| e.camelize}
71
+ Laborantin::Scenario.all.select{|e| opts[:scenarii].include? e.name}
72
+ end
73
+
74
+ # Actual run of experiments
75
+ envs.each do |eklass|
76
+ env = eklass.new
77
+ env.config[:git] = {:commit => git.commit_id}
78
+ #TODO: store revision
79
+ if env.valid?
80
+ env.prepare!
81
+ env.log "Running matching scenarii", :info #TODO: pass the logging+running in the env
82
+ scii.each do |sklass|
83
+ sklass.parameters.merge! params if params
84
+ env.log sklass.parameters.inspect
85
+ sklass.parameters.each_config do |cfg|
86
+ sc = sklass.new(env, cfg)
87
+ sc.prepare!
88
+ sc.perform!
89
+ sc.analyze! if opts[:analyze]
90
+ end
91
+ end
92
+ env.teardown!
93
+ env.log "Scenarii performed", :info
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module Laborantin
3
+ module VectorialProduct
4
+ def vectors
5
+ @vectors ||= {}
6
+ end
7
+
8
+ def vector_file(sym)
9
+ blk = lambda {|f| yield f}
10
+ if sym == :__raw__
11
+ raw_result_file &blk
12
+ else
13
+ product_file(sym, &blk)
14
+ end
15
+ end
16
+
17
+ def vector(sym=:__raw__)
18
+ vectors[sym] ||= read_vector(sym)
19
+ end
20
+
21
+ def read_vector(sym)
22
+ vals = []
23
+ vector_file(sym) do |file|
24
+ file.each_line do |line|
25
+ vals << line.chomp.to_f
26
+ end
27
+ end
28
+ vals
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,247 @@
1
+ #runner.rb
2
+
3
+ =begin
4
+
5
+ This file is part of Laborantin.
6
+
7
+ Laborantin is free software: you can redistribute it and/or modify
8
+ it under the terms of the GNU General Public License as published by
9
+ the Free Software Foundation, either version 3 of the License, or
10
+ (at your option) any later version.
11
+
12
+ Laborantin is distributed in the hope that it will be useful,
13
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ GNU General Public License for more details.
16
+
17
+ You should have received a copy of the GNU General Public License
18
+ along with Laborantin. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ Copyright (c) 2009, Lucas Di Cioccio
21
+
22
+ =end
23
+
24
+ require 'laborantin/runner/commands/load_classes'
25
+ require 'laborantin/runner/commands/load_results'
26
+ require 'laborantin/runner/commands/create'
27
+ require 'laborantin/runner/commands/complete'
28
+ require 'laborantin/runner/commands/describe'
29
+ require 'laborantin/runner/commands/run'
30
+ require 'laborantin/runner/commands/find'
31
+ require 'laborantin/runner/commands/scan'
32
+ require 'laborantin/runner/commands/analyze'
33
+ require 'laborantin/runner/commands/replay'
34
+ require 'laborantin/runner/commands/note'
35
+ require 'laborantin/runner/commands/cleanup'
36
+ require 'laborantin/runner/commands/rm'
37
+ require 'laborantin/runner/commands/config'
38
+ require 'optparse'
39
+ require 'find'
40
+ require 'yaml'
41
+ require 'singleton'
42
+ require 'laborantin/core/configurable'
43
+
44
+ module Laborantin
45
+
46
+ class Runner
47
+ include Metaprog::Configurable
48
+ include Singleton
49
+ # The configuration of the Runner, a hash serialized in the laborantin.yaml
50
+ # file.
51
+ attr_accessor :config
52
+
53
+ # The root_dir is the internal name for the working directory of a
54
+ # Laborantin's project.
55
+ attr_accessor :root_dir
56
+
57
+ # Initializes the root_dir with the current working directory of the shell
58
+ # (i.e. '.').
59
+ def initialize
60
+ @root_dir = File.expand_path('.')
61
+ end
62
+
63
+ # Provides a shortcut for building the correct directory path inside the root_dir.
64
+ # Returns a string.
65
+ # Does not check if the directory exists or is readable or anything.
66
+ # e.g. dir(:results) or dir('results') to build the path to the result dir.
67
+ def dir(*sym)
68
+ File.join(root_dir, sym.map{|s| s.to_s})
69
+ end
70
+
71
+ def resultdir
72
+ dir(:results)
73
+ end
74
+
75
+ def user_laborantin_dir
76
+ File.join(File.expand_path('~'), '.laborantin')
77
+ end
78
+
79
+ def file(dir, sym)
80
+ File.join(dir, sym)
81
+ end
82
+
83
+ def config_path
84
+ file(dir(:config), 'laborantin.yaml')
85
+ end
86
+
87
+ def load_dir(path)
88
+ # Verify the presence of the dir, needed for backward
89
+ # compatibility
90
+ if File.directory?(path)
91
+ Object::Find.find(path) do |file|
92
+ if File.extname(file) == '.rb'
93
+ require file
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ def load_user_commands
100
+ dir = File.join(user_laborantin_dir, 'commands')
101
+ load_dir(dir)
102
+ end
103
+
104
+ # Load the ruby files from the dirname directory of a Laborantin project.
105
+ # Current implementation require all .rb files found in the commands directory.
106
+ def load_local_dir(dirname)
107
+ d = dir(dirname)
108
+ load_dir(d)
109
+ end
110
+
111
+ def load_commands
112
+ load_local_dir(:commands)
113
+ end
114
+
115
+ def load_environments
116
+ load_local_dir(:environments)
117
+ end
118
+
119
+ def load_scenarii
120
+ load_local_dir(:scenarii)
121
+ end
122
+
123
+ def load_analyses
124
+ load_local_dir(:analyses)
125
+ end
126
+
127
+ def extra_dir
128
+ File.join('laborantin', 'extra')
129
+ end
130
+
131
+ def load_extra(what, name)
132
+ require File.join(extra_dir, what.to_s, name.to_s)
133
+ end
134
+
135
+ def load_extra_commands
136
+ if config and config[:extra].is_a? Hash
137
+ config[:extra].each_pair do |name, val|
138
+ load_extra(:commands, name) if val
139
+ end
140
+ end
141
+ end
142
+
143
+ # Prepare a Runner by loading the configuration and the extra commands.
144
+ def prepare
145
+ load_config!
146
+ load_user_commands
147
+ load_commands
148
+ load_environments
149
+ load_scenarii
150
+ load_analyses
151
+ load_extra_commands
152
+ end
153
+
154
+ end
155
+
156
+ class CliRunner < Runner
157
+
158
+ def command_for_argv(argv)
159
+ Command.sort_by{|c| - argv_klass_name(c).length}.find do |c|
160
+ invokation = argv_klass_name(c)
161
+ # does the first words on the CLI match this command's invokation?
162
+ (argv.slice(0, invokation.size) == invokation)
163
+ end
164
+ end
165
+
166
+ def argv_klass_name(command_klass)
167
+ ary = command_klass.command_name.split('::').map{|s| s.duck_case}
168
+
169
+ # Strip our default command path
170
+ ['laborantin', 'commands'].each do |prefix|
171
+ if ary.first == prefix
172
+ ary = ary[1 .. -1]
173
+ end
174
+ end
175
+
176
+ ary
177
+ end
178
+
179
+ def cli_klass_name(command_klass)
180
+ argv_klass_name(command_klass).join(' ')
181
+ end
182
+
183
+ def parse_opts(argv, klass)
184
+ extra_opts = {}
185
+
186
+ parser = OptionParser.new do |opt|
187
+ opt.banner = "Usage: #{File.basename($0)} #{cli_klass_name(klass)} [options...] [args...]"
188
+ opt.banner << "\n" + klass.description
189
+ opt.on_tail('-h', '--help', "show this help and exits") {|val| puts opt ; exit}
190
+ klass.options.each do |arg|
191
+ opt.on(arg.cli_short, arg.cli_long, arg.description_with_default, arg.default_type) {|val| extra_opts[arg.name] = val}
192
+ end
193
+ end
194
+
195
+ remaining_args = parser.parse!(argv)
196
+
197
+ return remaining_args, extra_opts
198
+ end
199
+
200
+ def print_generic_help
201
+ proposed = Command.reject{|cmd| cmd.plumbery?}
202
+ parser = OptionParser.new do |opt|
203
+ opt.banner = "Usage: #{File.basename($0)} <command> [options] [args...]\n"
204
+ opt.banner << "Run #{File.basename($0)} <command> --help to have help on a command\n"
205
+ opt.banner << "Known commands are: \n"
206
+ opt.banner << proposed.map do |klass|
207
+ line = klass.description.split("\n").first.chomp
208
+ "\t #{cli_klass_name(klass)} \n\t\t #{line}"
209
+ end.join("\n")
210
+ end
211
+ puts parser
212
+ end
213
+
214
+ # Actually runs the Runner.
215
+ # This method interprets cli as a command line where cli only contains the arguments
216
+ # (i.e., without the executable name).
217
+ # The arguments are splitted as separated by splitter.
218
+ def run_cli(cli, splitter=' ')
219
+ run_argv(cli.split(splitter))
220
+ end
221
+
222
+ # Actually runs the Runner, interpreting argv like an ARGV array of strings.
223
+ def run_argv(arguments)
224
+ argv = arguments.dup
225
+ prepare
226
+ cmd_klass = command_for_argv(argv)
227
+ if cmd_klass
228
+ # removes the heading of argv (i.e. the part corresponding to the class name in most of the cases)
229
+ argv_klass_name(cmd_klass).size.times do
230
+ argv.shift()
231
+ end
232
+
233
+ begin
234
+ args, opts = parse_opts(argv, cmd_klass)
235
+ cmd = cmd_klass.new(self)
236
+ cmd.run(args, opts)
237
+ rescue OptionParser::InvalidOption => err
238
+ puts err.message
239
+ puts "use 'labor #{argv_klass_name(cmd_klass)} --help' for help"
240
+ end
241
+ else
242
+ print_generic_help
243
+ end
244
+ end
245
+ end
246
+ end
247
+