bio-mummer 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37345c73287e9b80c15dc7426ee1045caad772f7
4
- data.tar.gz: 8b57b1755f5da0ba52af5ccbb92c3d55f06964e5
3
+ metadata.gz: 299d153188c65b1881bf1635b7389276ab1733f2
4
+ data.tar.gz: 3e3bf43c9c1551715d82c40bfd6c1b151f58dc2f
5
5
  SHA512:
6
- metadata.gz: c86e8bb5d957da2331d817101577aa9990e4685a6445d5b194747c069fc8d5708b93465e20ff95b4e1a37de9ab02402552c1d77a7ba1acd98ddbeb61bb98d574
7
- data.tar.gz: ffb5c859d7add8bb10c9d0a8dc5dc98a51dcb9a06370633d7fe6d69a3bd8d295836bafe4e32796ba6b62a855cde9a224731aa1c1cf45e077e8cc21eb4c31fb84
6
+ metadata.gz: c397099eb1478ceb5bc65d9657c64cfbe1bd02d2d0ce8e91a878065d1cf24407553d2f9609f8102af8b361d30b9048a499c4863a2b0772462f1977ae7d4d75ea
7
+ data.tar.gz: 3b1cdf0711552be2cdbfb3d1ad309d5d8abcdf7bbdae096cbcf439c6a9da2bba181b1085088ff4e1dcf1f8f84839186337977b048f7a89700b8c800717c71501
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.2.1
@@ -20,6 +20,7 @@ class OptParser
20
20
  opts.on("-d", "--delta FILENAME", "Delta file generated by nucmer") do |fn|
21
21
  pn = Pathname.new(fn)
22
22
  if pn.size?
23
+ options.samplename = pn.basename(".delta")
23
24
  options.delta = pn
24
25
  match = pn.each_line.first.match(/(?<ref>\/.*) (?<qry>\/.*)/)
25
26
  if match
@@ -38,6 +39,10 @@ class OptParser
38
39
  exit(1)
39
40
  end
40
41
  end
42
+
43
+ opts.on("-s", "--sample SAMPLENAME", "gVCF Sample name") do |samplename|
44
+ options.samplename = samplename
45
+ end
41
46
  end
42
47
  opt_parser.parse!(args)
43
48
  return options
@@ -45,7 +50,6 @@ class OptParser
45
50
  end
46
51
  options = OptParser.parse(ARGV)
47
52
 
48
- # Open the Delta File
49
53
  d = BioMummer::DeltaFile.new(StringIO.new(`delta-filter -r -q #{options.delta}`))
50
54
 
51
55
  puts '##fileformat=VCFv4.1
@@ -57,125 +61,173 @@ puts '##fileformat=VCFv4.1
57
61
  ##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
58
62
  ##FORMAT=<ID=MIN_DP,Number=1,Type=Integer,Description="Minimum DP observed within the GVCF block">
59
63
  ##FORMAT=<ID=PL,Number=G,Type=Integer,Description="Normalized, Phred-scaled likelihoods for genotypes as defined in the VCF specification">'
60
-
61
64
  options.ref.each do |name, seq|
62
65
  puts "##contig=<ID=#{name},length=#{seq.length}>"
63
66
  end
64
- puts "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\tDUMMYSAMPLENAME"
67
+ puts '##INFO=<ID=BaseQRankSum,Number=1,Type=Float,Description="Z-score from Wilcoxon rank sum test of Alt Vs. Ref base qualities">
68
+ ##INFO=<ID=ClippingRankSum,Number=1,Type=Float,Description="Z-score From Wilcoxon rank sum test of Alt vs. Ref number of hard clipped bases">
69
+ ##INFO=<ID=DP,Number=1,Type=Integer,Description="Approximate read depth; some reads may have been filtered">
70
+ ##INFO=<ID=DS,Number=0,Type=Flag,Description="Were any of the samples downsampled?">
71
+ ##INFO=<ID=END,Number=1,Type=Integer,Description="Stop position of the interval">
72
+ ##INFO=<ID=HaplotypeScore,Number=1,Type=Float,Description="Consistency of the site with at most two segregating haplotypes">
73
+ ##INFO=<ID=InbreedingCoeff,Number=1,Type=Float,Description="Inbreeding coefficient as estimated from the genotype likelihoods per-sample when compared against the Hardy-Weinberg expectation">
74
+ ##INFO=<ID=MLEAC,Number=A,Type=Integer,Description="Maximum likelihood expectation (MLE) for the allele counts (not necessarily the same as the AC), for each ALT allele, in the same order as listed">
75
+ ##INFO=<ID=MLEAF,Number=A,Type=Float,Description="Maximum likelihood expectation (MLE) for the allele frequency (not necessarily the same as the AF), for each ALT allele, in the same order as listed">
76
+ ##INFO=<ID=MQ,Number=1,Type=Float,Description="RMS Mapping Quality">
77
+ ##INFO=<ID=MQ0,Number=1,Type=Integer,Description="Total Mapping Quality Zero Reads">
78
+ ##INFO=<ID=MQRankSum,Number=1,Type=Float,Description="Z-score From Wilcoxon rank sum test of Alt vs. Ref read mapping qualities">
79
+ ##INFO=<ID=ReadPosRankSum,Number=1,Type=Float,Description="Z-score from Wilcoxon rank sum test of Alt vs. Ref read position bias">'
80
+ puts "#CHROM\tPOS\tID\tREF\tALT\tQUAL\tFILTER\tINFO\tFORMAT\t#{options.samplename}"
65
81
 
