ebook_generator 0.0.5 → 1.0.2

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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/{LICENSE.txt → MIT-LICENSE} +1 -3
  3. data/README.md +10 -17
  4. data/Rakefile +17 -1
  5. data/lib/ebook_generator.rb +75 -72
  6. data/lib/ebook_generator/version.rb +1 -1
  7. data/lib/generators/ebook_generator/ebook_generator_generator.rb +16 -0
  8. data/lib/generators/ebook_generator/templates/create_ebooks.rb +18 -0
  9. data/lib/generators/ebook_generator/templates/create_sections.rb +13 -0
  10. data/lib/generators/ebook_generator/templates/ebook.rb +8 -0
  11. data/lib/generators/ebook_generator/templates/section.rb +6 -0
  12. data/lib/tasks/ebook_generator_tasks.rake +4 -0
  13. data/spec/dummy/README.rdoc +28 -0
  14. data/spec/dummy/Rakefile +6 -0
  15. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  16. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  17. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  18. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  19. data/spec/dummy/app/models/ebook.rb +4 -0
  20. data/spec/dummy/app/models/section.rb +7 -0
  21. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/spec/dummy/bin/bundle +3 -0
  23. data/spec/dummy/bin/rails +4 -0
  24. data/spec/dummy/bin/rake +4 -0
  25. data/spec/dummy/config.ru +4 -0
  26. data/spec/dummy/config/application.rb +34 -0
  27. data/spec/dummy/config/boot.rb +5 -0
  28. data/spec/dummy/config/database.yml +25 -0
  29. data/spec/dummy/config/environment.rb +5 -0
  30. data/spec/dummy/config/environments/development.rb +37 -0
  31. data/spec/dummy/config/environments/production.rb +82 -0
  32. data/spec/dummy/config/environments/test.rb +39 -0
  33. data/spec/dummy/config/initializers/assets.rb +8 -0
  34. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  35. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  36. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  37. data/spec/dummy/config/initializers/inflections.rb +16 -0
  38. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  39. data/spec/dummy/config/initializers/session_store.rb +3 -0
  40. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  41. data/spec/dummy/config/locales/en.yml +23 -0
  42. data/spec/dummy/config/routes.rb +56 -0
  43. data/spec/dummy/config/secrets.yml +22 -0
  44. data/spec/dummy/db/development.sqlite3 +0 -0
  45. data/spec/dummy/db/migrate/20140416030807_ebooks.rb +17 -0
  46. data/spec/dummy/db/migrate/20140416032844_sections.rb +12 -0
  47. data/spec/dummy/db/migrate/20140416033000_add_index_to_sections_ebook_id_position.rb +6 -0
  48. data/spec/dummy/db/migrate/20140416033315_add_column_slug_to_ebooks.rb +6 -0
  49. data/spec/dummy/db/schema.rb +44 -0
  50. data/spec/dummy/db/test.sqlite3 +0 -0
  51. data/spec/dummy/log/development.log +549 -0
  52. data/spec/dummy/log/test.log +4812 -0
  53. data/spec/dummy/public/404.html +67 -0
  54. data/spec/dummy/public/422.html +67 -0
  55. data/spec/dummy/public/500.html +66 -0
  56. data/spec/dummy/public/favicon.ico +0 -0
  57. data/spec/dummy/tmp/Qui-voluptates-consequuntur-aliquam-iste-numquam..epub +0 -0
  58. data/spec/dummy/tmp/Ratione-soluta-magni-consectetur-delectus-sequi-necessitatibus..epub +0 -0
  59. data/spec/ebook_generator_spec.rb +56 -0
  60. data/spec/factories/factories.rb +35 -0
  61. data/spec/helpers/unzip.rb +13 -0
  62. data/spec/models/ebook_spec.rb +5 -0
  63. data/spec/models/section_spec.rb +5 -0
  64. data/spec/spec_helper.rb +21 -0
  65. data/spec/support/factory_girl.rb +3 -0
  66. metadata +176 -43
  67. data/.gitignore +0 -22
  68. data/Changelog.md +0 -23
  69. data/Gemfile +0 -3
  70. data/ebook_generator.gemspec +0 -28
  71. data/lib/ebook_generator/initializer.rb +0 -5
  72. data/lib/ebook_generator/migration.rb +0 -30
  73. data/lib/generators/ebook_generator_generator.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9bd956ba768bbc1c3c607b1b4f3dd7aca5108190
