genomer 0.0.5
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.
- data/.document +5 -0
- data/.gitignore +45 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +23 -0
- data/Rakefile +23 -0
- data/bin/genomer +18 -0
- data/cucumber.yml +2 -0
- data/features/api/annotation_ids.feature +152 -0
- data/features/api/annotation_location.feature +269 -0
- data/features/api/scaffold.feature +38 -0
- data/features/cli/create.feature +19 -0
- data/features/cli/error.feature +17 -0
- data/features/cli/help.feature +48 -0
- data/features/cli/man.feature +71 -0
- data/features/cli/plugins.feature +34 -0
- data/features/step_definitions/genomer_steps.rb +10 -0
- data/features/support/env.rb +13 -0
- data/genomer-plugin-simple/genomer-plugin-simple.gemspec +18 -0
- data/genomer-plugin-simple/lib/genomer-plugin-simple.rb +19 -0
- data/genomer-plugin-simple/man/genomer-simple-subcommand.ronn +6 -0
- data/genomer-plugin-simple/man/genomer-simple.ronn +6 -0
- data/genomer.gemspec +39 -0
- data/lib/genomer.rb +6 -0
- data/lib/genomer/error.rb +4 -0
- data/lib/genomer/plugin.rb +126 -0
- data/lib/genomer/runtime.rb +101 -0
- data/lib/genomer/version.rb +3 -0
- data/man/genomer-init.1.ronn +20 -0
- data/spec/genomer/plugin_spec.rb +341 -0
- data/spec/genomer/runtime_spec.rb +262 -0
- data/spec/spec_helper.rb +45 -0
- metadata +290 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'unindent'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'ronn/index'
|
4
|
+
require 'ronn/document'
|
5
|
+
|
6
|
+
class Genomer::Runtime
|
7
|
+
|
8
|
+
attr :command
|
9
|
+
attr :arguments
|
10
|
+
attr :flags
|
11
|
+
|
12
|
+
def initialize(settings)
|
13
|
+
@command = settings.rest.shift
|
14
|
+
@arguments = settings.rest
|
15
|
+
@flags = settings
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute!
|
19
|
+
case command
|
20
|
+
when nil then short_help
|
21
|
+
when "help" then help
|
22
|
+
when "init" then init
|
23
|
+
when "man" then man
|
24
|
+
else run_plugin
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def short_help
|
29
|
+
msg =<<-EOF
|
30
|
+
genomer COMMAND [options]
|
31
|
+
run `genomer help` for a list of available commands
|
32
|
+
EOF
|
33
|
+
msg.unindent
|
34
|
+
end
|
35
|
+
|
36
|
+
def help
|
37
|
+
msg =<<-EOF
|
38
|
+
genomer COMMAND [options]
|
39
|
+
|
40
|
+
Available commands:
|
41
|
+
init Create a new genomer project
|
42
|
+
man View man page for the specified plugin
|
43
|
+
EOF
|
44
|
+
msg.unindent!
|
45
|
+
|
46
|
+
msg << Genomer::Plugin.plugins.inject(String.new) do |str,p|
|
47
|
+
str << ' '
|
48
|
+
str << p.name.gsub("genomer-plugin-","").ljust(12)
|
49
|
+
str << p.summary
|
50
|
+
str << "\n"
|
51
|
+
end
|
52
|
+
msg.strip
|
53
|
+
end
|
54
|
+
|
55
|
+
def man
|
56
|
+
if not arguments.empty?
|
57
|
+
man_file_location = man_file(arguments.clone)
|
58
|
+
unless File.exists?(man_file_location)
|
59
|
+
raise Genomer::Error, "No manual entry for command '#{arguments.join(' ')}'"
|
60
|
+
end
|
61
|
+
|
62
|
+
Kernel.exec "man #{groffed_man_file(man_file_location).path}"
|
63
|
+
else
|
64
|
+
msg =<<-EOF
|
65
|
+
genomer man COMMAND
|
66
|
+
run `genomer help` for a list of available commands
|
67
|
+
EOF
|
68
|
+
msg.unindent!
|
69
|
+
msg.strip
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def man_file(arguments)
|
74
|
+
plugin = arguments.first
|
75
|
+
page = arguments.unshift("genomer").join('-') << ".ronn"
|
76
|
+
File.join(Genomer::Plugin.fetch(plugin).full_gem_path, 'man', page)
|
77
|
+
end
|
78
|
+
|
79
|
+
def groffed_man_file(original_man_file)
|
80
|
+
converted_man = Tempfile.new("genome-manpage-")
|
81
|
+
File.open(converted_man.path,'w') do |out|
|
82
|
+
out.puts Ronn::Document.new(original_man_file).convert('roff')
|
83
|
+
end
|
84
|
+
converted_man
|
85
|
+
end
|
86
|
+
|
87
|
+
def init
|
88
|
+
project_name = arguments.first
|
89
|
+
if File.exists?(project_name)
|
90
|
+
raise Genomer::Error, "Directory '#{project_name}' already exists."
|
91
|
+
else
|
92
|
+
Dir.mkdir project_name
|
93
|
+
Dir.mkdir File.join(project_name,'assembly')
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_plugin
|
98
|
+
Genomer::Plugin[command].new(arguments,flags).run
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
genomer-init(1) -- create a new genomer project
|
2
|
+
===============================================
|
3
|
+
|
4
|
+
## SYNOPSIS
|
5
|
+
|
6
|
+
`genomer init` <project-name>
|
7
|
+
|
8
|
+
## DESCRIPTION
|
9
|
+
|
10
|
+
Creates a new genomer project directory named **project-name**. Returns an
|
11
|
+
error if the project directory already exists.
|
12
|
+
|
13
|
+
## BUGS
|
14
|
+
|
15
|
+
**Genomer-init** is written in Ruby and uses several RubyGems dependencies. See
|
16
|
+
the Gemfile in the genomer gem install directory for version details.
|
17
|
+
|
18
|
+
## COPYRIGHT
|
19
|
+
|
20
|
+
**Genomer** is Copyright (C) 2011 Michael Barton <http://michaelbarton.me.uk>
|
@@ -0,0 +1,341 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Genomer::Plugin do
|
4
|
+
|
5
|
+
describe "#[]" do
|
6
|
+
|
7
|
+
before do
|
8
|
+
mock(described_class).plugins do
|
9
|
+
[Gem::Specification.new do |s|
|
10
|
+
s.name = 'genomer-plugin-simple'
|
11
|
+
end]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe "fetching an available plugin" do
|
16
|
+
|
17
|
+
before do
|
18
|
+
mock(described_class).require('genomer-plugin-simple')
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should return the class for this plugin" do
|
22
|
+
expected = GenomerPluginSimple = Class.new
|
23
|
+
described_class['simple'].should == expected
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "fethcing an unavailble plugin" do
|
29
|
+
|
30
|
+
it "should print an error message" do
|
31
|
+
error = "Unknown command or plugin 'unknown.'\n"
|
32
|
+
error << "run `genomer help` for a list of available commands\n"
|
33
|
+
lambda{ described_class['unknown'] }.should raise_error(Genomer::Error,error)
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#plugins" do
|
41
|
+
|
42
|
+
before do
|
43
|
+
mock(Bundler).setup do
|
44
|
+
mock!.gems{ gems }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "where a single genomer plugin is specified" do
|
49
|
+
|
50
|
+
let(:gems) do
|
51
|
+
[Gem::Specification.new do |s|
|
52
|
+
s.name = 'genomer-plugin-simple'
|
53
|
+
end]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should return the genomer plugin" do
|
57
|
+
described_class.plugins.should == gems
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "where no gems at all are specified" do
|
63
|
+
|
64
|
+
let(:gems) do
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should return an empty array" do
|
69
|
+
described_class.plugins.should be_empty
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "where one plugin and one non-plugin are specified" do
|
75
|
+
|
76
|
+
let(:gems) do
|
77
|
+
[Gem::Specification.new{|s| s.name = 'genomer-plugin-simple' },
|
78
|
+
Gem::Specification.new{|s| s.name = 'not-a-plugin' }]
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return the genomer plugin" do
|
82
|
+
described_class.plugins.should == [gems.first]
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe "#to_class_name" do
|
90
|
+
|
91
|
+
it "should dash separated words to camel case" do
|
92
|
+
described_class.to_class_name('words-with-dashes').should == "WordsWithDashes"
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
describe "#scaffold" do
|
98
|
+
|
99
|
+
let(:entries) do
|
100
|
+
[Sequence.new(:name => 'seq1', :sequence => 'ATGC')]
|
101
|
+
end
|
102
|
+
|
103
|
+
let (:sequence_file) do
|
104
|
+
File.new("assembly/sequence.fna",'w')
|
105
|
+
end
|
106
|
+
|
107
|
+
let (:scaffold_file) do
|
108
|
+
File.new("assembly/scaffold.yml",'w')
|
109
|
+
end
|
110
|
+
|
111
|
+
before do
|
112
|
+
Dir.mkdir('assembly')
|
113
|
+
write_sequence_file(entries,sequence_file)
|
114
|
+
write_scaffold_file(entries,scaffold_file)
|
115
|
+
end
|
116
|
+
|
117
|
+
after do
|
118
|
+
FileUtils.rm_rf 'assembly'
|
119
|
+
end
|
120
|
+
|
121
|
+
subject do
|
122
|
+
described_class.new(nil,nil)
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should return the expected scaffold built from the scaffold files" do
|
126
|
+
subject.scaffold.length.should == 1
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "#annotations" do
|
132
|
+
|
133
|
+
before do
|
134
|
+
Dir.mkdir('assembly')
|
135
|
+
write_sequence_file(entries, File.new("assembly/sequence.fna",'w'))
|
136
|
+
write_scaffold_file(entries, File.new("assembly/scaffold.yml",'w'))
|
137
|
+
generate_gff3_file(records, File.new("assembly/annotations.gff",'w'))
|
138
|
+
end
|
139
|
+
|
140
|
+
after do
|
141
|
+
FileUtils.rm_rf 'assembly'
|
142
|
+
end
|
143
|
+
|
144
|
+
let(:entries) do
|
145
|
+
[Sequence.new(:name => 'seq1', :sequence => 'ATGCATGCATGC')]
|
146
|
+
end
|
147
|
+
|
148
|
+
let(:annotation) do
|
149
|
+
Annotation.new(
|
150
|
+
:seqname => 'seq1',
|
151
|
+
:start => 1,
|
152
|
+
:end => 3,
|
153
|
+
:feature => 'gene',
|
154
|
+
:attributes => {'ID' => 'gene1'})
|
155
|
+
end
|
156
|
+
|
157
|
+
let(:options) do
|
158
|
+
{}
|
159
|
+
end
|
160
|
+
|
161
|
+
subject do
|
162
|
+
described_class.new(nil,nil).annotations(options)
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "with no annotations" do
|
166
|
+
|
167
|
+
let(:records) do
|
168
|
+
[]
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should return one annotation entry" do
|
172
|
+
subject.length.should == 0
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
describe "with one annotation" do
|
178
|
+
|
179
|
+
let(:records) do
|
180
|
+
[annotation]
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should return no annotations" do
|
184
|
+
subject.length.should == 1
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "with one contig annotation and one non-contig annotation" do
|
190
|
+
|
191
|
+
let(:records) do
|
192
|
+
[annotation,annotation.clone.seqname('seq2')]
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should return only the contig annotation" do
|
196
|
+
subject.length.should == 1
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
describe "with multiple unordered annotations" do
|
202
|
+
|
203
|
+
let(:records) do
|
204
|
+
[
|
205
|
+
annotation.clone.start(4).end(6),
|
206
|
+
annotation.clone.start(7).end(9),
|
207
|
+
annotation
|
208
|
+
]
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should return the annotations in order" do
|
212
|
+
subject[0].start.should == 1
|
213
|
+
subject[1].start.should == 4
|
214
|
+
subject[2].start.should == 7
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
describe "with the prefix option" do
|
220
|
+
|
221
|
+
let(:records) do
|
222
|
+
[annotation]
|
223
|
+
end
|
224
|
+
|
225
|
+
let(:options) do
|
226
|
+
{:prefix => 'pre_'}
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should prefix the annotation id" do
|
230
|
+
subject.first.id.should == 'pre_gene1'
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
describe "with reset numbering" do
|
236
|
+
|
237
|
+
let(:records) do
|
238
|
+
[
|
239
|
+
annotation.clone.start(4).end(6),
|
240
|
+
annotation.clone.start(7).end(9),
|
241
|
+
annotation
|
242
|
+
]
|
243
|
+
end
|
244
|
+
|
245
|
+
let(:options) do
|
246
|
+
{:reset => true}
|
247
|
+
end
|
248
|
+
|
249
|
+
it "should reset the locus tag numbering at 000001" do
|
250
|
+
subject[0].id.should == '000001'
|
251
|
+
subject[1].id.should == '000002'
|
252
|
+
subject[2].id.should == '000003'
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
describe "with reset numbering and an argument" do
|
258
|
+
|
259
|
+
let(:records) do
|
260
|
+
[
|
261
|
+
annotation.clone.start(4).end(6),
|
262
|
+
annotation.clone.start(7).end(9),
|
263
|
+
annotation
|
264
|
+
]
|
265
|
+
end
|
266
|
+
|
267
|
+
let(:options) do
|
268
|
+
{:reset => '5'}
|
269
|
+
end
|
270
|
+
|
271
|
+
it "should reset the locus tag numbering starting at 000005" do
|
272
|
+
subject[0].id.should == '000005'
|
273
|
+
subject[1].id.should == '000006'
|
274
|
+
subject[2].id.should == '000007'
|
275
|
+
end
|
276
|
+
|
277
|
+
end
|
278
|
+
|
279
|
+
describe "with the reset numbering and prefix option" do
|
280
|
+
|
281
|
+
let(:records) do
|
282
|
+
[
|
283
|
+
annotation.clone.start(4).end(6),
|
284
|
+
annotation.clone.start(7).end(9),
|
285
|
+
annotation
|
286
|
+
]
|
287
|
+
end
|
288
|
+
|
289
|
+
let(:options) do
|
290
|
+
{:reset => true, :prefix => 'pre_'}
|
291
|
+
end
|
292
|
+
|
293
|
+
it "should prefix and reset the locus tag numbering at 000001" do
|
294
|
+
subject[0].id.should == 'pre_000001'
|
295
|
+
subject[1].id.should == 'pre_000002'
|
296
|
+
subject[2].id.should == 'pre_000003'
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "with reset numbering given an argument and the prefix option" do
|
302
|
+
|
303
|
+
let(:records) do
|
304
|
+
[
|
305
|
+
annotation.clone.start(4).end(6),
|
306
|
+
annotation.clone.start(7).end(9),
|
307
|
+
annotation
|
308
|
+
]
|
309
|
+
end
|
310
|
+
|
311
|
+
let(:options) do
|
312
|
+
{:reset => '5', :prefix => 'pre_'}
|
313
|
+
end
|
314
|
+
|
315
|
+
it "should prefix and reset the locus tag numbering at 000005" do
|
316
|
+
subject[0].id.should == 'pre_000005'
|
317
|
+
subject[1].id.should == 'pre_000006'
|
318
|
+
subject[2].id.should == 'pre_000007'
|
319
|
+
end
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
describe "#initialize" do
|
326
|
+
|
327
|
+
subject do
|
328
|
+
described_class.new(:arguments,:flags)
|
329
|
+
end
|
330
|
+
|
331
|
+
it "should set the #arguments attribute" do
|
332
|
+
subject.arguments.should == :arguments
|
333
|
+
end
|
334
|
+
|
335
|
+
it "should set the #flags attribute" do
|
336
|
+
subject.arguments.should == :arguments
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
|
341
|
+
end
|