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.
- checksums.yaml +4 -4
- data/bin/miga +2 -244
- data/lib/miga/cli/action/about.rb +44 -0
- data/lib/miga/cli/action/add.rb +139 -0
- data/lib/miga/cli/action/add_result.rb +26 -0
- data/lib/miga/cli/action/console.rb +19 -0
- data/lib/miga/cli/action/daemon.rb +74 -0
- data/lib/miga/cli/action/date.rb +18 -0
- data/lib/miga/cli/action/doctor.rb +210 -0
- data/lib/miga/cli/action/edit.rb +24 -0
- data/lib/miga/cli/action/files.rb +31 -0
- data/lib/miga/cli/action/find.rb +48 -0
- data/lib/miga/cli/action/generic.rb +44 -0
- data/lib/miga/cli/action/get.rb +132 -0
- data/lib/miga/cli/action/init.rb +343 -0
- data/lib/miga/cli/action/ln.rb +42 -0
- data/lib/miga/cli/action/ls.rb +55 -0
- data/lib/miga/cli/action/ncbi_get.rb +218 -0
- data/lib/miga/cli/action/new.rb +45 -0
- data/lib/miga/cli/action/next_step.rb +27 -0
- data/lib/miga/cli/action/plugins.rb +28 -0
- data/lib/miga/cli/action/rm.rb +25 -0
- data/lib/miga/cli/action/run.rb +39 -0
- data/lib/miga/cli/action/stats.rb +140 -0
- data/lib/miga/cli/action/summary.rb +49 -0
- data/lib/miga/cli/action/tax_dist.rb +102 -0
- data/lib/miga/cli/action/tax_index.rb +47 -0
- data/lib/miga/cli/action/tax_set.rb +59 -0
- data/lib/miga/cli/action/tax_test.rb +77 -0
- data/lib/miga/cli/action.rb +66 -0
- data/lib/miga/cli/base.rb +90 -0
- data/lib/miga/cli.rb +426 -0
- data/lib/miga/project/result.rb +14 -6
- data/lib/miga/remote_dataset.rb +1 -1
- data/lib/miga/tax_index.rb +5 -4
- data/lib/miga/taxonomy/base.rb +63 -0
- data/lib/miga/taxonomy.rb +87 -92
- data/lib/miga/version.rb +6 -6
- data/test/taxonomy_test.rb +49 -9
- data/utils/distance/commands.rb +11 -11
- data/utils/distance/pipeline.rb +5 -5
- metadata +43 -49
- data/actions/about.rb +0 -43
- data/actions/add.rb +0 -129
- data/actions/add_result.rb +0 -30
- data/actions/daemon.rb +0 -55
- data/actions/date.rb +0 -14
- data/actions/doctor.rb +0 -201
- data/actions/edit.rb +0 -33
- data/actions/files.rb +0 -43
- data/actions/find.rb +0 -41
- data/actions/get.rb +0 -105
- data/actions/init.rb +0 -301
- data/actions/ln.rb +0 -47
- data/actions/ls.rb +0 -61
- data/actions/ncbi_get.rb +0 -192
- data/actions/new.rb +0 -44
- data/actions/next_step.rb +0 -33
- data/actions/plugins.rb +0 -25
- data/actions/rm.rb +0 -29
- data/actions/run.rb +0 -45
- data/actions/stats.rb +0 -149
- data/actions/summary.rb +0 -57
- data/actions/tax_dist.rb +0 -106
- data/actions/tax_index.rb +0 -46
- data/actions/tax_set.rb +0 -63
- 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
|
data/lib/miga/project/result.rb
CHANGED
@@ -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
|
##
|
data/lib/miga/remote_dataset.rb
CHANGED
@@ -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
|
|
data/lib/miga/tax_index.rb
CHANGED
@@ -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
|