lncs 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in lncs-fake.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,35 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ lncs (0.0.1)
5
+ json (~> 1.7)
6
+ pdf-reader (~> 1.3)
7
+ thor (~> 0.18)
8
+ zip (~> 2.0)
9
+
10
+ GEM
11
+ remote: https://rubygems.org/
12
+ specs:
13
+ Ascii85 (1.0.2)
14
+ afm (0.2.0)
15
+ hashery (2.1.0)
16
+ json (1.7.7)
17
+ pdf-reader (1.3.3)
18
+ Ascii85 (~> 1.0.0)
19
+ afm (~> 0.2.0)
20
+ hashery (~> 2.0)
21
+ ruby-rc4
22
+ ttfunk
23
+ rake (10.0.4)
24
+ ruby-rc4 (0.1.5)
25
+ thor (0.18.1)
26
+ ttfunk (1.0.3)
27
+ zip (2.0.2)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler (~> 1.3)
34
+ lncs!
35
+ rake
data/LICENCE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 Louis M. Rose
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,176 @@
1
+ LNCS Volume Editor Tools
2
+ ========================
3
+ A small set of tools that automate some of the more tedious parts of preparing an LNCS volume.
4
+
5
+ The conventions enforced by `lncs` are intended to comply with the LNCS guidelines for volume editors. If you find an inconsistency between `lncs` and the LNCS guidelines, please [file a bug report](https://github.com/louismrose/lncs/issues).
6
+
7
+ Installation
8
+ ------------
9
+ You'll need to install Ruby 1.9 or later. I recommend doing so via [rvm](https://rvm.io).
10
+
11
+ You can then install `lncs` via Ruby gems: `gem install lncs`
12
+
13
+ Get started with the `lncs` executable: `lncs help`
14
+
15
+ Recommended workflow
16
+ --------------------
17
+
18
+ 1. Download the submissions for your volume
19
+
20
+ 2. Setup a working directory
21
+
22
+ > mkdir ecmfa2013
23
+ > cd ecmfa2013
24
+ > lncs init
25
+
26
+ 3. Customise the manifest (e.g., set the path to the submissions, the volume number, ...)
27
+
28
+ > vi manifest.json
29
+ > cat manifest.json
30
+ {
31
+ "volume_number": 7949,
32
+ "sources": "/Users/louis/Downloads/submissions",
33
+ "sections": [
34
+ {
35
+ title: "Foundations",
36
+ papers: [7,11,14,18,20,24,35,46,63]
37
+ },
38
+ {
39
+ title: "Applications",
40
+ papers: [2,6,22,29,58,68]
41
+ }
42
+ ]
43
+ }
44
+
45
+ 4. Unpack and inspect all of the submissions
46
+
47
+ > lncs inspect
48
+ > cd submissions
49
+ > ls -R
50
+
51
+ ./ecmfa2013_submission_07:
52
+ 7
53
+ ./ecmfa2013_submission_07/7:
54
+ paper7.tex paper7.pdf copyright.pdf
55
+
56
+ ./ecmfa2013_submission_24:
57
+ ECMFA2013-cameraready.pdf ECMFA2013-cameraready.docx copyright.pdf
58
+
59
+
60
+ 5. Update the manifest with the paths to the PDF files of each paper
61
+
62
+ > vi manifest.json
63
+ > cat manifest.json
64
+ {
65
+ "volume_number": 7949,
66
+ "sources": "/Users/louis/Downloads/submissions",
67
+ "sections": [
68
+ {
69
+ title: "Foundations",
70
+ papers: [7,11,14,18,20,24,35,46,63]
71
+ },
72
+ {
73
+ title: "Applications",
74
+ papers: [2,6,22,29,58,68]
75
+ }
76
+ ],
77
+ papers: {
78
+ "7" : {
79
+ pdf: "7/paper.pdf"
80
+ },
81
+ "24" : {
82
+ pdf: "ECMFA2013-cameraready.pdf"
83
+ }
84
+ }
85
+ }
86
+
87
+ 6. Check the status of the submissions
88
+
89
+ > lncs report
90
+ Foundations
91
+ 007 -- 17pgs zip
92
+ 011 -- 17pgs zip
93
+ 014 -- 17pgs zip
94
+ 018 -- 16pgs zip
95
+ 020 -- 16pgs zip
96
+ 024 -- 16pgs zip
97
+ 035 -- 17pgs zip
98
+ 046 -- 16pgs zip
99
+ 063 -- 17pgs zip
100
+ Applications
101
+ 002 -- 13pgs zip
102
+ 006 -- 13pgs zip
103
+ 022 -- 13pgs zip
104
+ 029 -- 13pgs zip
105
+ 058 -- 13pgs zip
106
+ 068 -- 11pgs zip
107
+
108
+ If a submission exceeds your page limit, you may wish to contact the authors. If a submission is a PDF rather than a ZIP file, you may wish to do the same as you will need to send to Springer the sources and a signed copyright form for each submission.
109
+
110
+ 7. Generate the set of directories required by Springer for the body of the proceedings
111
+
112
+ > lncs body
113
+ > ls body
114
+ 79490001 79490020 79490054 79490086 79490119 79490152 79490178 79490204
115
+ 79490003 79490037 79490070 79490102 79490135 79490165 79490191 79490217
116
+
117
+ > ls -R body/79490001
118
+ 7
119
+
120
+ body/79490001/7:
121
+ paper7.tex paper7.pdf copyright.pdf
122
+
123
+ 8. Generate the title pages used to construct the table of contents and author index
124
+
125
+ > lncs titles
126
+ > ls titles
127
+ 0001.tex 0020.tex 0054.tex 0086.tex 0119.tex 0152.tex 0178.tex 0204.tex index.tex
128
+ 0003.tex 0037.tex 0070.tex 0102.tex 0135.tex 0165.tex 0191.tex 0217.tex
129
+
130
+ 9. Run LaTeX to produce your PDF
131
+
132
+ > latex2pdf main.tex
133
+ > open main.pdf
134
+
135
+ 10. Override any titles or names of authors (because, for example, `lncs` cannot extract title page information from MS Word source files).
136
+
137
+ > vi manifest.json
138
+ > cat manifest.json
139
+ {
140
+ "volume_number": 7949,
141
+ "sources": "/Users/louis/Downloads/submissions",
142
+ "sections": [
143
+ {
144
+ title: "Foundations",
145
+ papers: [7,11,14,18,20,24,35,46,63]
146
+ },
147
+ {
148
+ title: "Applications",
149
+ papers: [2,6,22,29,58,68]
150
+ }
151
+ ],
152
+ papers: {
153
+ "7" : {
154
+ pdf: "7/paper.pdf",
155
+ title: "MOCQL: A Declarative Language for Ad-Hoc Model Querying",
156
+ authors: ["Harald St\\\"orrle"]
157
+ },
158
+ "24" : {
159
+ pdf: "ECMFA2013-cameraready.pdf"
160
+ }
161
+ }
162
+ }
163
+
164
+ 11. Regenerate your titles and PDF.
165
+
166
+ > lncs titles
167
+ > latex2pdf main.tex
168
+ > open main.pdf
169
+
170
+
171
+ Contributing
172
+ ------------
173
+ I'm afraid that this code is not well tested, factored or documented. It was quickly hacked together whilst in my first attempt to edit an LNCS volume. If I'm ever asked to edit a second volume, I plan to tidy up this gem and add a few more features. In the meantime, pull requests are very welcome.
174
+
175
+ Todo list:
176
+ * Better error reporting when the titles key is specified but not authors and vice versa
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/lncs ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Trap interrupts to quit cleanly. See
4
+ # https://twitter.com/mitchellh/status/283014103189053442
5
+ Signal.trap("INT") { exit 1 }
6
+
7
+ require 'lncs'
8
+ require 'lncs/cli'
9
+ LNCS::CLI.start
@@ -0,0 +1,7 @@
1
+ require "thor"
2
+
3
+ module LNCS
4
+ class Actions < Thor
5
+ include Thor::Actions
6
+ end
7
+ end
data/lib/lncs/cli.rb ADDED
@@ -0,0 +1,62 @@
1
+ require "thor"
2
+ require "json"
3
+
4
+ module LNCS
5
+ class CLI < Thor
6
+ include Thor::Actions
7
+
8
+ desc "init", "Start an lncs project in the current directory"
9
+ def init
10
+ Initialiser.new.run
11
+ end
12
+
13
+ desc "clean", "Remove all working directories"
14
+ def clean
15
+ working_directories = %w{submissions body titles}
16
+ working_directories.each {|d| remove_dir(d) }
17
+ create_file 'titles/index.tex'
18
+ end
19
+
20
+ desc "inspect", "Unpack the submissions so that their contents can be inspected manually"
21
+ def inspect
22
+ remake_directory("submissions")
23
+ proceedings.copy_to("submissions")
24
+ end
25
+
26
+ desc "body", "Generate the set of directories containing the body of the report, in the format required by Springer LNCS"
27
+ def body
28
+ remake_directory("body")
29
+ proceedings.generate_body_to("body")
30
+ end
31
+
32
+ desc "titles", "Generate a title page for each submission, including correct page numbers and author index entries"
33
+ def titles
34
+ remake_directory("titles")
35
+ proceedings.generate_titles_to("titles")
36
+ end
37
+
38
+ desc "report", "Print useful statistics about the submissions"
39
+ def report
40
+ proceedings.report
41
+ end
42
+
43
+ private
44
+ def remake_directory(dir)
45
+ remove_dir dir
46
+ empty_directory dir
47
+ end
48
+
49
+ def proceedings
50
+ manifest_missing unless File.exist?("manifest.json")
51
+ Proceedings.new.tap do |p|
52
+ p.manifest = JSON.parse(File.read("manifest.json"))
53
+ end
54
+ end
55
+
56
+ def manifest_missing
57
+ puts "Error: lncs could not find a manifest.json file in the current directory."
58
+ puts "You can use 'lncs init' to create a basic manifest file."
59
+ exit 1
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ module LNCS
2
+ class Initialiser < Thor
3
+ include Thor::Actions
4
+
5
+ def self.source_root
6
+ File.join(File.dirname(__FILE__), '..', '..', 'templates')
7
+ end
8
+
9
+ no_commands do
10
+ def run
11
+ copy_file 'manifest.json'
12
+ copy_file 'main.tex'
13
+ copy_file 'llncs.cls'
14
+ copy_file 'sprmindx.sty'
15
+ copy_file 'front_matter/organisation.tex'
16
+ copy_file 'front_matter/preface.tex'
17
+ copy_file 'front_matter/sponsors.tex'
18
+ create_file 'titles/index.tex'
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/lncs/paper.rb ADDED
@@ -0,0 +1,166 @@
1
+ require 'tmpdir'
2
+ require 'pdf-reader'
3
+ require 'zip/zipfilesystem'
4
+ require 'lncs/actions'
5
+
6
+ module LNCS
7
+ class Paper
8
+ attr_accessor :manifest, :path
9
+
10
+ def pdf
11
+ manifest["pdf"]
12
+ end
13
+
14
+ def title
15
+ manifest["title"]
16
+ end
17
+
18
+ def authors
19
+ manifest["authors"]
20
+ end
21
+
22
+ def name
23
+ File.basename(path)
24
+ end
25
+
26
+ def id
27
+ name.match(/(\d+).(pdf|zip)/)[1]
28
+ end
29
+
30
+ def type
31
+ name.match(/(pdf|zip)/)[1]
32
+ end
33
+
34
+ def generate_title_to(dst, start_page)
35
+ raise "Error: Cannot generate title from PDF for paper ##{id}" if type == "pdf"
36
+ check_pdf_exists
37
+
38
+ captured = title_page_from_manifest_or_latex
39
+ captured += "\n" + authors_from_manifest_or_latex.map do |a|
40
+ a.gsub!(/(?<forename>\S*) (?<surname>.*)/, '\k<surname>, \k<forename>') if a
41
+ "\\index{#{a}}"
42
+ end.join("\n") + "\n"
43
+
44
+ captured += "\\maketitle\n"
45
+ captured += "\\clearpage\n"
46
+ captured += "\\setcounter{page}{#{start_page + page_count}}"
47
+
48
+ actions.create_file(dst, captured)
49
+ end
50
+
51
+ def copy_to(dst)
52
+ check_pdf_exists
53
+
54
+ FileUtils.mkdir_p(dst)
55
+
56
+ if type == "zip"
57
+ Zip::ZipFile.open(path) do |zipfile|
58
+ zipfile.select { |file| zipfile.get_entry(file).file? }.each do |file|
59
+ actions.create_file(File.join(dst, file.name), zipfile.read(file))
60
+ end
61
+ end
62
+ else
63
+ actions.copy_file(path, File.join(dst, name))
64
+ end
65
+ end
66
+
67
+ def page_count
68
+ check_pdf_exists
69
+
70
+ PDF::Reader.new(open_pdf).page_count
71
+ end
72
+
73
+ private
74
+ def check_pdf_exists
75
+ raise "Error: no file found at path '#{path}'" unless File.exist?(path)
76
+
77
+ if type == "zip"
78
+ pdf_exists = Zip::ZipFile.open(path) { |zipfile| zipfile.find_entry(pdf) }
79
+ raise "Error: no PDF file found at path '#{pdf}' within the ZIP file #{path}" unless pdf_exists
80
+ end
81
+ end
82
+
83
+ def open_pdf
84
+ if type == "zip"
85
+ extracted_pdf = File.join(Dir.tmpdir, pdf)
86
+ Zip::ZipFile.open(path) do |zipfile|
87
+ FileUtils.mkdir_p(File.dirname(extracted_pdf))
88
+ zipfile.extract(pdf, extracted_pdf) { true }
89
+ end
90
+ File.open(extracted_pdf, "rb")
91
+ else
92
+ File.open(path, "rb")
93
+ end
94
+ end
95
+
96
+ def authors_from_manifest_or_latex
97
+ if authors
98
+ authors
99
+ else
100
+ regex = %r{
101
+ \\author(?<re>
102
+ \{
103
+ (?:
104
+ (?> [^{}]+ )
105
+ |
106
+ \g<re>
107
+ )*
108
+ \}
109
+ )
110
+ }x
111
+
112
+ title_page = title_page_from_manifest_or_latex
113
+ if regex.match(title_page)
114
+ author_tag = regex.match(title_page)[1]
115
+ author_tag = author_tag[1..-2] # strip starting and closing bracket
116
+ author_tag.gsub! /\\\\/, '' # strip forced linebreaks
117
+ author_tag.gsub! /\\inst\{[^\}]*?\}/, '' # strip institutions
118
+ author_tag.split('\and').map do |a|
119
+ a.strip!
120
+ end
121
+ else
122
+ []
123
+ end
124
+ end
125
+ end
126
+
127
+ def title_page_from_manifest_or_latex
128
+ if title
129
+ """
130
+ \\title{#{title}}\n
131
+ \\author{#{authors_from_manifest_or_latex.join(" \\and ")}}\n
132
+ """
133
+ else
134
+ captured = ""
135
+ each_tex do |tex|
136
+ capturing = false
137
+ while (line = tex.gets)
138
+ break if line.include? '\maketitle'
139
+ captured += line if capturing and not line.include? '\frontmatter' and not line.include? '\mainmatter'
140
+ capturing = true if line.include? '\begin{document}'
141
+ end
142
+ end
143
+ captured.gsub /\\mails[a-z](\\\\)?/, '$ $' # remove any \mailsa or similar commands
144
+ end
145
+ end
146
+
147
+ def each_tex
148
+ if type == "zip"
149
+ Zip::ZipFile.open(path) do |zipfile|
150
+ zipfile.each do |file|
151
+ if file.name.end_with? "tex" and not file.name.end_with? "llncs.tex"
152
+ extracted_tex = File.join(Dir.tmpdir, file.name)
153
+ FileUtils.mkdir_p(File.dirname(extracted_tex))
154
+ zipfile.extract(file, extracted_tex) { true }
155
+ File.open(extracted_tex, "rb") { |file| yield(file) }
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def actions
163
+ Actions.new
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,75 @@
1
+ require 'lncs/actions'
2
+
3
+ module LNCS
4
+ class Proceedings
5
+ attr_accessor :manifest
6
+
7
+ def volume_number
8
+ manifest["volume_number"]
9
+ end
10
+
11
+ def source_directory
12
+ manifest["sources"]
13
+ end
14
+
15
+ def papers
16
+ manifest["papers"]
17
+ end
18
+
19
+ def paper_file_name_for(paper_id)
20
+ Dir.chdir(source_directory) do
21
+ Dir.glob("*_#{paper_id}.{pdf,zip}")[0]
22
+ end
23
+ end
24
+
25
+ def paper_path_for(paper_id)
26
+ filename = paper_file_name_for(paper_id)
27
+ raise "Error: no file with name ending '#{paper_id}.pdf' or '#{paper_id}.zip' found at path '#{source_directory}'" unless filename
28
+ File.join(source_directory, filename)
29
+ end
30
+
31
+ def paper_manifest_for(paper_id)
32
+ paper_manifest = papers.fetch(paper_id.to_s, {})
33
+ paper_manifest["pdf"] = paper_file_name_for(paper_id) unless paper_manifest["pdf"]
34
+ paper_manifest
35
+ end
36
+
37
+ def sections
38
+ manifest["sections"].map do |section|
39
+ Section.new.tap do |s|
40
+ s.manifest = section
41
+ s.proceedings = self
42
+ end
43
+ end
44
+ end
45
+
46
+ def copy_to(dst)
47
+ sections.each { |section| section.copy_to(dst) }
48
+ end
49
+
50
+ def generate_body_to(dst)
51
+ start_page = 1
52
+ sections.each do |s|
53
+ start_page = s.generate_body_to(dst, volume_number, start_page)
54
+ end
55
+ end
56
+
57
+ def generate_titles_to(dst)
58
+ actions.remove_file("#{dst}/index.tex")
59
+ actions.create_file("#{dst}/index.tex")
60
+ start_page = 1
61
+ sections.each do |s|
62
+ start_page = s.generate_titles_to(dst, start_page)
63
+ end
64
+ end
65
+
66
+ def report
67
+ sections.each { |s| s.report }
68
+ end
69
+
70
+ private
71
+ def actions
72
+ Actions.new
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,60 @@
1
+ require 'lncs/actions'
2
+
3
+ module LNCS
4
+ class Section
5
+ attr_accessor :manifest, :proceedings
6
+
7
+ def papers
8
+ manifest["papers"].map do |paper_id|
9
+ Paper.new.tap do |p|
10
+ p.manifest = proceedings.paper_manifest_for(paper_id)
11
+ p.path = proceedings.paper_path_for(paper_id)
12
+ end
13
+ end
14
+ end
15
+
16
+ def title
17
+ manifest["title"]
18
+ end
19
+
20
+ def copy_to(dst)
21
+ papers.each { |paper| paper.copy_to("#{dst}/#{paper.id}") }
22
+ end
23
+
24
+ def generate_body_to(dst, volume_number, start_page)
25
+ papers.each do |paper|
26
+ padded_start_page = "%04d" % start_page.to_s
27
+ paper.copy_to("#{dst}/#{volume_number}#{padded_start_page}")
28
+ start_page += paper.page_count
29
+ end
30
+ start_page
31
+ end
32
+
33
+ def generate_titles_to(dst, start_page)
34
+ titles = []
35
+ papers.each do |paper|
36
+ padded_start_page = "%04d" % start_page.to_s
37
+ titles << "#{dst}/#{padded_start_page}.tex"
38
+ paper.generate_title_to(titles.last, start_page)
39
+ start_page += paper.page_count
40
+ end
41
+
42
+ actions.append_file("#{dst}/index.tex") do
43
+ "\\addtocmark{#{title}}\n" +
44
+ titles.map {|title| "\\input{#{title}}\n"}.join
45
+ end
46
+
47
+ start_page
48
+ end
49
+
50
+ def report
51
+ puts title
52
+ papers.each { |paper| puts "#{"%03d" % paper.id} -- #{paper.page_count}pgs #{paper.type}" }
53
+ end
54
+
55
+ private
56
+ def actions
57
+ Actions.new
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,3 @@
1
+ module LNCS
2
+ VERSION = "0.0.1"
3
+ end
data/lib/lncs.rb ADDED
@@ -0,0 +1,6 @@
1
+ module LNCS
2
+ autoload :Initialiser, 'lncs/initialiser'
3
+ autoload :Paper, 'lncs/paper'
4
+ autoload :Proceedings, 'lncs/proceedings'
5
+ autoload :Section, 'lncs/section'
6
+ end
data/lncs.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ lib = File.expand_path('../lib/', __FILE__)
2
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
3
+ require 'lncs/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lncs'
7
+ spec.version = LNCS::VERSION
8
+ spec.licenses = ['MIT']
9
+ spec.authors = ["Louis M. Rose"]
10
+ spec.email = ["louismrose@gmail.com"]
11
+ spec.homepage = "https://github.com/louismrose/lncs"
12
+ spec.summary = %q{A few tools for automating the preparation of a Springer LNCS volume}
13
+ spec.description = %q{Automates the production of tables of contents and author indexes, and the arrangement of papers for a Springer LNCS volume.}
14
+
15
+ spec.required_ruby_version = '>= 1.9.3'
16
+ spec.required_rubygems_version = '>= 1.3.6'
17
+
18
+ spec.files = `git ls-files`.split($/)
19
+ spec.executables = %w(lncs)
20
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency "json", "~> 1.7"
24
+ spec.add_dependency "thor", "~> 0.18"
25
+ spec.add_dependency "zip", "~> 2.0"
26
+ spec.add_dependency "pdf-reader", "~> 1.3"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.3"
29
+ spec.add_development_dependency "rake"
30
+ end