miga-base 0.4.1.0 → 0.4.2.0

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 (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