66
82
  d.alignments
67
- .group_by{|a| a.refname}.sort_by{|refname, alignments| refname}
68
- .take(2)
83
+ .group_by{ |alignment| alignment.refname }
84
+ .sort_by{ |refname, alignments| refname }
69
85
  .each do |refname, alignments|
70
- cursor = 1
71
- overlap = 0
72
- alignments.sort_by{|a| [a.refstart, a.refstop].min}.each do |a|
73
- ref = options.ref[a.refname].subseq(a.refstart, a.refstop)
74
- qry = a.strand ? options.qry[a.queryname].subseq(a.querystart, a.querystop) : options.qry[a.queryname].subseq(a.querystart, a.querystop).complement
75
- if a.refstart > cursor
76
- puts [refname, cursor, '.', options.ref[refname][cursor].upcase, "<NON_REF>", '.', '.', "END=#{a.refstart - 1}", "GT:DP:GQ:MIN_DP:PL", "0:0:0:0:0,0"].join("\t")
77
- cursor = a.refstart
78
- elsif a.refstart < cursor
79
- overlap = cursor - a.refstart
80
- end
81
-
82
- a.distances.inject([0,0]) do |mem, distance|
83
- if distance > 0
84
- qry.insert(mem.first + distance - 1, ".")
85
- [mem.first + distance, mem.last + distance]
86
- else
87
- ref.insert(mem.last - distance - 1, ".")
88
- [mem.first - distance, mem.last - distance]
89
- end
90
- end
91
- overlap2 = 0
92
-
93
- ref.chars.zip(0..(ref.length-1), qry.chars).chunk do |refBase, i, qryBase|
94
- if refBase == qryBase
95
- "NOVAR"
96
- elsif refBase == "."
97
- "INS"
98
- elsif qryBase == "."
99
- "DEL"
86
+
87
+ clusters = Enumerator.new do |yielder|
88
+ # We sweep along each of the reference sequences. The cursor tracks
89
+ # our position as we go so we don't concatenate regions that are
90
+ # actually overlapping.
91
+ cursor = 1
92
+
93
+ # The =alignments= object is an array of BioMummer::Alignment
94
+ # objects that all match a particular scaffold.
95
+ alignments
96
+ .sort_by{ |alignment| alignment.refstart }.
97
+ each do |aln|
98
+ # Create Bio::Sequence::NA subsequences for the reference and
99
+ # query. They are initially unaligned.
100
+ refSeq = options.ref[aln.refname].subseq(aln.refstart, aln.refstop)
101
+ qrySeq = options.qry[aln.qryname].subseq(aln.qrystart, aln.qrystop)
102
+ qrySeq.complement! unless aln.strand
103
+
104
+ # Do we have uncovered bases before the alignment starts? These
105
+ # are detected as gaps between the cursor and the alignment start
106
+ # site in the reference.
107
+ gap_size = aln.refstart - cursor
108
+ overlap_size = 0
109
+ if gap_size > 0
110
+ # There is a stretch of reference bases without an alignment
111
+ gapSeq = options.ref[aln.refname].subseq(cursor, aln.refstart - 1)
112
+ gapSeq
113
+ .chars
114
+ .zip(("-" * gapSeq.length).chars, Range.new(cursor, aln.refstart, true))
115
+ .each{ |ary| yielder << ary }
116
+ cursor = aln.refstart
100
117
  else
101
- "SNP"
102
- end
103
- end.drop_while do |varClass, arr|
104
- if varClass != "INS"
105
- overlap, overlap2 = overlap - arr.length , overlap
118
+ overlap_size = cursor - aln.refstart
106
119
  end
