Merge pull request #45539 from geongeorge/feature/updates-guide-generation-epub

Feature/updates guide generation - EPUB
This commit is contained in:
Eileen M. Uchitelle 2022-08-04 15:03:56 -04:00 committed by GitHub
commit 914ac17156
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 409 additions and 257 deletions

View File

@ -51,8 +51,8 @@ group :doc do
gem "sdoc", ">= 2.4.0" gem "sdoc", ">= 2.4.0"
gem "redcarpet", "~> 3.2.3", platforms: :ruby gem "redcarpet", "~> 3.2.3", platforms: :ruby
gem "w3c_validators", "~> 1.3.6" gem "w3c_validators", "~> 1.3.6"
gem "kindlerb", "~> 1.2.0"
gem "rouge" gem "rouge"
gem "rubyzip", "~> 2.0"
end end
# Active Support # Active Support

View File

@ -181,7 +181,7 @@ GEM
crass (1.0.6) crass (1.0.6)
cssbundling-rails (1.1.0) cssbundling-rails (1.1.0)
railties (>= 6.0.0) railties (>= 6.0.0)
curses (1.4.3) curses (1.4.4)
daemons (1.4.1) daemons (1.4.1)
dalli (3.2.0) dalli (3.2.0)
dante (0.2.0) dante (0.2.0)
@ -303,9 +303,6 @@ GEM
railties (>= 6.0.0) railties (>= 6.0.0)
json (2.6.1) json (2.6.1)
jwt (2.3.0) jwt (2.3.0)
kindlerb (1.2.0)
mustache
nokogiri
libxml-ruby (3.2.1) libxml-ruby (3.2.1)
listen (3.7.0) listen (3.7.0)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
@ -336,7 +333,6 @@ GEM
msgpack (1.4.2) msgpack (1.4.2)
multi_json (1.15.0) multi_json (1.15.0)
multipart-post (2.1.1) multipart-post (2.1.1)
mustache (1.1.1)
mustermann (1.1.1) mustermann (1.1.1)
ruby2_keywords (~> 0.0.1) ruby2_keywords (~> 0.0.1)
mysql2 (0.5.4) mysql2 (0.5.4)
@ -583,7 +579,6 @@ DEPENDENCIES
importmap-rails importmap-rails
jsbundling-rails jsbundling-rails
json (>= 2.0.0) json (>= 2.0.0)
kindlerb (~> 1.2.0)
libxml-ruby libxml-ruby
listen (~> 3.3) listen (~> 3.3)
minitest (>= 5.15.0) minitest (>= 5.15.0)
@ -614,6 +609,7 @@ DEPENDENCIES
rubocop-packaging rubocop-packaging
rubocop-performance rubocop-performance
rubocop-rails rubocop-rails
rubyzip (~> 2.0)
sdoc (>= 2.4.0) sdoc (>= 2.4.0)
selenium-webdriver (>= 4.0.0) selenium-webdriver (>= 4.0.0)
sequel sequel
@ -637,4 +633,4 @@ DEPENDENCIES
websocket-client-simple! websocket-client-simple!
BUNDLED WITH BUNDLED WITH
2.3.14 2.3.17

View File

@ -10,16 +10,16 @@ namespace :guides do
ruby "-Eutf-8:utf-8", "rails_guides.rb" ruby "-Eutf-8:utf-8", "rails_guides.rb"
end 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 task :kindle do
require "kindlerb" require "active_support/deprecation"
unless Kindlerb.kindlegen_available? ActiveSupport::Deprecation.warn("The guides:generate:kindle rake task is deprecated and will be removed in 7.2. Run rake guides:generate:epub instead")
abort "Please run `setupkindlerb` to install kindlegen" Rake::Task["guides:generate:epub"].invoke
end end
unless /convert/.match?(`convert`)
abort "Please install ImageMagick" desc "Generate .epub file"
end task :epub do
ENV["KINDLE"] = "1" ENV["EPUB"] = "1"
Rake::Task["guides:generate:html"].invoke Rake::Task["guides:generate:html"].invoke
end end
end end
@ -68,7 +68,7 @@ Some arguments may be passed via environment variables:
Examples: Examples:
$ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0 $ rake guides:generate ALL=1 RAILS_VERSION=v5.1.0
$ rake guides:generate ONLY=migrations $ rake guides:generate ONLY=migrations
$ rake guides:generate:kindle $ rake guides:generate:epub
$ rake guides:generate GUIDES_LANGUAGE=es $ rake guides:generate GUIDES_LANGUAGE=es
HELP HELP
end end

View File

@ -8,4 +8,9 @@ p, H1, H2, H3, H4, H5, H6, H7, H8, table { margin-top: 1em;}
} }
#toc .document { #toc .document {
text-indent: 2em; text-indent: 2em;
}
img {
display: block;
max-width: 100%;
} }

View File

@ -24,7 +24,7 @@ RailsGuides::Generator.new(
version: version, version: version,
all: env_flag["ALL"], all: env_flag["ALL"],
only: env_value["ONLY"], only: env_value["ONLY"],
kindle: env_flag["KINDLE"], epub: env_flag["EPUB"],
language: env_value["GUIDES_LANGUAGE"], language: env_value["GUIDES_LANGUAGE"],
direction: env_value["DIRECTION"] direction: env_value["DIRECTION"]
).generate ).generate

View 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

View 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

View File

@ -2,6 +2,8 @@
require "set" require "set"
require "fileutils" require "fileutils"
require "nokogiri"
require "securerandom"
require "active_support/core_ext/string/output_safety" require "active_support/core_ext/string/output_safety"
require "active_support/core_ext/object/blank" require "active_support/core_ext/object/blank"
@ -10,23 +12,23 @@ require "action_view"
require "rails_guides/markdown" require "rails_guides/markdown"
require "rails_guides/helpers" require "rails_guides/helpers"
require "rails_guides/epub"
module RailsGuides module RailsGuides
class Generator class Generator
GUIDES_RE = /\.(?:erb|md)\z/ 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 @edge = edge
@version = version @version = version
@all = all @all = all
@only = only @only = only
@kindle = kindle @epub = epub
@language = language @language = language
@direction = direction || "ltr" @direction = direction || "ltr"
if @kindle if @epub
check_for_kindlegen register_special_mime_types
register_kindle_mime_types
end end
initialize_dirs initialize_dirs
@ -37,32 +39,24 @@ module RailsGuides
def generate def generate
generate_guides generate_guides
copy_assets copy_assets
generate_mobi if @kindle generate_epub if @epub
end end
private 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", :opf, %w(opf))
Mime::Type.register_alias("application/xml", :ncx, %w(ncx)) Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
end end
def check_for_kindlegen def generate_epub
if `which kindlegen`.blank? Epub.generate(@output_dir, epub_filename)
raise "Can't create a kindle version without `kindlegen`." puts "Epub generated at: output/epub/#{epub_filename}"
end
end end
def generate_mobi def epub_filename
require "rails_guides/kindle" epub_filename = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
out = "#{@output_dir}/kindlegen.out" epub_filename << ".#{@language}" if @language
Kindle.generate(@output_dir, mobi, out) epub_filename << ".epub"
puts "(kindlegen log at #{out})."
end
def mobi
mobi = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
mobi << ".#{@language}" if @language
mobi << ".mobi"
end end
def initialize_dirs def initialize_dirs
@ -72,7 +66,7 @@ module RailsGuides
@source_dir += "/#{@language}" if @language @source_dir += "/#{@language}" if @language
@output_dir = "#{@guides_dir}/output" @output_dir = "#{@guides_dir}/output"
@output_dir += "/kindle" if @kindle @output_dir += "/epub/OEBPS" if @epub
@output_dir += "/#{@language}" if @language @output_dir += "/#{@language}" if @language
end end
@ -95,10 +89,9 @@ module RailsGuides
def guides_to_generate def guides_to_generate
guides = Dir.entries(@source_dir).grep(GUIDES_RE) guides = Dir.entries(@source_dir).grep(GUIDES_RE)
if @kindle if @epub
Dir.entries("#{@source_dir}/kindle").grep(GUIDES_RE).map do |entry| Dir.entries("#{@source_dir}/epub").grep(GUIDES_RE).map do |entry|
next if entry == "KINDLE.md" guides << "epub/#{entry}"
guides << "kindle/#{entry}"
end end
end end
@ -108,7 +101,7 @@ module RailsGuides
def select_only(guides) def select_only(guides)
prefixes = @only.split(",").map(&:strip) prefixes = @only.split(",").map(&:strip)
guides.select do |guide| guides.select do |guide|
guide.start_with?("kindle", *prefixes) guide.start_with?("epub", *prefixes)
end end
end end
@ -137,15 +130,16 @@ module RailsGuides
def generate_guide(guide, output_file) def generate_guide(guide, output_file)
output_path = output_path_for(output_file) output_path = output_path_for(output_file)
puts "Generating #{guide} as #{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( view = ActionView::Base.with_empty_template_cache.with_view_paths(
[@source_dir], [@source_dir],
edge: @edge, edge: @edge,
version: @version, version: @version,
mobi: "kindle/#{mobi}", epub: "epub/#{epub_filename}",
language: @language, language: @language,
direction: @direction, direction: @direction,
uuid: SecureRandom.uuid
) )
view.extend(Helpers) view.extend(Helpers)
@ -161,7 +155,8 @@ module RailsGuides
view: view, view: view,
layout: layout, layout: layout,
edge: @edge, edge: @edge,
version: @version version: @version,
epub: @epub
).render(body) ).render(body)
warn_about_broken_links(result) warn_about_broken_links(result)