4
- data.tar.gz: 84b1f546c5ffc29dc60d82b76d731897058ba052
3
+ metadata.gz: 48d8c90867c3202845223f9a32ddfa97355c91c2
4
+ data.tar.gz: 6bc9b510a69a4408fd05edcb4222a9e4e0b0cd0d
5
5
  SHA512:
6
- metadata.gz: e2a11d62c96e321cd61e51bd951a9cc34a604b8c6bdeeedd4bafc954a03bde6f83f6c2d07124565ba03120dc02efd2495f29c9b1fa2bb3ded6bce29d87807752
7
- data.tar.gz: ba678ffba0836f3e65daede396bf2edcde3d6e485e4055e0e3c1c148b2d17e24a78e41071f3e18916160247827d0f999ffb929a1ae9f5a8c5567065eaa73ed37
6
+ metadata.gz: 3f5ce5e4a77897bd07833cb2f3a3267cd69a7e51810153e81f4ee04011a620b4e1d85957c6cb3d427ed1e22353526df6adc8dd6d63550985ed8c5260f809d0ee
7
+ data.tar.gz: b3b7e0fb42a30ca9346fae2b4725c90e34281a5114195c0f9fa846db273349ce945b45d545238bbdff0b2e7c7becb0041de28e8d45a48a06d4e120524bb3df8c
@@ -1,6 +1,4 @@
1
- Copyright (c) 2014 Kris Quigley
2
-
3
- MIT License
1
+ Copyright 2014 YOURNAME
4
2
 
5
3
  Permission is hereby granted, free of charge, to any person obtaining
6
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -5,10 +5,9 @@ Rails gem to generate eBooks using the Markdown syntax to format content for sec
5
5
  An example of this gem being used can be found at: [ebook-generator.affinity-tech.com](http://ebook-generator.affinity-tech.com)
6
6
 
7
7
  Requires:
8
- - Postgres with UUID support (uses the id to generate the ebook UUID)
8
+ - Rails 4
9
9
  - Rubyzip (to generate the .epub)
10
10
  - Redcarpet (to render Markdown into HTML)
11
- - Friendly_id (for nice slugs and for naming of generated ebook file)
12
11
 
13
12
  ## Installation
14
13
 
@@ -32,35 +31,29 @@ Generate the tables needed to process the ebooks:
32
31
  Migrate the database:
33
32
  `rake db:migrate`
34
33
 
35
- Pass the id for the ebook you want to generate:
36
- `EbookGenerator.generate_ebook(ebook.id)`
34
+ Pass the ebook object you want to generate:
35
+ `EbookGenerator.generate_ebook(ebook_object)`
37
36
 
38
37
  This will then generate an ePub based on the values in the db and output to the /tmp folder.
39
38
 
40
- ## Feature roadmap
39
+ For a Kindle mobi file then pass in `true` as the second argument:
40
+ `EbookGenerator.generate_ebook(ebook_object, true)`
41
41
 
42
- ### 0.1.0
43
- - Kindle support
44
- - Tests
42
+ ## Feature roadmap
45
43
 
46
- ### 0.2.0
44
+ ### 1.1.0
47
45
  - Style editing
48
46
 
49
- ### 0.3.0
47
+ ### 1.2.0
50
48
  - PDF out
51
49
 
52
- ### 0.4.0
50
+ ### 1.3.0
53
51
  - HTML out
54
52
  - Image support for the front cover
55
53
 
56
- ### 0.5.0
54
+ ### 1.4.0
57
55
  - HTML to eBook conversion
58
56
 
59
- ### 0.6.0
60
- - User membership to manage books
61
-
62
- Publishing support?
63
-
64
57
  ## Contributing
65
58
 
66
59
  1. Fork it ( https://github.com/[my-github-username]/ebook_generator/fork )
data/Rakefile CHANGED
@@ -1,2 +1,18 @@
1
- require 'bundler'
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+
8
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
9
+
2
10
  Bundler::GemHelper.install_tasks
11
+
12
+ require 'rspec/core'
13
+ require 'rspec/core/rake_task'
14
+
15
+ desc "Run all specs in spec directory (excluding plugin specs)"
16
+ RSpec::Core::RakeTask.new(:spec => 'app:db:test:prepare')
17
+
18
+ task :default => :spec
@@ -1,14 +1,57 @@
1
1
  require "ebook_generator/version"
2
2
  require "ebook_generator/zip_file_processor"
3
3
  require "builder"
4
+ require "redcarpet"
4
5
 
5
6
  module EbookGenerator
6
7
 
7
- def self.included(model_class)
8
- model_class.extend self
8
+ # def self.included(model_class)
9
+ # model_class.extend self
10
+ # end
11
+
12
+ def self.generate_ebook(ebook_object, generate_kindle_file = false)
13
+ # Set the root path of the ebook
14
+ path = Rails.root.to_s + "/tmp/#{ebook_object.id}"
15
+
16
+ # Make all required dirs
17
+ dirs = [path, path + "/META-INF", path + "/OEBPS", path + "/OEBPS/Text", path + "/OEBPS/Styles"]
18
+ make_dirs(dirs)
19
+
20
+ # Create container.xml
21
+ generate_container(path + "/META-INF")
22
+
23
+ # Create mimetype
24
+ generate_mimetype(path + "/mimetype")
25
+
26
+ # Move default stylesheet into styles folder
27
+ copy_style(path + "/OEBPS/Styles")
28
+
29
+ # loop through each section loading the reference header and saving as it's own section
30
+ generate_sections(path + "/OEBPS/Text", ebook_object)
31
+
32
+ # generate toc based on the number of sections generated
33
+ generate_content_opf(path + "/OEBPS", ebook_object)
34
+ generate_toc_ncx(path + "/OEBPS", ebook_object)
35
+
36
+ # change permissions for files that need to be executable
37
+ files = [path + "/OEBPS/toc.ncx", path + "/OEBPS/content.opf", path + "/mimetype"]
38
+ change_perms(files)
39
+
40
+ # zip all files
41
+ zipfile_name = Rails.root.to_s + "/tmp/" + ebook_object.slug + ".epub"
42
+ zf = ZipFileProcessor.new(path, zipfile_name)
43
+ zf.write
44
+
45
+ # Clean up the tmp dir
46
+ remove_tmp_dir(path + "/")
47
+
48
+ # return the file path
49
+ zipfile_name = generate_mobi(zipfile_name, ebook_object.slug) if generate_kindle_file
50
+ zipfile_name
9
51
  end
10
52
 
11
- # Move writing of files into its own class that accepts an array
53
+ private
54
+
12
55
  def self.make_dirs(paths)
13
56
  paths.each do |path|
14
57
  Dir.mkdir(path, 0777) unless File.exists?(path)
@@ -16,7 +59,6 @@ module EbookGenerator
16
59
  end
17
60
 
18
61
  def self.generate_container(path)
19
-
20
62
  file = File.new(path + "/container.xml", "wb")
21
63
  xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
22
64
  xm.instruct!
@@ -27,15 +69,12 @@ module EbookGenerator
27
69
  }
28
70
 
29
71
  file.close
30
-
31
72
  end
32
73
 
33
74
  def self.generate_mimetype(path)
34
-
35
75
  File.open(path, "w+") do |f|
36
76
  f.write("application/epub+zip")
37
77
  end
38
-
39
78
  end
40
79
 
41
80
  def self.copy_style(path)
@@ -43,10 +82,13 @@ module EbookGenerator
43
82
  #FileUtils.cp Rails.root.to_s + "/app/ebook/style.css", path
44
83
  end
45
84
 
46
- def self.generate_sections(path, attrs)
85
+ def self.convert_to_html(content)
47
86
  markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML, autolink: true, tables: true)