107
- overlap >= 0
108
- end.each do |varClass, arr|
109
- case varClass
110
- when "NOVAR"
111
- if overlap2 > 0
112
- refBase = arr[overlap2].first.upcase
120
+
121
+ # Nucmer records insertions and deletions as a series of positive
122
+ # and negative integers corresponding to how many steps along the
123
+ # alignment until you get to the next gap in the reference
124
+ # sequence (positive integer) or how many steps until the next gap
125
+ # in the query (negative integer). BioMummer exposes these as the
126
+ # alignments 'distances' array.
127
+ # We walk along the Bio::Sequence::NA objects and insert gaps
128
+ # (".") when needed. We store our current position in the
129
+ # reference and the query in a two-element array.
130
+ aln.distances.inject([0,0]) do |mem, distance_to_gap|
131
+ if distance_to_gap > 0
132
+ qrySeq.insert(mem[0] + distance_to_gap - 1, ".")
133
+ [mem[0] + distance_to_gap, mem[1] + distance_to_gap]
113
134
  else
114
- refBase = arr.first.first.upcase
115
- overlap2 = 0
116
- end
117
- puts [refname,
118
- cursor,
119
- ".",
120
- refBase,
121
- "<NON_REF>",
122
- '.',
123
- '.',
124
- "END=#{cursor + arr.length - 1 - overlap2};ALTSCAFF=#{a.queryname};RLEN=#{arr.length};OVERLAP2=#{overlap2}",
125
- "GT:DP:GQ:MIN_DP:PL",
126
- "0:200:200:200:0,800"].join("\t")
127
- cursor += arr.length - overlap2
128
- overlap2 = 0
129
- when "SNP"
130
- arr.each do |snp|
131
- puts [refname,
132
- cursor,
133
- ".",
134
- snp.first.upcase,
135
- [snp.last.upcase, "<NON_REF>"].join(","),
136
- '.',
137
- '.',
138
- "END=#{cursor};ALTSCAFF=#{a.queryname}",
139
- "GT:DP:GQ:MIN_DP:PL",
140
- "1:200:200:200:800,0,800"].join("\t")
141
- cursor += 1
135
+ refSeq.insert(mem[1] - distance_to_gap - 1, ".")
136
+ [mem[0] - distance_to_gap, mem[1] - distance_to_gap]
142
137
  end
143
- # puts [refname,
144
- # cursor,
145
- # ".",
146
- # arr.map{|a| a.first.upcase}.join,
147
- # [arr.map{|a| a.last.upcase}.join, "<NON_REF>"].join(","),
148
- # '.',
149
- # '.',
150
- # "END=#{cursor + arr.length - 1};ALTSCAFF=#{a.queryname}",
151
- # "GT:DP:GQ:MIN_DP:PL",
152
- # "1:200:200:200:800,0,800"].join("\t")
153
- # cursor += arr.length
154
- when "INS"
155
- refBase = options.ref[refname][cursor-2].upcase
156
- puts [refname,
157
- cursor-1,
158
- ".",
159
- refBase,
160
- [(refBase + arr.map{|a| a.last}.join).upcase, "<NON_REF>"].join(","),
161
- '.',
162
- '.',
163
- "END=#{cursor - 1};ALTSCAFF=#{a.queryname};STRAND=#{a.strand}",
164
- "GT:DP:GQ:MIN_DP:PL",
165
- "1:200:200:200:800,0,800"].join("\t")
166
- when "DEL"
167
- puts [refname,
168
- cursor,
169
- ".",
170
- arr.map{|a| a.first.upcase}.join,
171
- ".,<NON_REF>",
172
- '.',
173
- '.',
174
- "END=#{cursor + arr.length - 1};ALTSCAFF=#{a.queryname}",
175
- "GT:DP:GQ:MIN_DP:PL",
176
- "1:200:200:200:800,0,800"].join("\t")
177
- cursor += arr.length
138
+ end
139
+
140
+ if overlap_size < refSeq.length
141
+ i = aln.refstart - 1
142
+ positions = refSeq.chars.map{ |base| base == '.' ? i : i+=1 }
143
+ refSeq
144
+ .chars
145
+ .zip(qrySeq.chars, positions)
146
+ .drop(overlap_size)
147
+ .each{ |ary| yielder << ary }
178
148
  end
179
149
  end
180
150
  end
