2017-08-13 13:02:48 +00:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-06 17:21:59 +00:00
|
|
|
require "set"
|
|
|
|
require "fileutils"
|
2022-07-06 06:59:06 +00:00
|
|
|
require "nokogiri"
|
|
|
|
require "securerandom"
|
2009-02-28 02:24:23 +00:00
|
|
|
|
2016-08-06 17:21:59 +00:00
|
|
|
require "active_support/core_ext/string/output_safety"
|
|
|
|
require "active_support/core_ext/object/blank"
|
|
|
|
require "action_controller"
|
|
|
|
require "action_view"
|
2010-03-15 21:06:27 +00:00
|
|
|
|
2016-08-06 17:21:59 +00:00
|
|
|
require "rails_guides/markdown"
|
|
|
|
require "rails_guides/helpers"
|
2022-07-06 06:59:06 +00:00
|
|
|
require "rails_guides/epub"
|
2010-03-15 21:06:27 +00:00
|
|
|
|
2009-02-04 01:44:58 +00:00
|
|
|
module RailsGuides
|
|
|
|
class Generator
|
2012-09-18 21:04:12 +00:00
|
|
|
GUIDES_RE = /\.(?:erb|md)\z/
|
2009-02-04 01:44:58 +00:00
|
|
|
|
2022-07-06 06:59:06 +00:00
|
|
|
def initialize(edge:, version:, all:, only:, epub:, language:, direction: nil)
|
2018-11-22 19:48:25 +00:00
|
|
|
@edge = edge
|
|
|
|
@version = version
|
|
|
|
@all = all
|
|
|
|
@only = only
|
2022-07-06 06:59:06 +00:00
|
|
|
@epub = epub
|
2018-11-22 19:48:25 +00:00
|
|
|
@language = language
|
2022-06-22 19:43:07 +00:00
|
|
|
@direction = direction || "ltr"
|
2011-12-25 01:26:18 +00:00
|
|
|
|
2022-07-06 06:59:06 +00:00
|
|
|
if @epub
|
|
|
|
register_special_mime_types
|
2011-12-25 01:26:18 +00:00
|
|
|
end
|
|
|
|
|
2017-02-12 09:21:20 +00:00
|
|
|
initialize_dirs
|
2010-03-17 22:03:48 +00:00
|
|
|
create_output_dir_if_needed
|
2017-02-12 09:21:20 +00:00
|
|
|
initialize_markdown_renderer
|
2009-02-04 01:44:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def generate
|
2010-03-15 08:57:08 +00:00
|
|
|
generate_guides
|
2024-03-20 19:50:51 +00:00
|
|
|
process_scss
|
2010-03-15 08:57:08 +00:00
|
|
|
copy_assets
|
2022-07-06 06:59:06 +00:00
|
|
|
generate_epub if @epub
|
2010-03-15 08:57:08 +00:00
|
|
|
end
|
2009-02-04 01:44:58 +00:00
|
|
|
|
2010-03-15 08:57:08 +00:00
|
|
|
private
|
2022-07-06 06:59:06 +00:00
|
|
|
def register_special_mime_types
|
2017-02-12 09:21:20 +00:00
|
|
|
Mime::Type.register_alias("application/xml", :opf, %w(opf))
|
|
|
|
Mime::Type.register_alias("application/xml", :ncx, %w(ncx))
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2011-12-25 01:26:18 +00:00
|
|
|
|
2022-07-06 06:59:06 +00:00
|
|
|
def generate_epub
|
|
|
|
Epub.generate(@output_dir, epub_filename)
|
|
|
|
puts "Epub generated at: output/epub/#{epub_filename}"
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2011-12-25 01:26:18 +00:00
|
|
|
|
2022-07-06 06:59:06 +00:00
|
|
|
def epub_filename
|
|
|
|
epub_filename = +"ruby_on_rails_guides_#{@version || @edge[0, 7]}"
|
|
|
|
epub_filename << ".#{@language}" if @language
|
|
|
|
epub_filename << ".epub"
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2011-12-25 12:07:49 +00:00
|
|
|
|
2017-02-12 09:21:20 +00:00
|
|
|
def initialize_dirs
|
|
|
|
@guides_dir = File.expand_path("..", __dir__)
|
|
|
|
|
|
|
|
@source_dir = "#{@guides_dir}/source"
|
|
|
|
@source_dir += "/#{@language}" if @language
|
|
|
|
|
|
|
|
@output_dir = "#{@guides_dir}/output"
|
2022-07-06 06:59:06 +00:00
|
|
|
@output_dir += "/epub/OEBPS" if @epub
|
2017-03-08 01:54:06 +00:00
|
|
|
@output_dir += "/#{@language}" if @language
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2009-02-04 01:44:58 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def create_output_dir_if_needed
|
2017-02-12 09:21:20 +00:00
|
|
|
FileUtils.mkdir_p(@output_dir)
|
|
|
|
end
|
|
|
|
|
|
|
|
def initialize_markdown_renderer
|
|
|
|
Markdown::Renderer.edge = @edge
|
|
|
|
Markdown::Renderer.version = @version
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2010-04-02 21:53:39 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def generate_guides
|
|
|
|
guides_to_generate.each do |guide|
|
|
|
|
output_file = output_file_for(guide)
|
|
|
|
generate_guide(guide, output_file) if generate?(guide, output_file)
|
|
|
|
end
|
2009-02-04 01:44:58 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def guides_to_generate
|
2017-02-12 09:21:20 +00:00
|
|
|
guides = Dir.entries(@source_dir).grep(GUIDES_RE)
|
2011-12-25 01:26:18 +00:00
|
|
|
|
2022-07-06 06:59:06 +00:00
|
|
|
if @epub
|
|
|
|
Dir.entries("#{@source_dir}/epub").grep(GUIDES_RE).map do |entry|
|
|
|
|
guides << "epub/#{entry}"
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2011-12-25 01:26:18 +00:00
|
|
|
end
|
2016-08-06 17:55:02 +00:00
|
|
|
|
2017-02-12 09:21:20 +00:00
|
|
|
@only ? select_only(guides) : guides
|
2011-12-25 01:26:18 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def select_only(guides)
|
2017-02-12 09:21:20 +00:00
|
|
|
prefixes = @only.split(",").map(&:strip)
|
2016-08-06 17:55:02 +00:00
|
|
|
guides.select do |guide|
|
2022-07-06 06:59:06 +00:00
|
|
|
guide.start_with?("epub", *prefixes)
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
|
|
|
end
|
2009-02-04 01:44:58 +00:00
|
|
|
|
2024-03-20 19:50:51 +00:00
|
|
|
def process_scss
|
2024-04-18 16:31:43 +00:00
|
|
|
system "bundle exec dartsass ./assets/stylesrc/style.scss:#{@output_dir}/stylesheets/style.css ./assets/stylesrc/highlight.scss:#{@output_dir}/stylesheets/highlight.css ./assets/stylesrc/print.scss:#{@output_dir}/stylesheets/print.css"
|
2024-03-20 19:50:51 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def copy_assets
|
2024-03-20 19:50:51 +00:00
|
|
|
source_files = Dir.glob("#{@guides_dir}/assets/*").reject { |name| name.include?("stylesrc") }
|
|
|
|
FileUtils.cp_r(source_files, @output_dir)
|
2018-11-22 19:48:25 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def output_file_for(guide)
|
|
|
|
if guide.end_with?(".md")
|
|
|
|
guide.sub(/md\z/, "html")
|
|
|
|
else
|
2020-06-05 03:40:39 +00:00
|
|
|
guide.delete_suffix(".erb")
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
|
|
|
end
|
2009-02-21 23:29:11 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def output_path_for(output_file)
|
2017-02-12 09:21:20 +00:00
|
|
|
File.join(@output_dir, File.basename(output_file))
|
2011-12-25 01:26:18 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def generate?(source_file, output_file)
|
2017-02-12 09:21:20 +00:00
|
|
|
fin = File.join(@source_dir, source_file)
|
2016-08-06 17:55:02 +00:00
|
|
|
fout = output_path_for(output_file)
|
2017-02-12 09:21:20 +00:00
|
|
|
@all || !File.exist?(fout) || File.mtime(fout) < File.mtime(fin)
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2010-08-14 05:13:00 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def generate_guide(guide, output_file)
|
|
|
|
output_path = output_path_for(output_file)
|
|
|
|
puts "Generating #{guide} as #{output_file}"
|
2022-07-06 06:59:06 +00:00
|
|
|
layout = @epub ? "epub/layout" : "layout"
|
2009-02-21 23:29:11 +00:00
|
|
|
|
2023-03-08 02:25:16 +00:00
|
|
|
view = ActionView::Base.with_empty_template_cache.with_view_paths(
|
2019-01-29 23:17:52 +00:00
|
|
|
[@source_dir],
|
2018-04-02 09:30:23 +00:00
|
|
|
edge: @edge,
|
|
|
|
version: @version,
|
2022-07-06 06:59:06 +00:00
|
|
|
epub: "epub/#{epub_filename}",
|
2022-06-22 19:43:07 +00:00
|
|
|
language: @language,
|
|
|
|
direction: @direction,
|
2022-07-06 06:59:06 +00:00
|
|
|
uuid: SecureRandom.uuid
|
2018-04-02 09:30:23 +00:00
|
|
|
)
|
|
|
|
view.extend(Helpers)
|
|
|
|
|
|
|
|
if guide =~ /\.(\w+)\.erb$/
|
|
|
|
return if %w[_license _welcome layout].include?($`)
|
|
|
|
|
|
|
|
# Generate the special pages like the home.
|
|
|
|
# Passing a template handler in the template name is deprecated. So pass the file name without the extension.
|
2019-11-08 00:21:23 +00:00
|
|
|
result = view.render(layout: layout, formats: [$1.to_sym], template: $`)
|
2018-04-02 09:30:23 +00:00
|
|
|
else
|
|
|
|
body = File.read("#{@source_dir}/#{guide}")
|
|
|
|
result = RailsGuides::Markdown.new(
|
|
|
|
view: view,
|
|
|
|
layout: layout,
|
|
|
|
edge: @edge,
|
2022-07-06 06:59:06 +00:00
|
|
|
version: @version,
|
|
|
|
epub: @epub
|
2018-04-02 09:30:23 +00:00
|
|
|
).render(body)
|
|
|
|
|
|
|
|
warn_about_broken_links(result)
|
|
|
|
end
|
2010-02-17 19:22:37 +00:00
|
|
|
|
2018-04-02 09:30:23 +00:00
|
|
|
File.open(output_path, "w") do |f|
|
2016-08-06 17:55:02 +00:00
|
|
|
f.write(result)
|
2009-02-21 23:29:11 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def warn_about_broken_links(html)
|
|
|
|
anchors = extract_anchors(html)
|
|
|
|
check_fragment_identifiers(html, anchors)
|
|
|
|
end
|
2010-08-14 05:13:00 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def extract_anchors(html)
|
|
|
|
# Markdown generates headers with IDs computed from titles.
|
|
|
|
anchors = Set.new
|
|
|
|
html.scan(/<h\d\s+id="([^"]+)/).flatten.each do |anchor|
|
|
|
|
if anchors.member?(anchor)
|
2024-02-06 19:45:44 +00:00
|
|
|
puts "*** DUPLICATE ID: '#{anchor}', please make sure that there are no headings with the same name at the same level."
|
2016-08-06 17:55:02 +00:00
|
|
|
else
|
|
|
|
anchors << anchor
|
|
|
|
end
|
2009-03-15 21:16:27 +00:00
|
|
|
end
|
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
# Footnotes.
|
|
|
|
anchors += Set.new(html.scan(/<p\s+class="footnote"\s+id="([^"]+)/).flatten)
|
|
|
|
anchors += Set.new(html.scan(/<sup\s+class="footnote"\s+id="([^"]+)/).flatten)
|
2017-02-12 09:21:20 +00:00
|
|
|
anchors
|
2016-08-06 17:55:02 +00:00
|
|
|
end
|
2010-08-14 05:13:00 +00:00
|
|
|
|
2016-08-06 17:55:02 +00:00
|
|
|
def check_fragment_identifiers(html, anchors)
|
|
|
|
html.scan(/<a\s+href="#([^"]+)/).flatten.each do |fragment_identifier|
|
|
|
|
next if fragment_identifier == "mainCol" # in layout, jumps to some DIV
|
2018-11-19 16:59:52 +00:00
|
|
|
unless anchors.member?(CGI.unescape(fragment_identifier))
|
2022-03-14 17:52:04 +00:00
|
|
|
guess = DidYouMean::SpellChecker.new(dictionary: anchors).correct(fragment_identifier).first
|
2016-08-06 17:55:02 +00:00
|
|
|
puts "*** BROKEN LINK: ##{fragment_identifier}, perhaps you meant ##{guess}."
|
|
|
|
end
|
2009-02-28 02:24:23 +00:00
|
|
|
end
|
|
|
|
end
|
2009-02-04 01:44:58 +00:00
|
|
|
end
|
|
|
|
end
|