Replaces mobi guide with epub for docs because of discontinued support
Removes kindlerb logic Adds template for epub generator Renames the kindle dir to /epub Adds epub module to generator and replaces kindle Fixes mimetype Creates basic epub book Deletes old kindle module Adds zip package Updates rubyzip gem name Removes now unused gepub gem Adds the required container file for epubs Fixes media type Adds new epub generation logic Removes all buttons from output html Refactors and generates valid epub files Removes frontmatter logic used for kindlegen Filters out epub files in zip Updates link to kindle doc on sidebar Fixes rubocop issues Adds deprecation warning for the old kindle task Refactors and cleans up epub module Cleans up epub code Cleans up private internal method code style Removes unnecessary imagemagick check
This commit is contained in:
parent
31b1403919
commit
56e96d9344
2
Gemfile
2
Gemfile
@ -54,8 +54,8 @@ group :doc do
|
||||
gem "sdoc", ">= 2.4.0"
|
||||
gem "redcarpet", "~> 3.2.3", platforms: :ruby
|
||||
gem "w3c_validators", "~> 1.3.6"
|
||||
gem "kindlerb", "~> 1.2.0"
|
||||
gem "rouge"
|
||||
gem "rubyzip", "~> 2.0"
|
||||
end
|
||||
|
||||
# Active Support
|
||||
|
10
Gemfile.lock
10
Gemfile.lock
@ -181,7 +181,7 @@ GEM
|
||||
crass (1.0.6)
|
||||
cssbundling-rails (1.1.0)
|
||||
railties (>= 6.0.0)
|
||||
curses (1.4.3)
|
||||
curses (1.4.4)
|
||||
daemons (1.4.1)
|
||||
dalli (3.2.0)
|
||||
dante (0.2.0)
|
||||
@ -303,9 +303,6 @@ GEM
|
||||
railties (>= 6.0.0)
|
||||
json (2.6.1)
|
||||
jwt (2.3.0)
|
||||
kindlerb (1.2.0)
|
||||
mustache
|
||||
nokogiri
|
||||
libxml-ruby (3.2.1)
|
||||
listen (3.7.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
@ -336,7 +333,6 @@ GEM
|
||||
msgpack (1.4.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.1.1)
|
||||
mustache (1.1.1)
|
||||
mustermann (1.1.1)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
mysql2 (0.5.4)
|
||||
@ -583,7 +579,6 @@ DEPENDENCIES
|
||||
importmap-rails
|
||||
jsbundling-rails
|
||||
json (>= 2.0.0)
|
||||
kindlerb (~> 1.2.0)
|
||||
libxml-ruby
|
||||
listen (~> 3.3)
|
||||
minitest (>= 5.15.0)
|
||||
@ -615,6 +610,7 @@ DEPENDENCIES
|
||||
rubocop-packaging
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubyzip (~> 2.0)
|
||||
sdoc (>= 2.4.0)
|
||||
selenium-webdriver (>= 4.0.0)
|
||||
sequel
|
||||
@ -638,4 +634,4 @@ DEPENDENCIES
|
||||
websocket-client-simple!
|
||||
|
||||
BUNDLED WITH
|
||||
2.3.14
|
||||
2.3.17
|
||||
|
@ -10,16 +10,16 @@ namespace :guides do
|
||||
ruby "-Eutf-8:utf-8", "rails_guides.rb"
|
||||
end
|
||||
|
||||
desc "Generate .mobi file. The kindlegen executable must be in your PATH. You can get it for free from http://www.amazon.com/gp/feature.html?docId=1000765211"
|
||||
desc "Generate .mobi file"
|
||||
task :kindle do
|
||||
require "kindlerb"
|
||||
unless Kindlerb.kindlegen_available?
|
||||
abort "Please run `setupkindlerb` to install kindlegen"
|
||||
end
|
||||
unless /convert/.match?(`convert`)
|
||||
abort "Please install ImageMagick"
|
||||
end
|
||||
ENV["KINDLE"] = "1"
|
||||
require "active_support/deprecation"
|
||||
ActiveSupport::Deprecation.warn("The guides:generate:kindle rake task is deprecated and will be removed in 7.2. Run rake guides:generate:epub instead")
|
||||
Rake::Task["guides:generate:epub"].invoke
|
||||
end
|
||||
|
||||
desc "Generate .epub file"
|
||||
task :epub do
|
||||
ENV["EPUB"] = "1"
|
||||
Rake::Task["guides:generate:html"].invoke
|
||||
end
|
||||
end
|
||||
@ -68,7 +68,7 @@ Some arguments may be passed via environment variables:
|
||||
Examples:
|
||||
$ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0
|
||||
$ rake guides:generate ONLY=migrations
|
||||
$ rake guides:generate:kindle
|
||||
$ rake guides:generate:epub
|
||||
$ rake guides:generate GUIDES_LANGUAGE=es
|
||||
HELP
|
||||
end
|
||||
|
@ -8,4 +8,9 @@ p, H1, H2, H3, H4, H5, H6, H7, H8, table { margin-top: 1em;}
|
||||
}
|
||||
#toc .document {
|
||||
text-indent: 2em;
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
}
|
@ -24,7 +24,7 @@
|
||||
version: version,
|
||||
all: env_flag["ALL"],
|
||||
only: env_value["ONLY"],
|
||||
kindle: env_flag["KINDLE"],
|
||||
epub: env_flag["EPUB"],
|
||||
language: env_value["GUIDES_LANGUAGE"],
|
||||
direction: env_value["DIRECTION"]
|
||||
).generate
|
||||
|
94
guides/rails_guides/epub.rb
Normal file
94
guides/rails_guides/epub.rb
Normal file
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "nokogiri"
|
||||
require "fileutils"
|
||||
require "yaml"
|
||||
require "date"
|
||||
|
||||
require "rails_guides/epub_packer"
|
||||
|
||||
module Epub # :nodoc:
|
||||
extend self
|
||||
|
||||
def generate(output_dir, epub_outfile)
|
||||
fix_file_names(output_dir)
|
||||
generate_meta_files(output_dir)
|
||||
generate_epub(output_dir, epub_outfile)
|
||||
end
|
||||
|
||||
private
|
||||
def open_toc_doc(toc)
|
||||
Nokogiri::XML(toc).xpath("//ncx:content", "ncx" => "http://www.daisy.org/z3986/2005/ncx/")
|
||||
end
|
||||
|
||||
def generate_meta_files(output_dir)
|
||||
output_dir = File.absolute_path(File.join(output_dir, ".."))
|
||||
Dir.chdir output_dir do
|
||||
puts "=> Using output dir: #{output_dir}"
|
||||
puts "=> Generating meta files"
|
||||
FileUtils.mkdir_p("META-INF")
|
||||
File.write("META-INF/container.xml", <<~CONTENT)
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<container xmlns="urn:oasis:names:tc:opendocument:xmlns:container" version="1.0">
|
||||
<rootfiles>
|
||||
<rootfile full-path="OEBPS/rails_guides.opf" media-type="application/oebps-package+xml"/>
|
||||
</rootfiles>
|
||||
</container>
|
||||
CONTENT
|
||||
end
|
||||
end
|
||||
|
||||
def generate_epub(output_dir, epub_outfile)
|
||||
output_dir = File.absolute_path(File.join(output_dir, ".."))
|
||||
Dir.chdir output_dir do
|
||||
puts "=> Generating EPUB"
|
||||
EpubPacker.pack("./", epub_outfile)
|
||||
puts "=> Done Generating EPUB"
|
||||
end
|
||||
end
|
||||
|
||||
def is_name_invalid(name)
|
||||
(/[0-9]/ === name.chars.first)
|
||||
end
|
||||
|
||||
def fix_file_names(output_dir)
|
||||
book_dir = File.absolute_path(output_dir)
|
||||
Dir.chdir book_dir do
|
||||
puts "=> Using book dir: #{book_dir}"
|
||||
puts "=> Fixing filenames in Table of Contents"
|
||||
# opf file: item->id and itemref->idref attributes does not support values starting with a number
|
||||
toc = File.read("toc.ncx")
|
||||
toc_html = File.read("toc.html")
|
||||
opf = File.read("rails_guides.opf")
|
||||
|
||||
doc = open_toc_doc(toc)
|
||||
doc.each do |c|
|
||||
name = c[:src]
|
||||
|
||||
if is_name_invalid(name)
|
||||
FileUtils.mv(name, "rails_#{name}")
|
||||
toc.gsub!(name, "rails_#{name}")
|
||||
toc_html.gsub!(name, "rails_#{name}")
|
||||
opf.gsub!(name, "rails_#{name}")
|
||||
end
|
||||
end
|
||||
File.write("toc.ncx", toc)
|
||||
File.write("toc.html", toc_html)
|
||||
File.write("rails_guides.opf", opf)
|
||||
end
|
||||
end
|
||||
|
||||
def add_head_section(doc, title)
|
||||
head = Nokogiri::XML::Node.new "head", doc
|
||||
title_node = Nokogiri::XML::Node.new "title", doc
|
||||
title_node.content = title
|
||||
title_node.parent = head
|
||||
css = Nokogiri::XML::Node.new "link", doc
|
||||
css["rel"] = "stylesheet"
|
||||
css["type"] = "text/css"
|
||||
css["href"] = "#{Dir.pwd}/stylesheets/epub.css"
|
||||
css.parent = head
|
||||
doc.at("body").before head
|
||||
end
|
||||
end
|
59
guides/rails_guides/epub_packer.rb
Normal file
59
guides/rails_guides/epub_packer.rb
Normal file
@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "nokogiri"
|
||||
require "fileutils"
|
||||
require "yaml"
|
||||
require "date"
|
||||
require "zip"
|
||||
|
||||
module EpubPacker # :nodoc:
|
||||
extend self
|
||||
|
||||
def pack(output_dir, epub_file_name)
|
||||
@output_dir = output_dir
|
||||
|
||||
FileUtils.rm_f(epub_file_name)
|
||||
|
||||
Zip::OutputStream.open(epub_file_name) {
|
||||
|epub|
|
||||
create_epub(epub, epub_file_name)
|
||||
}
|
||||
|
||||
entries = Dir.entries(output_dir) - %w[. ..]
|
||||
|
||||
entries.reject! { |item| File.extname(item) == ".epub" }
|
||||
|
||||
Zip::File.open(epub_file_name, create: true) do |epub|
|
||||
write_entries(entries, "", epub)
|
||||
end
|
||||
end
|
||||
|
||||
def create_epub(epub, epub_file_name)
|
||||
epub.put_next_entry("mimetype", nil, nil, Zip::Entry::STORED, Zlib::NO_COMPRESSION)
|
||||
epub.write "application/epub+zip"
|
||||
end
|
||||
|
||||
def write_entries(entries, path, zipfile)
|
||||
entries.each do |e|
|
||||
zipfile_path = path == "" ? e : File.join(path, e)
|
||||
disk_file_path = File.join(@output_dir, zipfile_path)
|
||||
|
||||
if File.directory? disk_file_path
|
||||
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
||||
else
|
||||
put_into_archive(disk_file_path, zipfile, zipfile_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
||||
zipfile.mkdir zipfile_path
|
||||
subdir = Dir.entries(disk_file_path) - %w[. ..]
|
||||
write_entries subdir, zipfile_path, zipfile
|
||||
end
|
||||
|
||||
def put_into_archive(disk_file_path, zipfile, zipfile_path)
|
||||
zipfile.add(zipfile_path, disk_file_path)
|
||||
end
|
||||
end
|
@ -2,6 +2,8 @@
|
||||
|
||||
require "set"
|
||||
require "fileutils"
|
||||
require "nokogiri"
|
||||
require "securerandom"
|
||||
|
||||
require "active_support/core_ext/string/output_safety"
|
||||
require "active_support/core_ext/object/blank"
|
||||
@ -10,23 +12,23 @@
|
||||
|
||||
require "rails_guides/markdown"
|
||||
require "rails_guides/helpers"
|
||||
require "rails_guides/epub"
|
||||
|
||||
module RailsGuides
|
||||
class Generator
|
||||
GUIDES_RE = /\.(?:erb|md)\z/
|
||||
|
||||
def initialize(edge:, version:, all:, only:, kindle:, language:, direction: nil)
|
||||
def initialize(edge:, version:, all:, only:, epub:, language:, direction: nil)
|
||||
@edge = edge
|
||||
@version = version
|
||||
@all = all
|
||||
@only = only
|
||||
@kindle = kindle
|
||||
@epub = epub
|
||||
@language = language
|
||||
@direction = direction || "ltr"
|
||||
|
||||
if @kindle
|
||||
check_for_kindlegen
|
||||
register_kindle_mime_types
|
||||
if @epub
|
||||
register_special_mime_types
|
||||
end
|
||||
|
||||
initialize_dirs
|
||||
@ -37,32 +39,24 @@ def initialize(edge:, version:, all:, only:, kindle:, language:, direction: nil)
|
||||
def generate
|
||||
generate_guides
|
||||
copy_assets
|
||||
generate_mobi if @kindle
|
||||
generate_epub if @epub
|
||||
end
|
||||
|
||||
private
|
||||
def register_kindle_mime_types
|
||||
def register_special_mime_types
|
||||
Mime::Type.register_alias("application/xml", :opf, %w(opf))
|
||||
Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
|
||||
end
|
||||
|
||||
def check_for_kindlegen
|
||||
if `which kindlegen`.blank?
|
||||
raise "Can't create a kindle version without `kindlegen`."
|
||||
end
|
||||
def generate_epub
|
||||
Epub.generate(@output_dir, epub_filename)
|
||||
puts "Epub generated at: output/epub/#{epub_filename}"
|
||||
end
|
||||
|
||||
def generate_mobi
|
||||
require "rails_guides/kindle"
|
||||
out = "#{@output_dir}/kindlegen.out"
|
||||
Kindle.generate(@output_dir, mobi, out)
|
||||
puts "(kindlegen log at #{out})."
|
||||
end
|
||||
|
||||
def mobi
|
||||
mobi = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
|
||||
mobi << ".#{@language}" if @language
|
||||
mobi << ".mobi"
|
||||
def epub_filename
|
||||
epub_filename = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
|
||||
epub_filename << ".#{@language}" if @language
|
||||
epub_filename << ".epub"
|
||||
end
|
||||
|
||||
def initialize_dirs
|
||||
@ -72,7 +66,7 @@ def initialize_dirs
|
||||
@source_dir += "/#{@language}" if @language
|
||||
|
||||
@output_dir = "#{@guides_dir}/output"
|
||||
@output_dir += "/kindle" if @kindle
|
||||
@output_dir += "/epub/OEBPS" if @epub
|
||||
@output_dir += "/#{@language}" if @language
|
||||
end
|
||||
|
||||
@ -95,10 +89,9 @@ def generate_guides
|
||||
def guides_to_generate
|
||||
guides = Dir.entries(@source_dir).grep(GUIDES_RE)
|
||||
|
||||
if @kindle
|
||||
Dir.entries("#{@source_dir}/kindle").grep(GUIDES_RE).map do |entry|
|
||||
next if entry == "KINDLE.md"
|
||||
guides << "kindle/#{entry}"
|
||||
if @epub
|
||||
Dir.entries("#{@source_dir}/epub").grep(GUIDES_RE).map do |entry|
|
||||
guides << "epub/#{entry}"
|
||||
end
|
||||
end
|
||||
|
||||
@ -108,7 +101,7 @@ def guides_to_generate
|
||||
def select_only(guides)
|
||||
prefixes = @only.split(",").map(&:strip)
|
||||
guides.select do |guide|
|
||||
guide.start_with?("kindle", *prefixes)
|
||||
guide.start_with?("epub", *prefixes)
|
||||
end
|
||||
end
|
||||
|
||||
@ -137,15 +130,16 @@ def generate?(source_file, output_file)
|
||||
def generate_guide(guide, output_file)
|
||||
output_path = output_path_for(output_file)
|
||||
puts "Generating #{guide} as #{output_file}"
|
||||
layout = @kindle ? "kindle/layout" : "layout"
|
||||
layout = @epub ? "epub/layout" : "layout"
|
||||
|
||||
view = ActionView::Base.with_empty_template_cache.with_view_paths(
|
||||
[@source_dir],
|
||||
edge: @edge,
|
||||
version: @version,
|
||||
mobi: "kindle/#{mobi}",
|
||||
epub: "epub/#{epub_filename}",
|
||||
language: @language,
|
||||
direction: @direction,
|
||||
uuid: SecureRandom.uuid
|
||||
)
|
||||
view.extend(Helpers)
|
||||
|
||||
@ -161,7 +155,8 @@ def generate_guide(guide, output_file)
|
||||
view: view,
|
||||
layout: layout,
|
||||
edge: @edge,
|
||||
version: @version
|
||||
version: @version,
|
||||
epub: @epub
|
||||
).render(body)
|
||||
|
||||
warn_about_broken_links(result)
|
||||
|
@ -28,6 +28,15 @@ def finished_documents(documents)
|
||||
documents.reject { |document| document["work_in_progress"] }
|
||||
end
|
||||
|
||||
def all_images
|
||||
base_path = File.expand_path("../assets", __dir__)
|
||||
images_path = File.join(base_path, "images/**/*")
|
||||
@all_images = Dir.glob(images_path).reject { |f| File.directory?(f) }.map { |item|
|
||||
item.delete_prefix "#{base_path}/"
|
||||
}
|
||||
@all_images
|
||||
end
|
||||
|
||||
def docs_for_menu(position = nil)
|
||||
if position.nil?
|
||||
documents_by_section
|
||||
|
@ -1,116 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "kindlerb"
|
||||
require "nokogiri"
|
||||
require "fileutils"
|
||||
require "yaml"
|
||||
require "date"
|
||||
|
||||
module Kindle
|
||||
extend self
|
||||
|
||||
def generate(output_dir, mobi_outfile, logfile)
|
||||
output_dir = File.absolute_path(output_dir)
|
||||
Dir.chdir output_dir do
|
||||
puts "=> Using output dir: #{output_dir}"
|
||||
puts "=> Arranging html pages in document order"
|
||||
toc = File.read("toc.ncx")
|
||||
doc = Nokogiri::XML(toc).xpath("//ncx:content", "ncx" => "http://www.daisy.org/z3986/2005/ncx/")
|
||||
html_pages = doc.filter_map { |c| c[:src] }.uniq
|
||||
|
||||
generate_front_matter(html_pages)
|
||||
|
||||
generate_sections(html_pages)
|
||||
|
||||
generate_document_metadata(mobi_outfile)
|
||||
|
||||
puts "Creating MOBI document with kindlegen. This may take a while."
|
||||
if Kindlerb.run(output_dir)
|
||||
puts "MOBI document generated at #{File.expand_path(mobi_outfile, output_dir)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_front_matter(html_pages)
|
||||
frontmatter = []
|
||||
html_pages.delete_if { |x|
|
||||
if /(toc|welcome|copyright).html/.match?(x)
|
||||
frontmatter << x unless /toc/.match?(x)
|
||||
true
|
||||
end
|
||||
}
|
||||
html = frontmatter.map { |x|
|
||||
Nokogiri::HTML(File.open(x)).at("body").inner_html
|
||||
}.join("\n")
|
||||
|
||||
fdoc = Nokogiri::HTML(html)
|
||||
fdoc.search("h3").each do |h3|
|
||||
h3.name = "h4"
|
||||
end
|
||||
fdoc.search("h2").each do |h2|
|
||||
h2.name = "h3"
|
||||
h2["id"] = h2.inner_text.gsub(/\s/, "-")
|
||||
end
|
||||
add_head_section fdoc, "Front Matter"
|
||||
File.open("frontmatter.html", "w") { |f| f.puts fdoc.to_html }
|
||||
html_pages.unshift "frontmatter.html"
|
||||
end
|
||||
|
||||
def generate_sections(html_pages)
|
||||
FileUtils.rm_rf("sections/")
|
||||
html_pages.each_with_index do |page, section_idx|
|
||||
FileUtils.mkdir_p("sections/%03d" % section_idx)
|
||||
doc = Nokogiri::HTML(File.open(page))
|
||||
title = doc.at("title").inner_text.gsub("Ruby on Rails Guides: ", "")
|
||||
title = page.capitalize.gsub(".html", "") if title.strip == ""
|
||||
File.open("sections/%03d/_section.txt" % section_idx, "w") { |f| f.puts title }
|
||||
doc.xpath("//h3[@id]").each_with_index do |h3, item_idx|
|
||||
subsection = h3.inner_text
|
||||
content = h3.xpath("./following-sibling::*").take_while { |x| x.name != "h3" }.map(&:to_html)
|
||||
item = Nokogiri::HTML(h3.to_html + content.join("\n"))
|
||||
item_path = "sections/%03d/%03d.html" % [section_idx, item_idx]
|
||||
add_head_section(item, subsection)
|
||||
item.search("img").each do |img|
|
||||
img["src"] = "#{Dir.pwd}/#{img['src']}"
|
||||
end
|
||||
item.xpath("//li/p").each { |p| p.swap(p.children); p.remove }
|
||||
File.open(item_path, "w") { |f| f.puts item.to_html }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_document_metadata(mobi_outfile)
|
||||
puts "=> Generating _document.yml"
|
||||
x = Nokogiri::XML(File.open("rails_guides.opf")).remove_namespaces!
|
||||
cover_jpg = "#{Dir.pwd}/images/rails_guides_kindle_cover.jpg"
|
||||
cover_gif = cover_jpg.sub(/jpg$/, "gif")
|
||||
puts `convert #{cover_jpg} #{cover_gif}`
|
||||
document = {
|
||||
"doc_uuid" => x.at("package")["unique-identifier"],
|
||||
"title" => x.at("title").inner_text.gsub(/\(.*$/, " v2"),
|
||||
"publisher" => x.at("publisher").inner_text,
|
||||
"author" => x.at("creator").inner_text,
|
||||
"subject" => x.at("subject").inner_text,
|
||||
"date" => x.at("date").inner_text,
|
||||
"cover" => cover_gif,
|
||||
"masthead" => nil,
|
||||
"mobi_outfile" => mobi_outfile
|
||||
}
|
||||
puts document.to_yaml
|
||||
File.open("_document.yml", "w") { |f| f.puts document.to_yaml }
|
||||
end
|
||||
|
||||
def add_head_section(doc, title)
|
||||
head = Nokogiri::XML::Node.new "head", doc
|
||||
title_node = Nokogiri::XML::Node.new "title", doc
|
||||
title_node.content = title
|
||||
title_node.parent = head
|
||||
css = Nokogiri::XML::Node.new "link", doc
|
||||
css["rel"] = "stylesheet"
|
||||
css["type"] = "text/css"
|
||||
css["href"] = "#{Dir.pwd}/stylesheets/kindle.css"
|
||||
css.parent = head
|
||||
doc.at("body").before head
|
||||
end
|
||||
end
|
@ -3,11 +3,12 @@
|
||||
require "redcarpet"
|
||||
require "nokogiri"
|
||||
require "rails_guides/markdown/renderer"
|
||||
require "rails_guides/markdown/epub_renderer"
|
||||
require "rails-html-sanitizer"
|
||||
|
||||
module RailsGuides
|
||||
class Markdown
|
||||
def initialize(view:, layout:, edge:, version:)
|
||||
def initialize(view:, layout:, edge:, version:, epub:)
|
||||
@view = view
|
||||
@layout = layout
|
||||
@edge = edge
|
||||
@ -15,6 +16,7 @@ def initialize(view:, layout:, edge:, version:)
|
||||
@index_counter = Hash.new(0)
|
||||
@raw_header = ""
|
||||
@node_ids = {}
|
||||
@epub = epub
|
||||
end
|
||||
|
||||
def render(body)
|
||||
@ -59,7 +61,8 @@ def dom_id_text(text)
|
||||
end
|
||||
|
||||
def engine
|
||||
@engine ||= Redcarpet::Markdown.new(Renderer,
|
||||
renderer = @epub ? EpubRenderer : Renderer
|
||||
@engine ||= Redcarpet::Markdown.new(renderer,
|
||||
no_intra_emphasis: true,
|
||||
fenced_code_blocks: true,
|
||||
autolink: true,
|
||||
@ -91,7 +94,7 @@ def generate_description
|
||||
def generate_structure
|
||||
@headings_for_index = []
|
||||
if @body.present?
|
||||
@body = Nokogiri::HTML.fragment(@body).tap do |doc|
|
||||
document = Nokogiri::HTML.fragment(@body).tap do |doc|
|
||||
hierarchy = []
|
||||
|
||||
doc.children.each do |node|
|
||||
@ -117,7 +120,8 @@ def generate_structure
|
||||
doc.css("h3, h4, h5, h6").each do |node|
|
||||
node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>"
|
||||
end
|
||||
end.to_html
|
||||
end
|
||||
@body = @epub ? document.to_xhtml : document.to_html
|
||||
end
|
||||
end
|
||||
|
||||
|
113
guides/rails_guides/markdown/epub_renderer.rb
Normal file
113
guides/rails_guides/markdown/epub_renderer.rb
Normal file
@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require "rouge"
|
||||
|
||||
# Add more common shell commands
|
||||
Rouge::Lexers::Shell::BUILTINS << "|bin/rails|brew|bundle|gem|git|node|rails|rake|ruby|sqlite3|yarn"
|
||||
|
||||
module RailsGuides
|
||||
class Markdown
|
||||
class EpubRenderer < Redcarpet::Render::XHTML # :nodoc:
|
||||
cattr_accessor :edge, :version
|
||||
|
||||
def linebreak
|
||||
"<br/>"
|
||||
end
|
||||
|
||||
def link(url, title, content)
|
||||
if %r{https?://api\.rubyonrails\.org}.match?(url)
|
||||
%(<a href="#{api_link(url)}">#{content}</a>)
|
||||
elsif title
|
||||
%(<a href="#{url}" title="#{title}">#{content}</a>)
|
||||
else
|
||||
%(<a href="#{url}">#{content}</a>)
|
||||
end
|
||||
end
|
||||
|
||||
def header(text, header_level)
|
||||
# Always increase the heading level by 1, so we can use h1, h2 heading in the document
|
||||
header_level += 1
|
||||
|
||||
header_with_id = text.scan(/(.*){#(.*)}/)
|
||||
unless header_with_id.empty?
|
||||
%(<h#{header_level} id="#{header_with_id[0][1].strip}">#{header_with_id[0][0].strip}</h#{header_level}>)
|
||||
else
|
||||
%(<h#{header_level}>#{text}</h#{header_level}>)
|
||||
end
|
||||
end
|
||||
|
||||
def paragraph(text)
|
||||
if text =~ %r{^NOTE:\s+Defined\s+in\s+<code>(.*?)</code>\.?$}
|
||||
%(<div class="note"><p>Defined in <code><a href="#{github_file_url($1)}">#{$1}</a></code>.</p></div>)
|
||||
elsif /^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:]/.match?(text)
|
||||
convert_notes(text)
|
||||
elsif text.include?("DO NOT READ THIS FILE ON GITHUB")
|
||||
elsif text =~ /^\[<sup>(\d+)\]:<\/sup> (.+)$/
|
||||
linkback = %(<a href="#footnote-#{$1}-ref"><sup>#{$1}</sup></a>)
|
||||
%(<p class="footnote" id="footnote-#{$1}">#{linkback} #{$2}</p>)
|
||||
else
|
||||
text = convert_footnotes(text)
|
||||
"<p>#{text}</p>"
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def convert_footnotes(text)
|
||||
text.gsub(/\[<sup>(\d+)\]<\/sup>/i) do
|
||||
%(<sup class="footnote" id="footnote-#{$1}-ref">) +
|
||||
%(<a href="#footnote-#{$1}">#{$1}</a></sup>)
|
||||
end
|
||||
end
|
||||
|
||||
def convert_notes(body)
|
||||
# The following regexp detects special labels followed by a
|
||||
# paragraph, perhaps at the end of the document.
|
||||
#
|
||||
# It is important that we do not eat more than one newline
|
||||
# because formatting may be wrong otherwise. For example,
|
||||
# if a bulleted list follows, the first item is not rendered
|
||||
# as a list item, but as a paragraph starting with a plain
|
||||
# asterisk.
|
||||
body.gsub(/^(TIP|IMPORTANT|CAUTION|WARNING|NOTE|INFO|TODO)[.:](.*?)(\n(?=\n)|\Z)/m) do
|
||||
css_class = \
|
||||
case $1
|
||||
when "CAUTION", "IMPORTANT"
|
||||
"warning"
|
||||
when "TIP"
|
||||
"info"
|
||||
else
|
||||
$1.downcase
|
||||
end
|
||||
%(<div class="#{css_class}"><p>#{$2.strip}</p></div>)
|
||||
end
|
||||
end
|
||||
|
||||
def github_file_url(file_path)
|
||||
tree = version || edge
|
||||
|
||||
root = file_path[%r{(\w+)/}, 1]
|
||||
path = \
|
||||
case root
|
||||
when "abstract_controller", "action_controller", "action_dispatch"
|
||||
"actionpack/lib/#{file_path}"
|
||||
when /\A(action|active)_/
|
||||
"#{root.sub("_", "")}/lib/#{file_path}"
|
||||
else
|
||||
file_path
|
||||
end
|
||||
|
||||
"https://github.com/rails/rails/tree/#{tree}/#{path}"
|
||||
end
|
||||
|
||||
def api_link(url)
|
||||
if %r{https?://api\.rubyonrails\.org/v\d+\.}.match?(url)
|
||||
url
|
||||
elsif edge
|
||||
url.sub("api", "edgeapi")
|
||||
else
|
||||
url.sub(/(?<=\.org)/, "/#{version}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
@ -7,7 +7,7 @@
|
||||
|
||||
module RailsGuides
|
||||
class Markdown
|
||||
class Renderer < Redcarpet::Render::HTML
|
||||
class Renderer < Redcarpet::Render::HTML # :nodoc:
|
||||
cattr_accessor :edge, :version
|
||||
|
||||
def block_code(code, language)
|
||||
|
@ -1,14 +1,9 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
||||
<!DOCTYPE html>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/epub.css"></link>
|
||||
<title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/kindle.css" />
|
||||
|
||||
</head>
|
||||
<body class="guide">
|
||||
|
48
guides/source/epub/rails_guides.opf.erb
Normal file
48
guides/source/epub/rails_guides.opf.erb
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="RailsGuides">
|
||||
<metadata xmlns:opf="http://www.idpf.org/2007/opf" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<dc:identifier id="RailsGuides" opf:scheme="uuid"><%= @uuid %></dc:identifier>
|
||||
<dc:title>Ruby on Rails Guides (<%= @version || "main@#{@edge[0, 7]}" %>)</dc:title>
|
||||
<dc:language>en</dc:language>
|
||||
<dc:creator>Ruby on Rails</dc:creator>
|
||||
<dc:publisher>Ruby on Rails</dc:publisher>
|
||||
<dc:subject>Reference</dc:subject>
|
||||
<dc:date><%= Time.now.strftime('%Y-%m-%dT%H:%M:%SZ') %></dc:date>
|
||||
<dc:description>These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.</dc:description>
|
||||
<meta name="cover" content="cover"/>
|
||||
</metadata>
|
||||
|
||||
<manifest>
|
||||
<!-- HTML content files [mandatory] -->
|
||||
<% documents_flat.each do |document| %>
|
||||
<item id="<%= document['url'] %>" media-type="application/xhtml+xml" href="<%= document['url'] %>" />
|
||||
<% end %>
|
||||
|
||||
<% %w{toc.html welcome.html copyright.html}.each do |url| %>
|
||||
<item id="<%= url %>" media-type="application/xhtml+xml" href="<%= url %>" />
|
||||
<% end %>
|
||||
|
||||
<item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx"/>
|
||||
|
||||
<item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/>
|
||||
<item id="stylesheet" href="stylesheets/epub.css" media-type="text/css"/>
|
||||
|
||||
<!-- Images -->
|
||||
<% all_images.each do |image| %>
|
||||
<item id="<%= image %>" media-type="image/<%= image.split('.').last %>" href="<%= image %>" />
|
||||
<% end %>
|
||||
</manifest>
|
||||
|
||||
<spine toc="toc">
|
||||
<itemref idref="toc.html" />
|
||||
<itemref idref="welcome.html" />
|
||||
<itemref idref="copyright.html" />
|
||||
<% documents_flat.each do |document| %>
|
||||
<itemref idref="<%= document['url'] %>" />
|
||||
<% end %>
|
||||
</spine>
|
||||
|
||||
<guide>
|
||||
<reference type="toc" title="Table of Contents" href="toc.html"></reference>
|
||||
</guide>
|
||||
</package>
|
24
guides/source/epub/toc.html.erb
Normal file
24
guides/source/epub/toc.html.erb
Normal file
@ -0,0 +1,24 @@
|
||||
<% content_for :page_title do %>
|
||||
Ruby on Rails Guides
|
||||
<% end %>
|
||||
|
||||
<h1>Table of Contents</h1>
|
||||
<div id="toc">
|
||||
<ul><li><a href="welcome.html">Welcome</a></li></ul>
|
||||
|
||||
<% documents_by_section.each_with_index do |section, i| %>
|
||||
<h3><%= "#{i + 1}." %> <%= section['name'] %></h3>
|
||||
<ul>
|
||||
<% section['documents'].each do |document| %>
|
||||
<li>
|
||||
<a href="<%= document['url'] %>"><%= document['name'] %></a>
|
||||
<% if document['work_in_progress']%>(WIP)<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<hr />
|
||||
<ul>
|
||||
<li><a href="copyright.html">Copyright & License</a></li>
|
||||
</ul>
|
||||
</div>
|
@ -18,7 +18,7 @@
|
||||
</navLabel>
|
||||
<content src="toc.html"/>
|
||||
|
||||
<navPoint class="section" id="welcome" playOrder="1">
|
||||
<navPoint class="section" id="start" playOrder="1">
|
||||
<navLabel>
|
||||
<text>Introduction</text>
|
||||
</navLabel>
|
||||
@ -30,7 +30,7 @@
|
||||
</navLabel>
|
||||
<content src="welcome.html"/>
|
||||
</navPoint>
|
||||
<navPoint class="article" id="copyright" playOrder="4">
|
||||
<navPoint class="article" id="copyright" playOrder="3">
|
||||
<navLabel><text>Copyright & License</text></navLabel>
|
||||
<content src="copyright.html"/>
|
||||
</navPoint>
|
@ -10,7 +10,7 @@
|
||||
<dl>
|
||||
<dt></dt>
|
||||
<% unless @edge -%>
|
||||
<dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @mobi %>.</dd>
|
||||
<dd class="kindle">Rails Guides are also available for <%= link_to 'Kindle', @epub %>.</dd>
|
||||
<% end -%>
|
||||
<dd class="work-in-progress">Guides marked with this icon are currently being worked on and will not be available in the Guides Index menu. While still useful, they may contain incomplete information and even errors. You can help by reviewing them and posting your comments and corrections.</dd>
|
||||
</dl>
|
||||
|
@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="RailsGuides">
|
||||
<metadata>
|
||||
<meta name="cover" content="cover" />
|
||||
<dc-metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
|
||||
<dc:title>Ruby on Rails Guides (<%= @version || "main@#{@edge[0, 7]}" %>)</dc:title>
|
||||
|
||||
<dc:language>en-us</dc:language>
|
||||
<dc:creator>Ruby on Rails</dc:creator>
|
||||
<dc:publisher>Ruby on Rails</dc:publisher>
|
||||
<dc:subject>Reference</dc:subject>
|
||||
<dc:date><%= Time.now.strftime('%Y-%m-%d') %></dc:date>
|
||||
|
||||
<dc:description>These guides are designed to make you immediately productive with Rails, and to help you understand how all of the pieces fit together.</dc:description>
|
||||
</dc-metadata>
|
||||
<x-metadata>
|
||||
<output content-type="application/x-mobipocket-subscription-magazine" encoding="utf-8"/>
|
||||
</x-metadata>
|
||||
</metadata>
|
||||
|
||||
<manifest>
|
||||
<!-- HTML content files [mandatory] -->
|
||||
<% documents_flat.each do |document| %>
|
||||
<item id="<%= document['url'] %>" media-type="text/html" href="<%= document['url'] %>" />
|
||||
<% end %>
|
||||
|
||||
<% %w{toc.html welcome.html copyright.html}.each do |url| %>
|
||||
<item id="<%= url %>" media-type="text/html" href="<%= url %>" />
|
||||
<% end %>
|
||||
|
||||
<item id="toc" media-type="application/x-dtbncx+xml" href="toc.ncx" />
|
||||
|
||||
<item id="cover" media-type="image/jpeg" href="images/rails_guides_kindle_cover.jpg"/>
|
||||
</manifest>
|
||||
|
||||
<spine toc="toc">
|
||||
<itemref idref="toc.html" />
|
||||
<itemref idref="welcome.html" />
|
||||
<itemref idref="copyright.html" />
|
||||
<% documents_flat.each do |document| %>
|
||||
<itemref idref="<%= document['url'] %>" />
|
||||
<% end %>
|
||||
</spine>
|
||||
|
||||
<guide>
|
||||
<reference type="toc" title="Table of Contents" href="toc.html"></reference>
|
||||
</guide>
|
||||
|
||||
</package>
|
@ -1,23 +0,0 @@
|
||||
<% content_for :page_title do %>
|
||||
Ruby on Rails Guides
|
||||
<% end %>
|
||||
|
||||
<h1>Table of Contents</h1>
|
||||
<div id="toc">
|
||||
<ul><li><a href="welcome.html">Welcome</a></li></ul>
|
||||
<% documents_by_section.each_with_index do |section, i| %>
|
||||
<h3><%= "#{i + 1}." %> <%= section['name'] %></h3>
|
||||
<ul>
|
||||
<% section['documents'].each do |document| %>
|
||||
<li>
|
||||
<a href="<%= document['url'] %>"><%= document['name'] %></a>
|
||||
<% if document['work_in_progress']%>(WIP)<% end %>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
<% end %>
|
||||
<hr />
|
||||
<ul>
|
||||
<li><a href="copyright.html">Copyright & License</a></li>
|
||||
</ul>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user