151
+
152
+ cursor = 0
153
+ stolen_zipped = [nil, nil, nil]
154
+ chunked = clusters.find_all do |refBase, qryBase, position|
155
+ if position > cursor || (position == cursor && refBase == ".")
156
+ cursor = position
157
+ true
158
+ else
159
+ false
160
+ end
161
+ end.chunk do |refBase, qryBase, position|
162
+ # Classify runs of identiry, insertions, deletions or SNPs.
163
+ if refBase == qryBase
164
+ :NO_VARIATION
165
+ elsif qryBase == "-"
166
+ :NO_COVERAGE
167
+ elsif qryBase == '.'
168
+ :DELETION
169
+ elsif refBase == '.'
170
+ :INSERTION
171
+ else
172
+ :SNP
173
+ end
174
+ end.to_a
175
+
176
+ chunked.each_cons(2) do |current, upcoming|
177
+ cur_class = current.first
178
+ cur_zipped = current.last
179
+ nxt_class = upcoming.first
180
+
181
+ if nxt_class == :INSERTION || nxt_class == :DELETION
182
+ stolen_zipped = cur_zipped.pop
183
+ next if cur_zipped.length == 0
184
+ end
185
+
186
+ case cur_class
187
+ when :NO_COVERAGE
188
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{cur_zipped.first.first.upcase}\t<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t0:0:0:0,0"
189
+ when :NO_VARIATION
190
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{cur_zipped.first.first.upcase}\t<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t0:1:200:0,800"
191
+ when :SNP
192
+ cur_zipped.each do |refBase, qryBase, position|
193
+ puts "#{refname}\t#{position}\t.\t#{refBase.upcase}\t#{qryBase.upcase},<NON_REF>\t.\t.\tEND=#{position}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
194
+ end
195
+ when :INSERTION
196
+ refSeq, qrySeq = cur_zipped.unshift(stolen_zipped).transpose.map{|seq| seq.reject{|c|c=='.'}.join.upcase }
197
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{refSeq}\t#{qrySeq},<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
198
+ when :DELETION
199
+ refSeq, qrySeq = cur_zipped.unshift(stolen_zipped).transpose.map{|seq| seq.reject{|c|c=='.'}.join.upcase }
200
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{refSeq}\t#{qrySeq},<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
201
+ end
202
+ cursor = [cursor, cur_zipped.last.last].max
203
+ end
204
+
205
+ # We iterated over the entries in a window of length two, outputting
206
+ # the first in the pair. This leaves the last entry unobserved, so
207
+ # we take special care to output it here
208
+ cur_class, cur_zipped = chunked.last
209
+ case cur_class
210
+ when :NO_COVERAGE
211
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{cur_zipped.first.first.upcase}\t<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t0:0:0:0,0"
212
+ when :NO_VARIATION
213
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{cur_zipped.first.first.upcase}\t<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t0:1:200:0,800"
214
+ when :SNP
215
+ cur_zipped.each do |refBase, qryBase, position|
216
+ puts "#{refname}\t#{position}\t.\t#{refBase.upcase}\t#{qryBase.upcase},<NON_REF>\t.\t.\tEND=#{position}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
217
+ end
218
+ when :INSERTION
219
+ refSeq, qrySeq = cur_zipped.unshift(stolen_zipped).transpose.map{|seq| seq.reject{|c|c=='.'}.join.upcase }
220
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{refSeq}\t#{qrySeq},<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
221
+ when :DELETION
222
+ refSeq, qrySeq = cur_zipped.unshift(stolen_zipped).transpose.map{|seq| seq.reject{|c|c=='.'}.join.upcase }
223
+ puts "#{refname}\t#{cur_zipped.first.last}\t.\t#{refSeq}\t#{qrySeq},<NON_REF>\t.\t.\tEND=#{cur_zipped.last.last}\tGT:DP:GQ:PL\t1:1:200:800,0,800"
224
+ end
225
+
226
+ # It's likely that the alignments don't run to the end of the
227
+ # scaffold, so we fill in to the scaffold end with the 'no coverage'
228
+ # entry
229
+ remaining = options.ref[refname].length - cursor
230
+ if remaining > 0
231
+ puts "#{refname}\t#{cursor+1}\t.\t#{options.ref[refname][cursor].upcase}\t<NON_REF>\t.\t.\tEND=#{options.ref[refname].length}\tGT:DP:GQ:PL\t0:0:0:0,0"
232
+ end
181
233
  end
@@ -3,15 +3,15 @@ require 'stringio'
3
3
  module BioMummer
4
4
 
5
5
  class Alignment
6
- attr_accessor :refname, :queryname, :refstart, :refstop, :querystart, :querystop, :strand, :distances
6
+ attr_accessor :refname, :qryname, :refstart, :refstop, :qrystart, :qrystop, :strand, :distances
7
7
 
