miga-base 0.4.1.0 → 0.4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/bin/miga +2 -244
  3. data/lib/miga/cli/action/about.rb +44 -0
  4. data/lib/miga/cli/action/add.rb +139 -0
  5. data/lib/miga/cli/action/add_result.rb +26 -0
  6. data/lib/miga/cli/action/console.rb +19 -0
  7. data/lib/miga/cli/action/daemon.rb +74 -0
  8. data/lib/miga/cli/action/date.rb +18 -0
  9. data/lib/miga/cli/action/doctor.rb +210 -0
  10. data/lib/miga/cli/action/edit.rb +24 -0
  11. data/lib/miga/cli/action/files.rb +31 -0
  12. data/lib/miga/cli/action/find.rb +48 -0
  13. data/lib/miga/cli/action/generic.rb +44 -0
  14. data/lib/miga/cli/action/get.rb +132 -0
  15. data/lib/miga/cli/action/init.rb +343 -0
  16. data/lib/miga/cli/action/ln.rb +42 -0
  17. data/lib/miga/cli/action/ls.rb +55 -0
  18. data/lib/miga/cli/action/ncbi_get.rb +218 -0
  19. data/lib/miga/cli/action/new.rb +45 -0
  20. data/lib/miga/cli/action/next_step.rb +27 -0
  21. data/lib/miga/cli/action/plugins.rb +28 -0
  22. data/lib/miga/cli/action/rm.rb +25 -0
  23. data/lib/miga/cli/action/run.rb +39 -0
  24. data/lib/miga/cli/action/stats.rb +140 -0
  25. data/lib/miga/cli/action/summary.rb +49 -0
  26. data/lib/miga/cli/action/tax_dist.rb +102 -0
  27. data/lib/miga/cli/action/tax_index.rb +47 -0
  28. data/lib/miga/cli/action/tax_set.rb +59 -0
  29. data/lib/miga/cli/action/tax_test.rb +77 -0
  30. data/lib/miga/cli/action.rb +66 -0
  31. data/lib/miga/cli/base.rb +90 -0
  32. data/lib/miga/cli.rb +426 -0
  33. data/lib/miga/project/result.rb +14 -6
  34. data/lib/miga/remote_dataset.rb +1 -1
  35. data/lib/miga/tax_index.rb +5 -4
  36. data/lib/miga/taxonomy/base.rb +63 -0
  37. data/lib/miga/taxonomy.rb +87 -92
  38. data/lib/miga/version.rb +6 -6
  39. data/test/taxonomy_test.rb +49 -9
  40. data/utils/distance/commands.rb +11 -11
  41. data/utils/distance/pipeline.rb +5 -5
  42. metadata +43 -49
  43. data/actions/about.rb +0 -43
  44. data/actions/add.rb +0 -129
  45. data/actions/add_result.rb +0 -30
  46. data/actions/daemon.rb +0 -55
  47. data/actions/date.rb +0 -14
  48. data/actions/doctor.rb +0 -201
  49. data/actions/edit.rb +0 -33
  50. data/actions/files.rb +0 -43
  51. data/actions/find.rb +0 -41
  52. data/actions/get.rb +0 -105
  53. data/actions/init.rb +0 -301
  54. data/actions/ln.rb +0 -47
  55. data/actions/ls.rb +0 -61
  56. data/actions/ncbi_get.rb +0 -192
  57. data/actions/new.rb +0 -44
  58. data/actions/next_step.rb +0 -33
  59. data/actions/plugins.rb +0 -25
  60. data/actions/rm.rb +0 -29
  61. data/actions/run.rb +0 -45
  62. data/actions/stats.rb +0 -149
  63. data/actions/summary.rb +0 -57
  64. data/actions/tax_dist.rb +0 -106
  65. data/actions/tax_index.rb +0 -46
  66. data/actions/tax_set.rb +0 -63
  67. data/actions/tax_test.rb +0 -80