87
+ markdown.render(content)
88
+ end
48
89
 
49
- attrs.sections.each do |section|
90
+ def self.generate_sections(path, ebook_object)
91
+ ebook_object.sections.each do |section|
50
92
  file = File.new(path + "/Section#{section.position}.html", "wb")
51
93
 
52
94
  xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
@@ -54,45 +96,45 @@ module EbookGenerator
54
96
  xm.declare! :DOCTYPE, :html, :PUBLIC, "-//W3C//DTD XHTML 1.1//EN", "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"
55
97
  xm.html("xmlns" => "http://www.w3.org/1999/xhtml"){
56
98
  xm.head {
57
- xm.title { attrs.title }
58
- xm.meta("content" => attrs.title, "name" => "Title")
59
- xm.meta("content" => attrs.creator, "name" => "Author")
99
+ xm.title { ebook_object.title }
100
+ xm.meta("content" => ebook_object.title, "name" => "Title")
101
+ xm.meta("content" => ebook_object.creator, "name" => "Author")
60
102
  xm.link("href" => "../Styles/style.css", "rel" => "stylesheet", "type" => "text/css")
61
103
  }
62
- xm.body { |b| b << "<div id=\"#{section.title}\">" + markdown.render(section.content) + "</div>" }
104
+ xm.body { |b| b << "<div id=\"#{section.title}\">" + convert_to_html(section.content) + "</div>" }
63
105
  }
64
106
 
65
107
  file.close
66
108
  end
