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