stringyfi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/Guardfile +18 -0
- data/LICENSE.txt +22 -0
- data/README.md +65 -0
- data/Rakefile +12 -0
- data/bin/stringyfi +5 -0
- data/lib/stringyfi.rb +5 -0
- data/lib/stringyfi/converter.rb +164 -0
- data/lib/stringyfi/measures.rb +27 -0
- data/lib/stringyfi/note.rb +97 -0
- data/lib/stringyfi/shell.rb +13 -0
- data/lib/stringyfi/version.rb +3 -0
- data/spec/fixtures/music_xml/chromatic.gp +0 -0
- data/spec/fixtures/music_xml/chromatic.xml +2096 -0
- data/spec/fixtures/music_xml/quarters.gp +0 -0
- data/spec/fixtures/music_xml/quarters.xml +1339 -0
- data/spec/spec_helper.rb +85 -0
- data/spec/support/fixtures_helper.rb +14 -0
- data/spec/unit/converter_spec.rb +166 -0
- data/spec/unit/measures_spec.rb +34 -0
- data/spec/unit/note_spec.rb +106 -0
- data/spec/unit/shell_spec.rb +12 -0
- data/spec/unit/version_spec.rb +9 -0
- data/stringyfi.gemspec +31 -0
- metadata +197 -0
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'stringyfi'
|
2
|
+
|
3
|
+
# Requires supporting files with custom matchers and macros, etc,
|
4
|
+
# in ./support/ and its subdirectories.
|
5
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
6
|
+
|
7
|
+
|
8
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
9
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
10
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause this
|
11
|
+
# file to always be loaded, without a need to explicitly require it in any files.
|
12
|
+
#
|
13
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
14
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
15
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
16
|
+
# individual file that may not need all of that loaded. Instead, make a
|
17
|
+
# separate helper file that requires this one and then use it only in the specs
|
18
|
+
# that actually need it.
|
19
|
+
#
|
20
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
21
|
+
# users commonly want.
|
22
|
+
#
|
23
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
24
|
+
RSpec.configure do |config|
|
25
|
+
# The settings below are suggested to provide a good initial experience
|
26
|
+
# with RSpec, but feel free to customize to your heart's content.
|
27
|
+
=begin
|
28
|
+
# These two settings work together to allow you to limit a spec run
|
29
|
+
# to individual examples or groups you care about by tagging them with
|
30
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
31
|
+
# get run.
|
32
|
+
config.filter_run :focus
|
33
|
+
config.run_all_when_everything_filtered = true
|
34
|
+
|
35
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
36
|
+
# file, and it's useful to allow more verbose output when running an
|
37
|
+
# individual spec file.
|
38
|
+
if config.files_to_run.one?
|
39
|
+
# Use the documentation formatter for detailed output,
|
40
|
+
# unless a formatter has already been configured
|
41
|
+
# (e.g. via a command-line flag).
|
42
|
+
config.default_formatter = 'doc'
|
43
|
+
end
|
44
|
+
|
45
|
+
# Print the 10 slowest examples and example groups at the
|
46
|
+
# end of the spec run, to help surface which specs are running
|
47
|
+
# particularly slow.
|
48
|
+
config.profile_examples = 10
|
49
|
+
|
50
|
+
# Run specs in random order to surface order dependencies. If you find an
|
51
|
+
# order dependency and want to debug it, you can fix the order by providing
|
52
|
+
# the seed, which is printed after each run.
|
53
|
+
# --seed 1234
|
54
|
+
config.order = :random
|
55
|
+
|
56
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
57
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
58
|
+
# test failures related to randomization by passing the same `--seed` value
|
59
|
+
# as the one that triggered the failure.
|
60
|
+
Kernel.srand config.seed
|
61
|
+
|
62
|
+
# rspec-expectations config goes here. You can use an alternate
|
63
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
64
|
+
# assertions if you prefer.
|
65
|
+
config.expect_with :rspec do |expectations|
|
66
|
+
# Enable only the newer, non-monkey-patching expect syntax.
|
67
|
+
# For more details, see:
|
68
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
69
|
+
expectations.syntax = :expect
|
70
|
+
end
|
71
|
+
|
72
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
73
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
74
|
+
config.mock_with :rspec do |mocks|
|
75
|
+
# Enable only the newer, non-monkey-patching expect syntax.
|
76
|
+
# For more details, see:
|
77
|
+
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
78
|
+
mocks.syntax = :expect
|
79
|
+
|
80
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
81
|
+
# a real object. This is generally recommended.
|
82
|
+
mocks.verify_partial_doubles = true
|
83
|
+
end
|
84
|
+
=end
|
85
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module FixturesHelper
|
2
|
+
|
3
|
+
def xml_sample_path(name='chromatic.xml')
|
4
|
+
File.join(xml_samples_path, name)
|
5
|
+
end
|
6
|
+
|
7
|
+
def xml_samples_path
|
8
|
+
"#{File.dirname(__FILE__)}/../fixtures/music_xml"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
RSpec.configure do |conf|
|
13
|
+
conf.include FixturesHelper
|
14
|
+
end
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StringyFi::Converter do
|
4
|
+
subject(:converter) { described_class.new(filename) }
|
5
|
+
|
6
|
+
context "with chromatic.xml sample" do
|
7
|
+
let(:filename) { xml_sample_path }
|
8
|
+
|
9
|
+
describe "#xml_doc" do
|
10
|
+
subject { converter.xml_doc }
|
11
|
+
it "returns the XML doc" do
|
12
|
+
expect(subject).to be_a(Nokogiri::XML::Document)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#identification" do
|
17
|
+
subject(:identification) { converter.identification }
|
18
|
+
it "returns expected metadata" do
|
19
|
+
expect(subject).to eql({
|
20
|
+
title: "chromatic scale",
|
21
|
+
encoding: {
|
22
|
+
date: "2017-10-15",
|
23
|
+
software: "Guitar Pro 7.0.6"
|
24
|
+
}
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "#part_list" do
|
30
|
+
subject(:part_list) { converter.part_list }
|
31
|
+
it "returns an array of parts" do
|
32
|
+
expect(part_list).to eql([
|
33
|
+
{id: "P1"}
|
34
|
+
])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "#parts" do
|
39
|
+
subject(:parts) { converter.parts }
|
40
|
+
it "returns parts corresponding to parts list" do
|
41
|
+
expect(parts.count).to eql(converter.part_list.count)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#measures" do
|
46
|
+
subject(:measures) { converter.measures }
|
47
|
+
it "has the correct number of measures" do
|
48
|
+
expect(measures).to be_a(StringyFi::Measures)
|
49
|
+
expect(measures.count).to eql(7)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#measure" do
|
54
|
+
subject(:measure) { converter.measure(measure_id) }
|
55
|
+
context "with first measure" do
|
56
|
+
let(:measure_id) { 0 }
|
57
|
+
it "has the correct note info" do
|
58
|
+
expect(measure).to be_an(Array)
|
59
|
+
expect(measure.size).to eql(1)
|
60
|
+
expect(measure[0]).to be_a(StringyFi::Note)
|
61
|
+
expect(measure[0].to_str).to eql("3:E:0")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "#score_preamble" do
|
67
|
+
subject { converter.score_preamble }
|
68
|
+
it "includes the expected identification" do
|
69
|
+
expect(subject).to include(';** Title: chromatic scale')
|
70
|
+
end
|
71
|
+
it "includes the tstart" do
|
72
|
+
expect(subject).to include("\ttstart DemoTune")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#score_coda" do
|
77
|
+
subject { converter.score_coda }
|
78
|
+
it "includes a rest and tstop" do
|
79
|
+
expect(subject).to include("\ttrest 8")
|
80
|
+
expect(subject).to include("\ttstop")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "#score_body" do
|
85
|
+
subject { converter.score_body(1/32.0) }
|
86
|
+
it "returns the expected conversion" do
|
87
|
+
expect(subject).to eql([
|
88
|
+
"\t; measure 1",
|
89
|
+
"\ttoctave -1",
|
90
|
+
"\ttnote E1,4,0 ; 3:E:0",
|
91
|
+
"\t; measure 2",
|
92
|
+
"\ttnote F1,4,0 ; 3:F:0",
|
93
|
+
"\ttnote F1S,4,0 ; 3:F:1",
|
94
|
+
"\t; measure 3",
|
95
|
+
"\ttnote G1,4,0 ; 3:G:0",
|
96
|
+
"\ttnote A1,4,0 ; 3:A:0",
|
97
|
+
"\ttnote A1S,4,0 ; 3:A:1",
|
98
|
+
"\ttnote B1,4,0 ; 3:B:0",
|
99
|
+
"\t; measure 4",
|
100
|
+
"\ttoctave +1",
|
101
|
+
"\ttnote C1,4,0 ; 4:C:0",
|
102
|
+
"\ttnote C1S,4,0 ; 4:C:1",
|
103
|
+
"\ttnote D1,4,0 ; 4:D:0",
|
104
|
+
"\ttnote D1S,4,0 ; 4:D:1",
|
105
|
+
"\t; measure 5",
|
106
|
+
"\ttnote E1,3,0 ; 4:E:0",
|
107
|
+
"\ttnote F1,3,0 ; 4:F:0",
|
108
|
+
"\ttnote F1S,3,0 ; 4:F:1",
|
109
|
+
"\ttnote G1,3,0 ; 4:G:0",
|
110
|
+
"\ttnote G1S,3,0 ; 4:G:1",
|
111
|
+
"\ttnote A1,3,0 ; 4:A:0",
|
112
|
+
"\ttnote A1S,3,0 ; 4:A:1",
|
113
|
+
"\ttnote B1,3,0 ; 4:B:0",
|
114
|
+
"\t; measure 6",
|
115
|
+
"\ttoctave +1",
|
116
|
+
"\ttnote C1,2,0 ; 5:C:0",
|
117
|
+
"\ttnote C1S,2,0 ; 5:C:1",
|
118
|
+
"\ttnote D1,2,0 ; 5:D:0",
|
119
|
+
"\ttnote D1S,2,0 ; 5:D:1",
|
120
|
+
"\ttnote E1,2,0 ; 5:E:0",
|
121
|
+
"\ttnote F1,2,0 ; 5:F:0",
|
122
|
+
"\ttnote F1S,2,0 ; 5:F:1",
|
123
|
+
"\ttnote G1,2,0 ; 5:G:0",
|
124
|
+
"\ttnote G1S,2,0 ; 5:G:1",
|
125
|
+
"\ttnote A1,2,0 ; 5:A:0",
|
126
|
+
"\ttnote A1S,2,0 ; 5:A:1",
|
127
|
+
"\ttnote B1,2,0 ; 5:B:0",
|
128
|
+
"\ttoctave +1",
|
129
|
+
"\ttnote C1,2,0 ; 6:C:0",
|
130
|
+
"\ttnote C1S,2,0 ; 6:C:1",
|
131
|
+
"\ttnote D1,2,0 ; 6:D:0",
|
132
|
+
"\ttnote D1S,2,0 ; 6:D:1",
|
133
|
+
"\t; measure 7",
|
134
|
+
"\ttnote E1,1,0 ; 6:E:0",
|
135
|
+
"\ttnote F1,1,0 ; 6:F:0",
|
136
|
+
"\ttnote F1S,1,0 ; 6:F:1",
|
137
|
+
"\ttnote G1,1,0 ; 6:G:0",
|
138
|
+
"\ttnote G1S,1,0 ; 6:G:1",
|
139
|
+
"\ttnote A1,1,0 ; 6:A:0",
|
140
|
+
"\ttnote A1S,1,0 ; 6:A:1",
|
141
|
+
"\ttnote B1,1,0 ; 6:B:0",
|
142
|
+
"\ttoctave +1",
|
143
|
+
"\ttnote C1,1,0 ; 7:C:0",
|
144
|
+
"\ttnote C1S,1,0 ; 7:C:1",
|
145
|
+
"\ttnote D1,1,0 ; 7:D:0",
|
146
|
+
"\ttnote D1S,1,0 ; 7:D:1",
|
147
|
+
"\ttnote E1,4,0 ; 7:E:0",
|
148
|
+
"\ttnote E1,4,0 ; 7:E:0"
|
149
|
+
])
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
describe "#convert!" do
|
154
|
+
subject { converter.convert! }
|
155
|
+
it "calls all the necessary bits" do
|
156
|
+
expect(converter).to receive(:score_preamble)
|
157
|
+
expect(converter).to receive(:score_body)
|
158
|
+
expect(converter).to receive(:score_coda)
|
159
|
+
expect($stderr).to receive(:puts).exactly(4).times
|
160
|
+
expect($stdout).to receive(:puts).exactly(3).times
|
161
|
+
subject
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StringyFi::Measures do
|
4
|
+
|
5
|
+
let(:note_class) { StringyFi::Note }
|
6
|
+
let(:measures) { described_class.new }
|
7
|
+
|
8
|
+
before do
|
9
|
+
measures << [
|
10
|
+
note_class.new('G', 4, 1, 1, "half"),
|
11
|
+
note_class.new('G', 2, 0, 1, "quarter")
|
12
|
+
]
|
13
|
+
measures << [
|
14
|
+
note_class.new('G', 3, 0, 1, "whole"),
|
15
|
+
note_class.new('A', 4, 0, 1, "32nd"),
|
16
|
+
note_class.new('G', 4, 1, 1, "eighth")
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#shortest_fractional_duration" do
|
21
|
+
subject { measures.shortest_fractional_duration }
|
22
|
+
it "returns the shortest of any note" do
|
23
|
+
expect(subject).to eql(1/32.0)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "#octave_range" do
|
28
|
+
subject { measures.octave_range }
|
29
|
+
it "returns the lowest and highest as an array" do
|
30
|
+
expect(subject).to eql([2, 4])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StringyFi::Note do
|
4
|
+
|
5
|
+
let(:name) { 'G' }
|
6
|
+
let(:octave) { '4' }
|
7
|
+
let(:alter) { nil }
|
8
|
+
let(:duration) { 1 }
|
9
|
+
let(:duration_type) { "quarter" }
|
10
|
+
subject(:note) { described_class.new(name, octave, alter, duration, duration_type) }
|
11
|
+
|
12
|
+
|
13
|
+
describe "#stringy_durations" do
|
14
|
+
subject { note.stringy_durations(shortest_fractional_duration)}
|
15
|
+
context "when quarter is shortest" do
|
16
|
+
let(:shortest_fractional_duration) { 1/4.0 }
|
17
|
+
[
|
18
|
+
{duration: 1, duration_type: "quarter", expect: [1, 0, 0, 0]},
|
19
|
+
{duration: 2, duration_type: "quarter", expect: [0, 1, 0, 0]},
|
20
|
+
{duration: 3, duration_type: "quarter", expect: [0, 1, 0, 0]},
|
21
|
+
{duration: 4, duration_type: "quarter", expect: [0, 0, 1, 0]},
|
22
|
+
{duration: 5, duration_type: "quarter", expect: [0, 0, 1, 0]},
|
23
|
+
{duration: 6, duration_type: "quarter", expect: [0, 0, 1, 0]},
|
24
|
+
{duration: 7, duration_type: "quarter", expect: [0, 0, 1, 0]},
|
25
|
+
{duration: 1, duration_type: "half", expect: [0, 1, 0, 0]},
|
26
|
+
{duration: 4, duration_type: "half", expect: [0, 0, 0, 1]},
|
27
|
+
{duration: 1, duration_type: "whole", expect: [0, 0, 1, 0]},
|
28
|
+
{duration: 2, duration_type: "whole", expect: [0, 0, 0, 1]},
|
29
|
+
].each do |options|
|
30
|
+
context "and duration=#{options[:duration]}-#{options[:duration_type]}" do
|
31
|
+
let(:duration) { options[:duration] }
|
32
|
+
let(:duration_type) { options[:duration_type] }
|
33
|
+
it "returns expected repeats/remainder" do
|
34
|
+
expect(subject).to eql(options[:expect])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
context "when 32nd is shortest" do
|
40
|
+
let(:shortest_fractional_duration) { 1/32.0 }
|
41
|
+
[
|
42
|
+
{duration: 1, duration_type: "32nd", expect: [1, 0, 0, 0]},
|
43
|
+
{duration: 1, duration_type: "quarter", expect: [0, 0, 0, 1]},
|
44
|
+
].each do |options|
|
45
|
+
context "and duration=#{options[:duration]}-#{options[:duration_type]}" do
|
46
|
+
let(:duration) { options[:duration] }
|
47
|
+
let(:duration_type) { options[:duration_type] }
|
48
|
+
it "returns expected repeats/remainder" do
|
49
|
+
expect(subject).to eql(options[:expect])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#to_s" do
|
57
|
+
subject { note.to_s }
|
58
|
+
context "when not altered" do
|
59
|
+
it { should eql('G')}
|
60
|
+
end
|
61
|
+
context "when altered up" do
|
62
|
+
let(:alter) { 1 }
|
63
|
+
it { should eql('G#')}
|
64
|
+
end
|
65
|
+
context "when altered down" do
|
66
|
+
let(:alter) { -1 }
|
67
|
+
it { should eql('Gb')}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#to_str" do
|
72
|
+
subject { note.to_str }
|
73
|
+
context "when not altered" do
|
74
|
+
it { should eql('4:G:0')}
|
75
|
+
end
|
76
|
+
context "when altered up" do
|
77
|
+
let(:alter) { 1 }
|
78
|
+
it { should eql('4:G:1')}
|
79
|
+
end
|
80
|
+
context "when altered down" do
|
81
|
+
let(:alter) { -1 }
|
82
|
+
it { should eql('4:G:-1')}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe "#<=>" do
|
87
|
+
let(:other_name) { name }
|
88
|
+
let(:other_octave) { octave }
|
89
|
+
let(:other_alter) { alter }
|
90
|
+
let(:other_note) { described_class.new(other_name, other_octave, other_alter) }
|
91
|
+
|
92
|
+
subject { note <=> other_note }
|
93
|
+
context "when same" do
|
94
|
+
it { should eql(0) }
|
95
|
+
end
|
96
|
+
context "when vary by alter up" do
|
97
|
+
let(:other_alter) { 1 }
|
98
|
+
it { should eql(-1) }
|
99
|
+
end
|
100
|
+
context "when vary by octave down" do
|
101
|
+
let(:other_octave) { 3 }
|
102
|
+
it { should eql(1) }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe StringyFi::Shell do
|
4
|
+
subject(:shell) { described_class.new }
|
5
|
+
|
6
|
+
context "when no args passed" do
|
7
|
+
it "initialises with sample fixture" do
|
8
|
+
expect(subject).to be_a(described_class)
|
9
|
+
expect(subject.converter.filename).to include("chromatic.xml")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/stringyfi.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'stringyfi/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "stringyfi"
|
8
|
+
spec.version = StringyFi::VERSION
|
9
|
+
spec.authors = ["Paul Gallagher"]
|
10
|
+
spec.email = ["gallagher.paul@gmail.com"]
|
11
|
+
spec.summary = "Convert MusicXML to PIC assembler for the Boldport Stringy"
|
12
|
+
spec.homepage = "https://github.com/tardate/stringyfi"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_runtime_dependency "nokogiri", "~> 1.8"
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
|
26
|
+
# guard versions are pegged to avoid issue with ruby_dep requires Ruby version >= 2.2.5, ~> 2.2.
|
27
|
+
spec.add_development_dependency "guard-rspec", "4.6.4"
|
28
|
+
spec.add_development_dependency "rb-fsevent", "0.9.6"
|
29
|
+
spec.add_development_dependency "rb-inotify", "0.9.5"
|
30
|
+
spec.add_development_dependency "pry-coolline", "0.2.5" # avoid readline dependency
|
31
|
+
end
|