8
- def initialize(refname, queryname, refstart, refstop, querystart, querystop, strand, distances)
8
+ def initialize(refname, qryname, refstart, refstop, qrystart, qrystop, strand, distances)
9
9
  @refname = refname
10
- @queryname = queryname
10
+ @qryname = qryname
11
11
  @refstart = refstart
12
12
  @refstop = refstop
13
- @querystart = querystart
14
- @querystop = querystop
13
+ @qrystart = qrystart
14
+ @qrystop = qrystop
15
15
  @strand = strand
16
16
  @distances = distances
17
17
  end
@@ -35,13 +35,13 @@ module BioMummer
35
35
 
36
36
  def ref_to_query(ref_position)
37
37
  position_in_alignment = ref_position - refstart + 1
38
- querypos = nil
38
+ qrypos = nil
39
39
  if position_in_alignment >= deltas.length
40
- querypos = @querystart - 1 + position_in_alignment + deltas.last
40
+ qrypos = @qrystart - 1 + position_in_alignment + deltas.last
41
41
  elsif deltas[position_in_alignment-1]
42
- querypos = @querystart - 1 + position_in_alignment + deltas[position_in_alignment-1]
42
+ qrypos = @qrystart - 1 + position_in_alignment + deltas[position_in_alignment-1]
43
43
  end
44
- !@strand && querypos ? @querystart + @querystop - querypos : querypos
44
+ !@strand && qrypos ? @qrystart + @qrystop - qrypos : qrypos
45
45
  end
46
46
  end
47
47
 
@@ -64,21 +64,21 @@ module BioMummer
64
64
 
65
65
  def parse(string)
66
66
  string.split("\n").slice_before(/^>/).flat_map do |block|
67
- refname, queryname = block.shift.match(/>(.*) (.*) \d+ \d+/).captures
67
+ refname, qryname = block.shift.match(/>(.*) (.*) \d+ \d+/).captures
68
68
  block.slice_before(/\d+ \d+ \d+ \d+/).map do |alignment|
69
- refstart, refstop, querystart, querystop = alignment
69
+ refstart, refstop, qrystart, qrystop = alignment
70
70
  .shift
71
71
  .match(/(\d+) (\d+) (\d+) (\d+) /)
72
72
  .captures
73
73
  .map{ |c| c.to_i }
74
74
  alignment.pop
75
75
  Alignment.new(refname,
76
- queryname,
76
+ qryname,
77
77
  refstart,
78
78
  refstop,
79
- [querystart, querystop].min,
80
- [querystart, querystop].max,
81
- querystart < querystop,
79
+ [qrystart, qrystop].min,
80
+ [qrystart, qrystop].max,
81
+ qrystart < qrystop,
82
82
  alignment.map{ |i| i.to_i })
83
83
  end
84
84
  end
@@ -110,13 +110,13 @@ module BioMummer
110
110
  a.refname == refname && a.refstart <= startpos && a.refstop >= endpos
111
111
  end
112
112
  if a
113
- queryname = a.queryname
114
- querystart = a.ref_to_query(startpos)
115
- querystop = a.ref_to_query(endpos)
116
- if querystart.nil? || querystop.nil?
113
+ qryname = a.qryname
114
+ qrystart = a.ref_to_query(startpos)
115
+ qrystop = a.ref_to_query(endpos)
116
+ if qrystart.nil? || qrystop.nil?
117
117
  return nil
118
118
  else
119
- return [queryname, querystart, querystop, a.strand]
119
+ return [qryname, qrystart, qrystop, a.strand]
120
120
  end
121
121
  else
122
122
  return nil
@@ -15,8 +15,8 @@ class TestBioMummer < MiniTest::Test
15
15
  assert_kind_of Fixnum, @report.alignments.first.refstart
16
16
  assert_equal 1, @report.alignments.first.refstart
17
17
  assert_equal 2435, @report.alignments.first.refstop
18
- assert_equal 1, @report.alignments.first.querystart
19
- assert_equal 2435, @report.alignments.first.querystop
18
+ assert_equal 1, @report.alignments.first.qrystart
19
+ assert_equal 2435, @report.alignments.first.qrystop
20
20
  end
21
21
 
22
22
  should "do basic coordinate transforms on the positive strand" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bio-mummer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - robsyme
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-23 00:00:00.000000000 Z
11
+ date: 2015-03-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: shoulda
@@ -105,7 +105,6 @@ extra_rdoc_files:
105
105
  - README.md
106
106
  - README.rdoc
107
107
  files:
108
- - .document
109
108
  - .travis.yml
110
109
  - Gemfile
111
110
  - LICENSE.txt
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt