mirror of https://github.com/rails/rails
Merge pull request #45539 from geongeorge/feature/updates-guide-generation-epub
Feature/updates guide generation - EPUB
This commit is contained in:
commit
914ac17156
2
Gemfile
2
Gemfile
|
@ -51,8 +51,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)
|
||||
|
@ -614,6 +609,7 @@ DEPENDENCIES
|
|||
rubocop-packaging
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubyzip (~> 2.0)
|
||||
sdoc (>= 2.4.0)
|
||||
selenium-webdriver (>= 4.0.0)
|
||||
sequel
|
||||
|
@ -637,4 +633,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"
|
||||
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
|
||||
unless /convert/.match?(`convert`)
|
||||
abort "Please install ImageMagick"
|
||||
end
|
||||
ENV["KINDLE"] = "1"
|
||||
|
||||
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
|
||||
|
|
|
@ -9,3 +9,8 @@ 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 @@ RailsGuides::Generator.new(
|
|||
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
|
||||
|
|
|
@ -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
|
|
@ -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 "action_view"
|
|||
|
||||
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 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
@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 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
view: view,
|
||||
layout: layout,
|
||||
edge: @edge,
|
||||
version: @version
|
||||
version: @version,
|
||||
epub: @epub
|
||||
).render(body)
|
||||
|
||||
warn_about_broken_links(result)
|
||||
|
|
|
@ -28,6 +28,15 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
@index_counter = Hash.new(0)
|
||||
@raw_header = ""
|
||||
@node_ids = {}
|
||||
@epub = epub
|
||||
end
|
||||
|
||||
def render(body)
|
||||
|
@ -59,7 +61,8 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
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 @@ module RailsGuides
|
|||
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
|
||||
|
||||
|
|
|
@ -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 @@ Rouge::Lexers::Shell::BUILTINS << "|bin/rails|brew|bundle|gem|git|node|rails|rak
|
|||
|
||||
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">
|
||||
|
|
@ -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>
|
|
@ -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