67
109
  end
68
110
 
69
- def self.generate_content_opf(path, attrs)
111
+ def self.generate_content_opf(path, ebook_object)
70
112
  file = File.new(path + "/content.opf", "wb")
71
113
 
72
114
  xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
73
115
  xm.instruct!
74
116
  xm.package("xmlns" => "http://www.idpf.org/2007/opf", "unique-identifier" => "BookId", "version" => "2.0") {
75
117
  xm.metadata("xmlns:dc" => "http://purl.org/dc/elements/1.1/", "xmlns:opf" => "http://www.idpf.org/2007/opf") {
76
- xm.tag!("dc:identifier", attrs.id, "id" => "BookId", "opf:scheme" => "UUID")
77
- xm.tag!("dc:title", attrs.title)
78
- xm.tag!("dc:creator", attrs.creator, "opf:role" => "aut")
79
- xm.tag!("dc:language", attrs.language)
80
- xm.tag!("dc:date", attrs.updated_at, "opf:event" => "modification")
81
- xm.tag!("dc:description", attrs.description)
82
- xm.tag!("dc:publisher", attrs.publisher)
83
- xm.tag!("dc:rights", attrs.rights)
84
- xm.tag!("dc:subject", attrs.subject)
85
- xm.tag!("dc:contributor", attrs.contributor, "opf:role" => "cov")
118
+ xm.tag!("dc:identifier", ebook_object.id, "id" => "BookId", "opf:scheme" => "UUID")
119
+ xm.tag!("dc:title", ebook_object.title)
120
+ xm.tag!("dc:creator", ebook_object.creator, "opf:role" => "aut")
121
+ xm.tag!("dc:language", ebook_object.language)
122
+ xm.tag!("dc:date", ebook_object.updated_at, "opf:event" => "modification")
123
+ xm.tag!("dc:description", ebook_object.description)
124
+ xm.tag!("dc:publisher", ebook_object.publisher)
125
+ xm.tag!("dc:rights", ebook_object.rights)
126
+ xm.tag!("dc:subject", ebook_object.subject)
127
+ xm.tag!("dc:contributor", ebook_object.contributor, "opf:role" => "cov")
86
128
  }
87
129
  xm.manifest {
88
130
  xm.item("href" => "toc.ncx", "id" => "ncx", "media-type" => "application/x-dtbncx+xml")
89
131
  xm.item("href" => "Styles/style.css", "media-type" => "text/css")
90
- attrs.sections.each do |section|
132
+ ebook_object.sections.each do |section|
91
133
  xm.item("href" => "Text/Section#{section.position}.html", "id" => "Section#{section.position}.html", "media-type" => "application/xhtml+xml")
92
134
  end
93
135
  }
94
136
  xm.spine("toc" => "ncx") {
95
- attrs.sections.each do |section|
137
+ ebook_object.sections.each do |section|
96
138
  xm.itemref("idref" => "Section#{section.position}.html")
97
139
  end
98
140
  }
@@ -100,11 +142,9 @@ module EbookGenerator
100
142
  }
101
143
 
102
144
  file.close
103
-
104
145
  end
105
146
 
106
- def self.generate_toc_ncx(path, attrs)
107
-
147
+ def self.generate_toc_ncx(path, ebook_object)
108
148
  file = File.new(path + "/toc.ncx", "wb")
109
149
 
110
150
  xm = Builder::XmlMarkup.new(:target => file, :indent => 2)
@@ -112,16 +152,16 @@ module EbookGenerator
112
152
  xm.declare! :DOCTYPE, :ncx, :PUBLIC, "-//NISO//DTD ncx 2005-1//EN", "http://www.daisy.org/z3986/2005/ncx-2005-1.dtd"