@@ -0,0 +1,66 @@
1
+ # @package MiGA
2
+ # @license Artistic-2.0
3
+
4
+ require 'miga/cli'
5
+
6
+ ##
7
+ # An action to be performed by the CLI. This is a generic class to be extended
8
+ # by MiGA::Cli::Action::* classes. Do not attempt creating directly with +new+,
9
+ # use instead the MiGA::Cli::Action.load interface.
10
+ class MiGA::Cli::Action < MiGA::MiGA
11
+
12
+ class << self
13
+ def load(task, cli)
14
+ require "miga/cli/action/#{task}"
15
+ camel = task.to_s.gsub(/(?:_|^)(\S)/, &:upcase).delete('_')
16
+ klass = Object.const_get("MiGA::Cli::Action::#{camel}")
17
+ klass.new(cli)
18
+ end
19
+ end
20
+
21
+ attr_accessor :cli
22
+
23
+ def initialize(cli)
24
+ @cli = cli
25
+ end
26
+
27
+ ##
28
+ # Launch the sequence
29
+ def launch
30
+ empty_action if cli.argv.empty?
31
+ parse_cli
32
+ perform
33
+ complete
34
+ end
35
+
36
+ ##
37
+ # Parse the CLI object
38
+ def parse_cli
39
+ raise "Undefined interface for the command line of #{cli.task}"
40
+ end
41
+
42
+ ##
43
+ # Perform the action
44
+ def perform
45
+ raise "Undefined action for the command line of #{cli.task}"
46
+ end
47
+
48
+ ##
49
+ # Complete the action
50
+ def complete
51
+ cli.say 'Done'
52
+ end
53
+
54
+ ##
55
+ # Name of the action, as referred to by the CLI
56
+ def name
57
+ camel = self.class.to_s.gsub(/.*::/,'')
58
+ camel.gsub(/(\S)([A-Z])/,'\1_\2').downcase
59
+ end
60
+
61
+ ##
62
+ # What to do when cli.argv is empty
63
+ def empty_action
64
+ cli.argv << '-h'
65
+ end
66
+ end
@@ -0,0 +1,90 @@
1
+ # @package MiGA
2
+ # @license Artistic-2.0
3
+
4
+ module MiGA::Cli::Base
5
+
6
+ @@TASK_DESC = {
7
+ generic: 'MiGA: The Microbial Genomes Atlas',
8
+ # Projects
9
+ new: 'Creates an empty MiGA project',
10
+ about: 'Displays information about a MiGA project',
11
+ plugins: 'Lists or (un)installs plugins in a MiGA project',
12
+ doctor: 'Performs consistency checks on a MiGA project',
13
+ # Datasets
14
+ add: 'Creates a dataset in a MiGA project',
15
+ get: 'Downloads a dataset from public databases into a MiGA project',
16
+ ncbi_get: 'Downloads all genomes in a taxon from NCBI into a MiGA project',
17
+ rm: 'Removes a dataset from an MiGA project',
18
+ find: 'Finds unregistered datasets based on result files',
19
+ ln: 'Link datasets (including results) from one project to another',
20
+ ls: 'Lists all registered datasets in an MiGA project',
21
+ # Results
22
+ add_result: 'Registers a result',
23
+ stats: 'Extracts statistics for the given result',
24
+ files: 'Lists registered files from the results of a dataset or project',
25
+ run: 'Executes locally one step analysis producing the given result',
26
+ summary: 'Generates a summary table for the statistics of all datasets',
27
+ next_step: 'Returns the next task to run in a dataset or project',
28
+ # Objects (Datasets or Projects)
29
+ edit: 'Edits the metadata of a dataset or project',
30
+ # System
31
+ init: 'Initialize MiGA to process new projects',
32
+ daemon: 'Controls the daemon of a MiGA project',
33
+ date: 'Returns the current date in standard MiGA format',
34
+ console: 'Opens an IRB console with MiGA',
35
+ # Taxonomy
36
+ tax_set: 'Registers taxonomic information for datasets',
37
+ tax_test: 'Returns test of taxonomic distributions for query datasets',
38
+ tax_index: 'Creates a taxonomy-indexed list of the datasets',
39
+ tax_dist: 'Estimates distributions of distance by taxonomy',
40
+ }
41
+
42
+ @@TASK_ALIAS = {
43
+ # Projects
44
+ create_project: :new,
45
+ project_info: :about,
46
+ # Datasets
47
+ create_dataset: :add,
48
+ download_dataset: :get,
49
+ unlink_dataset: :rm,
50
+ find_datasets: :find,
51
+ import_datasets: :ln,
52
+ list_datasets: :ls,
53
+ # Results
54
+ result_stats: :stats,
55
+ list_files: :files,
56
+ run_local: :run,
57
+ sum_stats: :summary,
58
+ next_task: :next_step,
59
+ # Objects
60
+ update_metadata: :edit,
61
+ # System
62
+ c: :console,
63
+ # Taxonomy
64
+ add_taxonomy: :tax_set,
65
+ test_taxonomy: :tax_test,
66
+ index_taxonomy: :tax_index,
67
+ tax_distributions: :tax_dist,
68
+ }
69
+
70
+ @@TASK_ALIAS.each do |nick, task|
71
+ @@TASK_DESC[task] = ((@@TASK_DESC[task] =~ /\(alias: .*\)\./) ?
72
+ @@TASK_DESC[task].sub(/\)\.$/, ", #{nick}).") :
73
+ @@TASK_DESC[task].sub(/\.$/, " (alias: #{nick}).")
74
+ )
75
+ end
76
+
77
+ @@EXECS = @@TASK_DESC.keys
78
+
79
+ end
80
+
81
+ class MiGA::Cli < MiGA::MiGA
82
+
83
+ include MiGA::Cli::Base
84
+
85
+ class << self
86
+ def TASK_DESC; @@TASK_DESC end
87
+ def TASK_ALIAS; @@TASK_ALIAS end
88
+ def EXECS; @@EXECS end
89
+ end
90
+ end
data/lib/miga/cli.rb ADDED
@@ -0,0 +1,426 @@
1
+ # @package MiGA
2
+ # @license Artistic-2.0
3
+
4
+ require 'miga/project'
5
+ require 'optparse'
6
+
7
+ ##
8
+ # MiGA Command Line Interface API.
9
+ class MiGA::Cli < MiGA::MiGA
10
+
11
+ require 'miga/cli/base'
12
+ require 'miga/cli/action'
13
+
14
+ ##
15
+ # Task to execute, a symbol
16
+ attr_accessor :task
17
+
18
+ ##
19
+ # The CLI parameters (except the task), and Array of String
20
+ attr_accessor :argv
21
+
22
+ ##
23
+ # Action to launch, an object inheriting from MiGA::Cli::Action
24
+ attr_accessor :action
25
+
26
+ ##
27
+ # If files are expected after the parameters, a boolean
28
+ attr_accessor :expect_files
29
+
30
+ ##
31
+ # Files passed after all other options, if +#expect_files = true+
32
+ attr_accessor :files
33
+
34
+ ##
35
+ # If an operation verb preceding all other arguments is to be expected
36
+ attr_accessor :expect_operation
37
+
38
+ ##
39
+ # Interactivity with the user is expected
40
+ attr_accessor :interactive
41
+
42
+ ##
43
+ # Operation preceding all other options, if +#expect_operation = true+
44
+ attr_accessor :operation
45
+
46
+ ##
47
+ # Include common options, a boolean (true by default)
48
+ attr_writer :opt_common
49
+
50
+ ##
51
+ # Default values as a Hash
52
+ attr_accessor :defaults
53
+
54
+ ##
55
+ # Parsed values as a Hash
56
+ attr_reader :data
57
+
58
+ def initialize(argv)
59
+ @data = {}
60
+ @defaults = {verbose: false, tabular: false}
61
+ @opt_common = true
62
+ @objects = {}
63
+ if argv[0].nil? or argv[0].to_s[0] == '-'
64
+ @task = :generic
65
+ else
66
+ @task = argv.shift.to_sym
67
+ @task = @@TASK_ALIAS[task] unless @@TASK_ALIAS[task].nil?
68
+ end
69
+ @argv = argv
70
+ reset_action
71
+ end
72
+
73
+ ##
74
+ # Send +par+ to $stdout, ensuring new line at the end
75
+ def puts(*par)
76
+ $stdout.puts(*par)
77
+ end
78
+
79
+ ##
80
+ # Send +par+ to $stdout as is
81
+ def print(*par)
82
+ $stdout.print(*par)
83
+ end
84
+
85
+ ##
86
+ # Display a table with headers +header+ and contents +values+, both Array
87
+ def table(header, values)
88
+ self.puts MiGA.tabulate(header, values, self[:tabular])
89
+ end
90
+
91
+ ##
92
+ # Send +par+ to $stderr (ensuring new line at the end), iff --verbose.
93
+ # Date/time each line.
94
+ def say(*par)
95
+ return unless self[:verbose]
96
+ par.map! { |i| "[#{Time.now}] #{i}" }
97
+ $stderr.puts(*par)
98
+ end
99
+
100
+ ##
101
+ # Reports the advance of a task at +step+ (String), the +n+ out of +total+
102
+ # The report goes to $stderr iff --verborse
103
+ def advance(step, n = 0, total = nil)
104
+ return unless self[:verbose]
105
+ adv = total.nil? ? '' : ('%.1f%% (%d/%d)' % [n/total, n, total])
106
+ $stderr.print("[%s] %s %s \r" % [Time.now, step, adv])
107
+ end
108
+
109
+ ##
110
+ # Ask a question +question+ to the user (requires +#interactive = true+)
111
+ # The +default+ is used if the answer is empty
112
+ # The +answers+ are supported values, unless nil
113
+ # If --auto, all questions are anwered with +default+ unless +force+
114
+ def ask_user(question, default = nil, answers = nil, force = false)
115
+ ans = " (#{answers.join(' / ')})" unless answers.nil?
116
+ dft = " [#{default}]" unless default.nil?
117
+ print "#{question}#{ans}#{dft} > "
118
+ if self[:auto] && !force
119
+ puts ''
120
+ else
121
+ y = gets.chomp
122
+ end
123
+ y = default.to_s if y.nil? or y.empty?
124
+ unless answers.nil? or answers.map(&:to_s).include?(y)
125
+ warn "Answer not recognized: '#{y}'"
126
+ return ask_user(question, default, answers, force)
127
+ end
128
+ y
129
+ end
130
+
131
+ ##
132
+ # Set default values in the Hash +hsh+
133
+ def defaults=(hsh)
134
+ hsh.each{ |k,v| @defaults[k] = v }
135
+ end
136
+
137
+ ##
138
+ # Access parsed data
139
+ def [](k)
140
+ k = k.to_sym
141
+ @data[k].nil? ? @defaults[k] : @data[k]
142
+ end
143
+
144
+ ##
145
+ # Set parsed data
146
+ def []=(k, v)
147
+ @data[k.to_sym] = v
148
+ end
149
+
150
+ ##
151
+ # Redefine #action based on #task
152
+ def reset_action
153
+ @action = nil
154
+ if @@EXECS.include? task
155
+ @action = Action.load(task, self)
156
+ else
157
+ warn "No action set for #{task}"
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Perform the task requested (see #task)
163
+ def launch
164
+ begin
165
+ raise "See `miga -h`" if action.nil?
166
+ action.launch
167
+ rescue => err
168
+ $stderr.puts "Exception: #{err}"
169
+ $stderr.puts ''
170
+ err.backtrace.each { |l| $stderr.puts "DEBUG: #{l}" }
171
+ err
172
+ end
173
+ end
174
+
175
+ ##
176
+ # Parse the #argv parameters
177
+ def parse(&fun)
178
+ if expect_operation
179
+ @operation = @argv.shift unless argv.first =~ /^-/
180
+ end
181
+ OptionParser.new do |opt|
182
+ banner(opt)
183
+ fun[opt]
184
+ opt_common(opt)
185
+ end.parse!(@argv)
186
+ if expect_files
187
+ @files = argv
188
+ end
189
+ end
190
+
191
+ ##
192
+ # Send MiGA's banner to OptionParser +opt+
193
+ def banner(opt)
194
+ usage = "Usage: miga #{action.name}"
195
+ usage += ' {operation}' if expect_operation
196
+ usage += ' [options]'
197
+ usage += ' {FILES...}' if expect_files
198
+ opt.banner = "\n#{task_description}\n\n#{usage}\n"
199
+ opt.separator ''
200
+ end
201
+
202
+ ##
203
+ # Common options at the end of most actions, passed to OptionParser +opt+
204
+ # No action is performed if +#opt_common = false+ is passed
205
+ # Executes only once, unless +#opt_common = true+ is passed between calls
206
+ def opt_common(opt)
207
+ return unless @opt_common
208
+ opt.on(
209
+ '--auto',
210
+ 'Accept all defaults as answers'
211
+ ){ |v| cli[:auto] = v } if interactive
212
+ opt.on(
213
+ '-v', '--verbose',
214
+ 'Print additional information to STDERR'
215
+ ){ |v| self[:verbose] = v }
216
+ opt.on(
217
+ '-d', '--debug INT', Integer,
218
+ 'Print debugging information to STDERR (1: debug, 2: trace)'
219
+ ){ |v| (v > 1) ? MiGA.DEBUG_TRACE_ON : MiGA.DEBUG_ON }
220
+ opt.on(
221
+ '-h', '--help',
222
+ 'Display this screen'
223
+ ){ puts opt ; exit }
224
+ opt.separator ''
225
+ self.opt_common = false
226
+ end
227
+
228
+ ##
229
+ # Options to load an object passed to OptionParser +opt+, as determined
230
+ # by +what+ an Array with any combination of:
231
+ # - :project To require a project
232
+ # - :dataset To require a dataset
233
+ # - :dataset_opt To allow (optionally) a dataset
234
+ # - :dataset_type To allow (optionally) a type of dataset
235
+ # - :dataset_type_req To require a type of dataset
236
+ # - :project_type To allow (optionally) a type of project
237
+ # - :project_type_req To require a type of project
238
+ # - :result To require a type of project or dataset result
239
+ # - :result_dataset To require a type of dataset result
240
+ # - :result_project To require a type of project result
241
+ def opt_object(opt, what = [:project, :dataset])
242
+ opt.on(
243
+ '-P', '--project PATH',
244
+ '(Mandatory) Path to the project'
245
+ ){ |v| self[:project] = v } if what.include? :project
246
+ opt.on(
247
+ '-D', '--dataset STRING',
248
+ (what.include?(:dataset) ? '(Mandatory) ' : '') + 'Name of the dataset'
249
+ ){ |v| self[:dataset] = v } if what.include? :dataset or
250
+ what.include? :dataset_opt
251
+ opt.on(
252
+ '-D', '--dataset STRING',
253
+ 'Name of the dataset'
254
+ ){ |v| self[:dataset] = v } if what.include? :dataset_opt
255
+ opt.on(
256
+ '-t', '--type STRING',
257
+ (what.include?(:dataset_type_req) ? '(Mandatory) ' : '') +
258
+ 'Type of dataset. Recognized types include:',
259
+ *Dataset.KNOWN_TYPES.map{ |k,v| "~ #{k}: #{v[:description]}" }
260
+ ){ |v| self[:type] = v.downcase.to_sym } if what.include? :dataset_type or
261
+ what.include? :dataset_type_req
262
+ opt.on(
263
+ '-t', '--type STRING',
264
+ (what.include?(:project_type_req) ? '(Mandatory) ' : '') +
265
+ 'Type of project. Recognized types include:',
266
+ *Project.KNOWN_TYPES.map{ |k,v| "~ #{k}: #{v[:description]}"}
267
+ ){ |v| self[:type] = v.downcase.to_sym } if what.include? :project_type or
268
+ what.include? :project_type_req
269
+ opt.on(
270
+ '-r', '--result STRING',
271
+ '(Mandatory) Name of the result',
272
+ 'Recognized names for dataset-specific results include:',
273
+ *Dataset.RESULT_DIRS.keys.map{|n| " ~ #{n}"},
274
+ 'Recognized names for project-wide results include:',
275
+ *Project.RESULT_DIRS.keys.map{|n| " ~ #{n}"}
276
+ ){ |v| self[:result] = v.downcase.to_sym } if what.include? :result
277
+ opt.on(
278
+ '-r', '--result STRING',
279
+ '(Mandatory) Name of the result, one of:',
280
+ *Dataset.RESULT_DIRS.keys.map{|n| " ~ #{n}"}
281
+ ){ |v| self[:result] = v.downcase.to_sym } if what.include? :result_dataset
282
+ opt.on(
283
+ '-r', '--result STRING',
284
+ '(Mandatory) Name of the result, one of:',
285
+ *Project.RESULT_DIRS.keys.map{|n| " ~ #{n}"}
286
+ ){ |v| self[:result] = v.downcase.to_sym } if what.include? :result_project
287
+ end
288
+
289
+ ##
290
+ # Options to filter a list of datasets passed to OptionParser +opt+,
291
+ # as determined by +what+ an Array with any combination of:
292
+ # - :ref To filter by reference (--ref) or query (--no-ref)
293
+ # - :multi To filter by multiple (--multi) or single (--no-multi) species
294
+ # - :active To filter by active (--active) or inactive (--no-active)
295
+ # - :taxonomy To filter by taxonomy (--taxonomy)
296
+ # The "k-th" filter (--dataset-k) is always included
297
+ def opt_filter_datasets(opt, what = [:ref, :multi, :active, :taxonomy])
298
+ opt.on(
299
+ '--[no-]ref',
300
+ 'Use only reference (or only non-reference) datasets'
301
+ ){ |v| self[:ref] = v } if what.include? :ref
302
+ opt.on(
303
+ '--[no-]multi',
304
+ 'Use only multi-species (or only single-species) datasets'
305
+ ){ |v| self[:multi] = v } if what.include? :multi
306
+ opt.on(
307
+ '--[no-]active',
308
+ 'Use only active (or inactive) datasets'
309
+ ){ |v| self[:active] = v } if what.include? :active
310
+ opt.on(
311
+ '-t', '--taxonomy RANK:TAXON',
312
+ 'Filter by taxonomy'
313
+ ){ |v| self[:taxonomy] = Taxonomy.new(v) } if what.include? :taxonomy
314
+ opt.on(
315
+ '--dataset-k INTEGER', Integer,
316
+ 'Use only the k-th dataset in the list'
317
+ ){ |v| self[:dataset_k] = v }
318
+ end
319
+
320
+ ##
321
+ # Ensure that these parameters have been passed to the CLI, as defined by
322
+ # +par+, a Hash with object names as keys and parameter flag as values.
323
+ # If missing, raise an error with message +msg+
324
+ def ensure_par(req, msg = '%<name>s is mandatory: please provide %<flag>s')
325
+ req.each do |k,v|
326
+ raise (msg % {name: k, flag: v}) if self[k].nil?
327
+ end
328
+ end
329
+
330
+ ##
331
+ # Ensure that "type" is passed and valid for the given +klass+
332
+ def ensure_type(klass)
333
+ ensure_par(type: '-t')
334
+ if klass.KNOWN_TYPES[self[:type]].nil?
335
+ raise "Unrecognized type: #{self[:type]}"
336
+ end
337
+ end
338
+
339
+ ##
340
+ # Get the project defined in the CLI by parameter +name+ and +flag+
341
+ def load_project(name = :project, flag = '-P')
342
+ return @objects[name] unless @objects[name].nil?
343
+ ensure_par(name => flag)
344
+ say "Loading project: #{self[name]}"
345
+ @objects[name] = Project.load(self[name])
346
+ raise "Cannot load project: #{self[name]}" if @objects[name].nil?
347
+ @objects[name]
348
+ end
349
+
350
+ ##
351
+ # Load the dataset defined in the CLI
352
+ # If +silent=true+, it allows failures silently
353
+ def load_dataset(silent = false)
354
+ return @objects[:dataset] unless @objects[:dataset].nil?
355
+ ensure_par(dataset: '-D')
356
+ @objects[:dataset] = load_project.dataset(self[:dataset])
357
+ if !silent && @objects[:dataset].nil?
358
+ raise "Cannot load dataset: #{self[:dataset]}"
359
+ end
360
+ return @objects[:dataset]
361
+ end
362
+
363
+ ##
364
+ # Load an a project or (if defined) a dataset
365
+ def load_project_or_dataset
366
+ self[:dataset].nil? ? load_project : load_dataset
367
+ end
368
+
369
+ ##
370
+ # Load and filter a list of datasets as requested in the CLI
371
+ # If +silent=true+, it allows failures silently
372
+ def load_and_filter_datasets(silent = false)
373
+ return @objects[:filtered_datasets] unless @objects[:filtered_datasets].nil?
374
+ say 'Listing datasets'
375
+ ds = self[:dataset].nil? ?
376
+ load_project.datasets : [load_dataset(silent)].compact
377
+ ds.select! { |d| d.is_ref? == self[:ref] } unless self[:ref].nil?
378
+ ds.select! { |d| d.is_active? == self[:active] } unless self[:active].nil?
379
+ ds.select! do |d|
380
+ self[:multi] ? d.is_multi? : d.is_nonmulti?
381
+ end unless self[:multi].nil?
382
+ ds.select! do |d|
383
+ (not d.metadata[:tax].nil?) && d.metadata[:tax].in?(self[:taxonomy])
384
+ end unless self[:taxonomy].nil?
385
+ ds = ds.values_at(self[:dataset_k]-1) unless self[:dataset_k].nil?
386
+ @objects[:filtered_datasets] = ds
387
+ end
388
+
389
+ def load_result
390
+ return @objects[:result] unless @objects[:result].nil?
391
+ ensure_par(result: '-r')
392
+ obj = load_project_or_dataset
393
+ if obj.class.RESULT_DIRS[self[:result]].nil?
394
+ raise "Unsupported result for #{obj.class.to_s.gsub(/.*::/,'')}: #{self[:result]}"
395
+ end
396
+ r = obj.add_result(self[:result], false)
397
+ raise "Cannot load result: #{self[:result]}" if r.nil?
398
+ @objects[:result] = r
399
+ end
400
+
401
+ def add_metadata(obj, cli = self)
402
+ cli[:metadata].split(',').each do |pair|
403
+ (k,v) = pair.split('=')
404
+ case v
405
+ when 'true'; v = true
406
+ when 'false'; v = false
407
+ when 'nil'; v = nil
408
+ end
409
+ if k == '_step'
410
+ obj.metadata["_try_#{v}"] ||= 0
411
+ obj.metadata["_try_#{v}"] += 1
412
+ end
413
+ obj.metadata[k] = v
414
+ end unless cli[:metadata].nil?
415
+ [:type, :name, :user, :description, :comments].each do |k|
416
+ obj.metadata[k] = cli[k] unless cli[k].nil?
417
+ end
418
+ obj
419
+ end
420
+
421
+ ##
422
+ # Task description
423
+ def task_description
424
+ @@TASK_DESC[task]
425
+ end
426
+ end
@@ -9,7 +9,7 @@ require 'miga/project/base'
9
9
  module MiGA::Project::Result
10
10
 
11
11
  include MiGA::Project::Base
12
-
12
+
13
13
  ##
14
14
  # Get result identified by Symbol +name+, returns MiGA::Result.
15
15
  def result(name)
@@ -23,7 +23,15 @@ module MiGA::Project::Result
23
23
  def results
24
24
  @@RESULT_DIRS.keys.map{ |k| result(k) }.reject{ |r| r.nil? }
25
25
  end
26
-
26
+
27
+ ##
28
+ # For each result executes the 2-ary +blk+ block: key symbol and MiGA::Result.
29
+ def each_result(&blk)
30
+ @@RESULT_DIRS.keys.each do |k|
31
+ blk.call(k, result(k)) unless result(k).nil?
32
+ end
33
+ end
34
+
27
35
  ##
28
36
  # Add the result identified by Symbol +name+, and return MiGA::Result. Save
29
37
  # the result if +save+. The +opts+ hash controls result creation (if
@@ -45,12 +53,12 @@ module MiGA::Project::Result
45
53
  r.save unless r.nil?
46
54
  r
47
55
  end
48
-
56
+
49
57
  ##
50
58
  # Get the next distances task, saving intermediate results if +save+. Returns
51
59
  # a Symbol.
52
60
  def next_distances(save = true) ; next_task(@@DISTANCE_TASKS, save) ; end
53
-
61
+
54
62
  ##
55
63
  # Get the next inclade task, saving intermediate results if +save+. Returns a
56
64
  # Symbol.
@@ -72,8 +80,8 @@ module MiGA::Project::Result
72
80
  end
73
81
  end
74
82
  end
75
-
76
-
83
+
84
+
77
85
  private
78
86
 
79
87
  ##
@@ -16,7 +16,7 @@ class MiGA::RemoteDataset < MiGA::MiGA
16
16
  search_doc = MiGA::Json.parse(
17
17
  download(:ncbi_search, :assembly, acc, :json),
18
18
  symbolize: false, contents: true)
19
- search_doc['esearchresult']['idlist'].first
19
+ (search_doc['esearchresult']['idlist'] || []).first
20
20
  end
21
21
  end
22
22
 
@@ -6,9 +6,9 @@ require "miga/taxonomy"
6
6
  ##
7
7
  # Indexing methods based on taxonomy.
8
8
  class MiGA::TaxIndex < MiGA::MiGA
9
-
9
+
10
10
  # Instance-level
11
-
11
+
12
12
  ##
13
13
  # Datasets in the index.
14
14
  attr_reader :datasets
@@ -28,6 +28,7 @@ class MiGA::TaxIndex < MiGA::MiGA
28
28
  return nil if dataset.metadata[:tax].nil?
29
29
  taxon = @root
30
30
  MiGA::Taxonomy.KNOWN_RANKS.each do |rank|
31
+ next if rank == :ns
31
32
  taxon = taxon.add_child(rank, dataset.metadata[:tax][rank])
32
33
  end
33
34
  taxon.add_dataset dataset
@@ -69,9 +70,9 @@ end
69
70
  ##
70
71
  # Helper class for MiGA::TaxIndex.
71
72
  class MiGA::TaxIndexTaxon < MiGA::MiGA
72
-
73
+
73
74
  # Instance-level
74
-
75
+
75
76
  ##
76
77
  # Rank of the taxon.
77
78
  attr_reader :rank