View File

@ -28,6 +28,15 @@ module RailsGuides
documents.reject { |document| document["work_in_progress"] } documents.reject { |document| document["work_in_progress"] }
end 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) def docs_for_menu(position = nil)
if position.nil? if position.nil?
documents_by_section documents_by_section

View File

@ -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

View File

@ -3,11 +3,12 @@
require "redcarpet" require "redcarpet"
require "nokogiri" require "nokogiri"
require "rails_guides/markdown/renderer" require "rails_guides/markdown/renderer"
require "rails_guides/markdown/epub_renderer"
require "rails-html-sanitizer" require "rails-html-sanitizer"
module RailsGuides module RailsGuides
class Markdown class Markdown
def initialize(view:, layout:, edge:, version:) def initialize(view:, layout:, edge:, version:, epub:)
@view = view @view = view
@layout = layout @layout = layout
@edge = edge @edge = edge
@ -15,6 +16,7 @@ module RailsGuides
@index_counter = Hash.new(0) @index_counter = Hash.new(0)
@raw_header = "" @raw_header = ""
@node_ids = {} @node_ids = {}
@epub = epub
end end
def render(body) def render(body)
@ -59,7 +61,8 @@ module RailsGuides
end end
def engine def engine
@engine ||= Redcarpet::Markdown.new(Renderer, renderer = @epub ? EpubRenderer : Renderer
@engine ||= Redcarpet::Markdown.new(renderer,
no_intra_emphasis: true, no_intra_emphasis: true,
fenced_code_blocks: true, fenced_code_blocks: true,
autolink: true, autolink: true,
@ -91,7 +94,7 @@ module RailsGuides
def generate_structure def generate_structure
@headings_for_index = [] @headings_for_index = []
if @body.present? if @body.present?
@body = Nokogiri::HTML.fragment(@body).tap do |doc| document = Nokogiri::HTML.fragment(@body).tap do |doc|
hierarchy = [] hierarchy = []
doc.children.each do |node| doc.children.each do |node|
@ -117,7 +120,8 @@ module RailsGuides
doc.css("h3, h4, h5, h6").each do |node| doc.css("h3, h4, h5, h6").each do |node|
node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>" node.inner_html = "<a class='anchorlink' href='##{node[:id]}'>#{node.inner_html}</a>"
end end
end.to_html end
@body = @epub ? document.to_xhtml : document.to_html
end end
end end

View 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

View File

@ -7,7 +7,7 @@ Rouge::Lexers::Shell::BUILTINS << "|bin/rails|brew|bundle|gem|git|node|rails|rak
module RailsGuides module RailsGuides
class Markdown class Markdown
class Renderer < Redcarpet::Render::HTML class Renderer < Redcarpet::Render::HTML # :nodoc:
cattr_accessor :edge, :version cattr_accessor :edge, :version
def block_code(code, language) def block_code(code, language)

View File

@ -1,14 +1,9 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" <!DOCTYPE html>
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head> <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> <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title>
<link rel="stylesheet" type="text/css" href="stylesheets/kindle.css" />
</head> </head>
<body class="guide"> <body class="guide">

View 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>

View 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 &amp; License</a></li>
</ul>
</div>

View File

@ -18,7 +18,7 @@
</navLabel> </navLabel>
<content src="toc.html"/> <content src="toc.html"/>
<navPoint class="section" id="welcome" playOrder="1"> <navPoint class="section" id="start" playOrder="1">
<navLabel> <navLabel>
<text>Introduction</text> <text>Introduction</text>
</navLabel> </navLabel>
@ -30,7 +30,7 @@
</navLabel> </navLabel>
<content src="welcome.html"/> <content src="welcome.html"/>
</navPoint> </navPoint>
<navPoint class="article" id="copyright" playOrder="4"> <navPoint class="article" id="copyright" playOrder="3">
<navLabel><text>Copyright &amp; License</text></navLabel> <navLabel><text>Copyright &amp; License</text></navLabel>
<content src="copyright.html"/> <content src="copyright.html"/>
</navPoint> </navPoint>

View File

@ -10,7 +10,7 @@
<dl> <dl>
<dt></dt> <dt></dt>
<% unless @edge -%> <% 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 -%> <% 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> <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> </dl>

View File

@ -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>

View File

@ -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 &amp; License</a></li>
</ul>
</div>