113
153
  xm.ncx("xmlns" => "http://www.daisy.org/z3986/2005/ncx/", "version" => "2005-1") {
114
154
  xm.head {
115
- xm.meta("content" => "urn:uuid:${attrs.id}", "name" => "dtb:uid")
155
+ xm.meta("content" => "urn:uuid:${ebook_object.id}", "name" => "dtb:uid")
116
156
  xm.meta("content" => "2", "name" => "dtb:depth")
117
157
  xm.meta("content" => "0", "name" => "dtb:totalPageCount")
118
158
  xm.meta("content" => "0", "name" => "dtb:maxPageNumber")
119
159
  }
120
160
  xm.docTitle {
121
- xm.text(attrs.title)
161
+ xm.text(ebook_object.title)
122
162
  }
123
163
  xm.navMap {
124
- attrs.sections.each do |section|
164
+ ebook_object.sections.each do |section|
125
165
  xm.navPoint("id" => "navPoint-#{section.position}", "playOrder" => "#{section.position}") {
126
166
  xm.navLabel {
127
167
  xm.text(section.title)
@@ -133,7 +173,6 @@ module EbookGenerator
133
173
  }
134
174
 
135
175
  file.close
136
-
137
176
  end
138
177
 
139
178
  def self.change_perms(files)
@@ -146,44 +185,8 @@ module EbookGenerator
146
185
  FileUtils.remove_dir(directory, true)
147
186
  end
148
187
 
149
- def self.generate_ebook(ebook_id)
150
-
151
- # Set the root path of the ebook
152
- path = Rails.root.to_s + "/tmp/#{ebook_id}"
153
-
154
- # Make all required dirs
155
- dirs = [path, path + "/META-INF", path + "/OEBPS", path + "/OEBPS/Text", path + "/OEBPS/Styles"]
156
- make_dirs(dirs)
157
-
158
- # Create container.xml
159
- generate_container(path + "/META-INF")
160
-
161
- # Create mimetype
162
- generate_mimetype(path + "/mimetype")
163
-
164
- # Move default stylesheet into styles folder
165
- copy_style(path + "/OEBPS/Styles")
166
-
167
- # loop through each section loading the reference header and saving as it's own section
168
- attrs = Ebook.find(ebook_id)
169
- generate_sections(path + "/OEBPS/Text", attrs)
170
-
171
- # generate toc based on the number of sections generated
172
- generate_content_opf(path + "/OEBPS", attrs)
173
- generate_toc_ncx(path + "/OEBPS", attrs)
174
-
175
- # change permissions for files that need to be executable
176
- files = [path + "/OEBPS/toc.ncx", path + "/OEBPS/content.opf", path + "/mimetype"]
177
- change_perms(files)
178
-
179
- # zip all files
180
- zipfile_name = Rails.root.to_s + "/tmp/" + attrs.slug + ".epub"
181
- zf = ZipFileProcessor.new(path, zipfile_name)
182
- zf.write
183
-
184
- # Clean up the tmp dir
185
- remove_tmp_dir(path + "/")
186
-
188
+ def self.generate_mobi(path, slug)
189
+ `#{Rails.root.to_s}/bin/kindlegen #{path} -o #{slug}.mobi`
190
+ "#{Rails.root.to_s}/tmp/#{slug}.mobi"
187
191
  end
188
-
189
192
  end
@@ -1,3 +1,3 @@
1
1
  module EbookGenerator
2
- VERSION = "0.0.5"
2
+ VERSION = "1.0.2"
3
3
  end
@@ -0,0 +1,16 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+ require 'rails/generators/active_record'
4
+
5
+ class EbookGeneratorGenerator < ActiveRecord::Generators::Base
6
+ argument :name, type: :string, default: 'random_name'
7
+
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_model_files
11
+ template "ebook.rb", "app/models/ebook.rb"
12
+ template "section.rb", "app/models/section.rb"
13
+ migration_template "create_ebooks.rb", "db/migrate/create_ebooks.rb"
14
+ migration_template "create_sections.rb", "db/migrate/create_sections.rb"
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ class CreateEbooks < ActiveRecord::Migration
2
+ def change
3
+ create_table :ebooks, force: true do |t|
4
+ t.string :title, null: false
5
+ t.string :creator
6
+ t.string :language, limit: 2, null: false
7
+ t.string :contributor
8
+ t.text :description
9
+ t.string :publisher
10
+ t.text :rights
11
+ t.string :subject
12
+ t.string :slug, null: false
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :ebooks, :slug, unique: true
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ class CreateSections < ActiveRecord::Migration
2
+ def change
3
+ create_table :sections, force: true do |t|
4
+ t.belongs_to :ebook
5
+ t.string :title, null: false
6
+ t.text :content, null: false
7
+ t.integer :position, null: false
8
+ t.timestamps
9
+ end
10
+
11
+ add_index :sections, ["position"], name: "index_sections_on_position", using: :btree
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class Ebook < ActiveRecord::Base
2
+ extend FriendlyId
3
+ has_many :sections, ->{ order(:position) }
4
+
5
+ friendly_id :title, use: :slugged
6
+
7
+ validates :title, presence: true
8
+ end
@@ -0,0 +1,6 @@
1
+ class Section < ActiveRecord::Base
2
+ belongs_to :ebook
3
+
4
+ validates :title, :presence => true
5
+ validates :content, :presence => true
6
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :ebook_generator do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.