bookshelf 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/.gitmodules +3 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +66 -0
- data/README.md +162 -0
- data/Rakefile +5 -0
- data/bin/bookshelf +5 -0
- data/bookshelf.gemspec +37 -0
- data/lib/bookshelf.rb +64 -0
- data/lib/bookshelf/adapters/markdown.rb +34 -0
- data/lib/bookshelf/cli.rb +125 -0
- data/lib/bookshelf/dependency.rb +15 -0
- data/lib/bookshelf/errors.rb +3 -0
- data/lib/bookshelf/exporter.rb +56 -0
- data/lib/bookshelf/extensions/redcloth.rb +69 -0
- data/lib/bookshelf/extensions/string.rb +11 -0
- data/lib/bookshelf/generator.rb +75 -0
- data/lib/bookshelf/parser.rb +54 -0
- data/lib/bookshelf/parser/epub.rb +146 -0
- data/lib/bookshelf/parser/html.rb +177 -0
- data/lib/bookshelf/parser/mobi.rb +14 -0
- data/lib/bookshelf/parser/pdf.rb +44 -0
- data/lib/bookshelf/parser/txt.rb +18 -0
- data/lib/bookshelf/stats.rb +45 -0
- data/lib/bookshelf/stream.rb +27 -0
- data/lib/bookshelf/syntax.rb +124 -0
- data/lib/bookshelf/toc.rb +6 -0
- data/lib/bookshelf/toc/epub.rb +41 -0
- data/lib/bookshelf/toc/html.rb +78 -0
- data/lib/bookshelf/version.rb +8 -0
- data/templates/Guardfile +12 -0
- data/templates/config.erb +44 -0
- data/templates/cover.erb +16 -0
- data/templates/cover.png +0 -0
- data/templates/ebook.png +0 -0
- data/templates/epub.css +500 -0
- data/templates/epub.erb +15 -0
- data/templates/helper.rb +29 -0
- data/templates/layout.css +353 -0
- data/templates/layout.erb +44 -0
- data/templates/user.css +1 -0
- metadata +244 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
module Bookshelf
|
2
|
+
class Dependency
|
3
|
+
def self.kindlegen?
|
4
|
+
@kindlegen ||= `which kindlegen` && $?.success?
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.prince?
|
8
|
+
@prince ||= `which prince` && $?.success?
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.html2text?
|
12
|
+
@html2text ||= `which html2text` && $?.success?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bookshelf
|
2
|
+
class Exporter
|
3
|
+
def self.run(book_dir, options)
|
4
|
+
exporter = new(book_dir, options)
|
5
|
+
exporter.export!
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor :book_dir
|
9
|
+
attr_accessor :options
|
10
|
+
|
11
|
+
def initialize(book_dir, options)
|
12
|
+
@book_dir = book_dir
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def ui
|
17
|
+
@ui ||= Thor::Base.shell.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def export!
|
21
|
+
helper = Bookshelf.root_dir.join("config/helper.rb")
|
22
|
+
load(helper) if helper.exist?
|
23
|
+
|
24
|
+
export_pdf = [nil, "pdf"].include?(options[:only])
|
25
|
+
export_epub = [nil, "mobi", "epub"].include?(options[:only])
|
26
|
+
export_mobi = [nil, "mobi"].include?(options[:only])
|
27
|
+
export_txt = [nil, "txt"].include?(options[:only])
|
28
|
+
|
29
|
+
exported = []
|
30
|
+
exported << Parser::HTML.parse(book_dir)
|
31
|
+
exported << Parser::PDF.parse(book_dir) if export_pdf && Dependency.prince?
|
32
|
+
exported << Parser::Epub.parse(book_dir) if export_epub
|
33
|
+
exported << Parser::Mobi.parse(book_dir) if export_mobi && Dependency.kindlegen?
|
34
|
+
exported << Parser::Txt.parse(book_dir) if export_txt && Dependency.html2text?
|
35
|
+
|
36
|
+
if exported.all?
|
37
|
+
color = :green
|
38
|
+
message = options[:auto] ? "exported!" : "** e-book has been exported"
|
39
|
+
Notifier.notify(
|
40
|
+
:image => Bookshelf::ROOT.join("templates/ebook.png"),
|
41
|
+
:title => "Bookshelf",
|
42
|
+
:message => "Your \"#{config[:title]}\" e-book has been exported!"
|
43
|
+
)
|
44
|
+
else
|
45
|
+
color = :red
|
46
|
+
message = options[:auto] ? "could not be exported!" : "** e-book couldn't be exported"
|
47
|
+
end
|
48
|
+
|
49
|
+
ui.say message, color
|
50
|
+
end
|
51
|
+
|
52
|
+
def config
|
53
|
+
Bookshelf.config(book_dir)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module RedCloth
|
2
|
+
INLINE_FORMATTERS = [:textile, :footnote, :link]
|
3
|
+
|
4
|
+
def self.convert(text)
|
5
|
+
new(text).to_html(*INLINE_FORMATTERS)
|
6
|
+
end
|
7
|
+
|
8
|
+
module Inline
|
9
|
+
FN_RE = /
|
10
|
+
(\s+)? # getting spaces
|
11
|
+
(\\)?%\{ # opening
|
12
|
+
(.*?) # footnote
|
13
|
+
\}# # closing
|
14
|
+
/xm
|
15
|
+
|
16
|
+
def footnote(text)
|
17
|
+
text.gsub!(FN_RE) do |m|
|
18
|
+
if $2
|
19
|
+
%[#{$1}%{#{$3}}]
|
20
|
+
else
|
21
|
+
%(<span class="footnote">#{$3}</span>)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
LINK_RE = /
|
27
|
+
<
|
28
|
+
((?:https?|ftp):\/\/.*?)
|
29
|
+
>
|
30
|
+
/xm
|
31
|
+
|
32
|
+
def link(text)
|
33
|
+
text.gsub!(LINK_RE) do |m|
|
34
|
+
%(<a href="#{$1}">#{$1}</a>)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Formatters
|
40
|
+
module HTML
|
41
|
+
def figure(options = {})
|
42
|
+
%[<p class="figure"><img src="../images/#{options[:text]}" alt="#{options[:class]}" /><br/><span class="caption">#{options[:class]}</span></p>]
|
43
|
+
end
|
44
|
+
|
45
|
+
def note(options = {})
|
46
|
+
%[<p class="note">#{options[:text]}</p>]
|
47
|
+
end
|
48
|
+
|
49
|
+
def attention(options = {})
|
50
|
+
%[<p class="attention">#{options[:text]}</p>]
|
51
|
+
end
|
52
|
+
|
53
|
+
def file(options = {})
|
54
|
+
base_url = Bookshelf.config[:base_url]
|
55
|
+
|
56
|
+
if base_url
|
57
|
+
url = File.join(base_url, options[:text])
|
58
|
+
else
|
59
|
+
url = content
|
60
|
+
$stderr << "\nYou're using `file. #{content}` but didn't set base_url in your configuration file.\n"
|
61
|
+
end
|
62
|
+
|
63
|
+
%[<p class="file"><span><strong>Download</strong> <a href="#{url}">#{options[:text]}</a></span></p>]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
RedCloth.send(:include, RedCloth::Inline)
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class String
|
2
|
+
def to_permalink
|
3
|
+
str = ActiveSupport::Multibyte::Chars.new(self.dup)
|
4
|
+
str = str.normalize(:kd).gsub(/[^\x00-\x7F]/,'').to_s
|
5
|
+
str.gsub!(/[^-\w\d]+/xim, "-")
|
6
|
+
str.gsub!(/-+/xm, "-")
|
7
|
+
str.gsub!(/^-?(.*?)-?$/, '\1')
|
8
|
+
str.downcase!
|
9
|
+
str
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Bookshelf
|
2
|
+
# The Bookshelf::Generator class will create a new book structure.
|
3
|
+
#
|
4
|
+
# ebook = Bookshelf::Generator.new
|
5
|
+
# ebook.destination_root = "/some/path/book-name"
|
6
|
+
# ebook.invoke_all
|
7
|
+
#
|
8
|
+
class Generator < Thor::Group
|
9
|
+
include Thor::Actions
|
10
|
+
|
11
|
+
desc "Generate a new e-Book structure"
|
12
|
+
|
13
|
+
def self.source_root
|
14
|
+
File.dirname(__FILE__) + "/../../templates"
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_html_templates
|
18
|
+
copy_file "layout.erb" , "templates/html/layout.erb"
|
19
|
+
copy_file "layout.css" , "templates/html/layout.css"
|
20
|
+
copy_file "user.css" , "templates/html/user.css"
|
21
|
+
copy_file "syntax.css" , "templates/html/syntax.css"
|
22
|
+
end
|
23
|
+
|
24
|
+
def copy_epub_templates
|
25
|
+
copy_file "cover.erb" , "templates/epub/cover.erb"
|
26
|
+
copy_file "epub.css" , "templates/epub/user.css"
|
27
|
+
copy_file "epub.erb" , "templates/epub/page.erb"
|
28
|
+
copy_file "cover.png" , "templates/epub/cover.png"
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy_sample_page
|
32
|
+
copy_file "sample.md" , "text/01_Welcome.md"
|
33
|
+
end
|
34
|
+
|
35
|
+
def copy_config_file
|
36
|
+
@name = full_name
|
37
|
+
@uid = Digest::MD5.hexdigest("#{Time.now}--#{rand}")
|
38
|
+
@year = Date.today.year
|
39
|
+
template "config.erb", "config/bookshelf.yml"
|
40
|
+
end
|
41
|
+
|
42
|
+
def copy_helper_file
|
43
|
+
copy_file "helper.rb", "config/helper.rb"
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_directories
|
47
|
+
empty_directory "output"
|
48
|
+
empty_directory "images"
|
49
|
+
empty_directory "code"
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_git_files
|
53
|
+
create_file ".gitignore" do
|
54
|
+
"output/*.{html,epub,pdf}\noutput/tmp"
|
55
|
+
end
|
56
|
+
|
57
|
+
create_file "output/.gitkeep"
|
58
|
+
create_file "images/.gitkeep"
|
59
|
+
create_file "code/.gitkeep"
|
60
|
+
end
|
61
|
+
|
62
|
+
def copy_guardfile
|
63
|
+
copy_file "Guardfile", "Guardfile"
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
# Retrieve user's name using finger.
|
68
|
+
# Defaults to <tt>John Doe</tt>.
|
69
|
+
#
|
70
|
+
def full_name
|
71
|
+
name = `finger $USER 2> /dev/null | grep Login | colrm 1 46`.chomp
|
72
|
+
name.present? ? name.squish : "John Doe"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module Bookshelf
|
4
|
+
module Parser
|
5
|
+
autoload :HTML , "bookshelf/parser/html"
|
6
|
+
autoload :PDF , "bookshelf/parser/pdf"
|
7
|
+
autoload :Epub , "bookshelf/parser/epub"
|
8
|
+
autoload :Mobi , "bookshelf/parser/mobi"
|
9
|
+
autoload :Txt , "bookshelf/parser/txt"
|
10
|
+
|
11
|
+
class Base
|
12
|
+
# The e-book directory.
|
13
|
+
#
|
14
|
+
attr_accessor :book_dir
|
15
|
+
|
16
|
+
def self.parse(book_dir)
|
17
|
+
new(book_dir).parse
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(book_dir)
|
21
|
+
@book_dir = Pathname.new(book_dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return directory's basename.
|
25
|
+
#
|
26
|
+
def name
|
27
|
+
File.basename(book_dir)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the configuration file.
|
31
|
+
#
|
32
|
+
def config
|
33
|
+
Bookshelf.config(book_dir)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Render a eRb template using +locals+ as data seed.
|
37
|
+
#
|
38
|
+
def render_template(file, locals = {})
|
39
|
+
ERB.new(File.read(file)).result OpenStruct.new(locals).instance_eval{ binding }
|
40
|
+
end
|
41
|
+
|
42
|
+
def spawn_command(cmd)
|
43
|
+
begin
|
44
|
+
stdout_and_stderr, status = Open3.capture2e(*cmd)
|
45
|
+
rescue Errno::ENOENT => e
|
46
|
+
puts e.message
|
47
|
+
else
|
48
|
+
puts stdout_and_stderr unless status.success?
|
49
|
+
status.success?
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Bookshelf
|
2
|
+
module Parser
|
3
|
+
class Epub < Base
|
4
|
+
def sections
|
5
|
+
@sections ||= html.css("div.chapter").each_with_index.map do |chapter, index|
|
6
|
+
OpenStruct.new({
|
7
|
+
:index => index,
|
8
|
+
:filename => "section_#{index}.html",
|
9
|
+
:filepath => tmp_dir.join("section_#{index}.html").to_s,
|
10
|
+
:html => Nokogiri::HTML(chapter.inner_html)
|
11
|
+
})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def epub
|
16
|
+
@epub ||= EeePub::Maker.new
|
17
|
+
end
|
18
|
+
|
19
|
+
def html
|
20
|
+
@html ||= Nokogiri::HTML(html_path.read)
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse
|
24
|
+
epub.title config[:title]
|
25
|
+
epub.language config[:language]
|
26
|
+
epub.creator config[:authors].to_sentence
|
27
|
+
epub.publisher config[:publisher]
|
28
|
+
epub.date config[:published_at]
|
29
|
+
epub.uid config[:uid]
|
30
|
+
epub.identifier config[:identifier][:id], :scheme => config[:identifier][:type]
|
31
|
+
epub.cover_page cover_image if cover_image && File.exist?(cover_image)
|
32
|
+
|
33
|
+
write_sections!
|
34
|
+
write_toc!
|
35
|
+
|
36
|
+
epub.files sections.map(&:filepath) + assets
|
37
|
+
epub.nav navigation
|
38
|
+
epub.toc_page toc_path
|
39
|
+
|
40
|
+
epub.save(epub_path)
|
41
|
+
|
42
|
+
true
|
43
|
+
rescue Exception
|
44
|
+
p $!, $@
|
45
|
+
false
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_toc!
|
49
|
+
toc = TOC::Epub.new(navigation)
|
50
|
+
|
51
|
+
File.open(toc_path, "w") do |file|
|
52
|
+
file << toc.to_html
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def write_sections!
|
57
|
+
# First we need to get all ids, which are used as
|
58
|
+
# the anchor target.
|
59
|
+
#
|
60
|
+
links = sections.inject({}) do |buffer, section|
|
61
|
+
section.html.css("[id]").each do |element|
|
62
|
+
anchor = "##{element["id"]}"
|
63
|
+
buffer[anchor] = "#{section.filename}#{anchor}"
|
64
|
+
end
|
65
|
+
|
66
|
+
buffer
|
67
|
+
end
|
68
|
+
|
69
|
+
# Then we can normalize all links and
|
70
|
+
# manipulate other paths.
|
71
|
+
#
|
72
|
+
sections.each do |section|
|
73
|
+
section.html.css("a[href^='#']").each do |link|
|
74
|
+
href = link["href"]
|
75
|
+
link.set_attribute("href", links.fetch(href, href))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Replace all srcs.
|
79
|
+
#
|
80
|
+
section.html.css("[src]").each do |element|
|
81
|
+
src = File.basename(element["src"]).gsub(/\.svg$/, ".png")
|
82
|
+
element.set_attribute("src", src)
|
83
|
+
element.set_attribute("alt", "")
|
84
|
+
element.node_name = "img"
|
85
|
+
end
|
86
|
+
|
87
|
+
FileUtils.mkdir_p(tmp_dir)
|
88
|
+
|
89
|
+
# Save file to disk.
|
90
|
+
#
|
91
|
+
File.open(section.filepath, "w") do |file|
|
92
|
+
body = section.html.css("body").to_xhtml.gsub(%r[<body>(.*?)</body>]m, "\\1")
|
93
|
+
file << render_chapter(body)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def render_chapter(content)
|
99
|
+
locals = config.merge(:content => content)
|
100
|
+
render_template(template_path, locals)
|
101
|
+
end
|
102
|
+
|
103
|
+
def assets
|
104
|
+
@assets ||= begin
|
105
|
+
assets = Dir[Bookshelf.root_dir.join("templates/epub/*.css")]
|
106
|
+
assets += Dir[Bookshelf.root_dir.join("assets/images/**/*.{jpg,png,gif}")]
|
107
|
+
assets
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def cover_image
|
112
|
+
path = Dir[book_dir.join("cover.{jpg,png,gif}").to_s].first
|
113
|
+
return path if path && File.exist?(path)
|
114
|
+
end
|
115
|
+
|
116
|
+
def navigation
|
117
|
+
sections.map do |section|
|
118
|
+
{
|
119
|
+
:label => section.html.css("h2:first-of-type").text,
|
120
|
+
:content => section.filename
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def template_path
|
126
|
+
Bookshelf.root_dir.join("templates/epub/page.erb")
|
127
|
+
end
|
128
|
+
|
129
|
+
def html_path
|
130
|
+
Bookshelf.root_dir.join("output/#{name}.html")
|
131
|
+
end
|
132
|
+
|
133
|
+
def epub_path
|
134
|
+
Bookshelf.root_dir.join("output/#{name}.epub")
|
135
|
+
end
|
136
|
+
|
137
|
+
def tmp_dir
|
138
|
+
Bookshelf.root_dir.join("output/tmp")
|
139
|
+
end
|
140
|
+
|
141
|
+
def toc_path
|
142
|
+
tmp_dir.join("toc.html")
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module Bookshelf
|
2
|
+
module Parser
|
3
|
+
class HTML < Base
|
4
|
+
# List of directories that should be skipped.
|
5
|
+
#
|
6
|
+
IGNORE_DIR = %w[. .. .svn]
|
7
|
+
|
8
|
+
# Files that should be skipped.
|
9
|
+
#
|
10
|
+
IGNORE_FILES = /^(TOC)\..*?$/
|
11
|
+
|
12
|
+
# List of recognized extensions.
|
13
|
+
#
|
14
|
+
EXTENSIONS = %w[md mkdn markdown textile html]
|
15
|
+
|
16
|
+
class << self
|
17
|
+
# The footnote index control. We have to manipulate footnotes
|
18
|
+
# because each chapter starts from 1, so we have duplicated references.
|
19
|
+
#
|
20
|
+
attr_accessor :footnote_index
|
21
|
+
end
|
22
|
+
|
23
|
+
# Parse all files and save the parsed content
|
24
|
+
# to <tt>output/book_name.html</tt>.
|
25
|
+
#
|
26
|
+
def parse
|
27
|
+
reset_footnote_index!
|
28
|
+
|
29
|
+
File.open(Bookshelf.root_dir.join("output/#{name}.html"), "w") do |file|
|
30
|
+
file << parse_layout(content)
|
31
|
+
end
|
32
|
+
true
|
33
|
+
# rescue Exception
|
34
|
+
# false
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset_footnote_index!
|
38
|
+
self.class.footnote_index = 1
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return all chapters wrapped in a <tt>div.chapter</tt> tag.
|
42
|
+
#
|
43
|
+
def content
|
44
|
+
String.new.tap do |chapters|
|
45
|
+
entries.each do |entry|
|
46
|
+
files = chapter_files(entry)
|
47
|
+
|
48
|
+
# no markup files, so skip to the next one!
|
49
|
+
next if files.empty?
|
50
|
+
|
51
|
+
chapters << %[<div class="chapter">#{render_chapter(files)}</div>]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return a list of all recognized files.
|
57
|
+
#
|
58
|
+
def entries
|
59
|
+
Dir.entries(book_dir).sort.inject([]) do |buffer, entry|
|
60
|
+
buffer << book_dir.join(entry) if valid_entry?(entry)
|
61
|
+
buffer
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def chapter_files(entry)
|
67
|
+
# Chapters can be files outside a directory.
|
68
|
+
if File.file?(entry)
|
69
|
+
[entry]
|
70
|
+
else
|
71
|
+
Dir.glob("#{entry}/**/*.{#{EXTENSIONS.join(",")}}").sort
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Check if path is a valid entry.
|
76
|
+
# Files/directories that start with a dot or underscore will be skipped.
|
77
|
+
#
|
78
|
+
def valid_entry?(entry)
|
79
|
+
entry !~ /^(\.|_)/ && (valid_directory?(entry) || valid_file?(entry))
|
80
|
+
end
|
81
|
+
|
82
|
+
# Check if path is a valid directory.
|
83
|
+
#
|
84
|
+
def valid_directory?(entry)
|
85
|
+
File.directory?(book_dir.join(entry)) && !IGNORE_DIR.include?(File.basename(entry))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Check if path is a valid file.
|
89
|
+
#
|
90
|
+
def valid_file?(entry)
|
91
|
+
ext = File.extname(entry).gsub(/\./, "").downcase
|
92
|
+
File.file?(book_dir.join(entry)) && EXTENSIONS.include?(ext) && entry !~ IGNORE_FILES
|
93
|
+
end
|
94
|
+
|
95
|
+
# Render +file+ considering its extension.
|
96
|
+
#
|
97
|
+
def render_file(file, plain_syntax = false)
|
98
|
+
file_format = format(file)
|
99
|
+
content = Bookshelf::Syntax.render(book_dir, file_format, File.read(file), plain_syntax)
|
100
|
+
content = case file_format
|
101
|
+
when :markdown
|
102
|
+
Markdown.to_html(content)
|
103
|
+
when :textile
|
104
|
+
RedCloth.convert(content)
|
105
|
+
else
|
106
|
+
content
|
107
|
+
end
|
108
|
+
|
109
|
+
render_footnotes(content, plain_syntax)
|
110
|
+
end
|
111
|
+
|
112
|
+
def render_footnotes(content, plain_syntax = false)
|
113
|
+
html = Nokogiri::HTML(content)
|
114
|
+
footnotes = html.css("p[id^='fn']")
|
115
|
+
|
116
|
+
return content if footnotes.empty?
|
117
|
+
|
118
|
+
reset_footnote_index! unless self.class.footnote_index
|
119
|
+
|
120
|
+
footnotes.each do |fn|
|
121
|
+
index = self.class.footnote_index
|
122
|
+
actual_index = fn["id"].gsub(/[^\d]/, "")
|
123
|
+
|
124
|
+
fn.set_attribute("id", "_fn#{index}")
|
125
|
+
|
126
|
+
html.css("a[href='#fn#{actual_index}']").each do |link|
|
127
|
+
link.set_attribute("href", "#_fn#{index}")
|
128
|
+
end
|
129
|
+
|
130
|
+
html.css("a[href='#fnr#{actual_index}']").each do |link|
|
131
|
+
link.set_attribute("href", "#_fnr#{index}")
|
132
|
+
end
|
133
|
+
|
134
|
+
html.css("[id=fnr#{actual_index}]").each do |tag|
|
135
|
+
tag.set_attribute("id", "_fnr#{index}")
|
136
|
+
end
|
137
|
+
|
138
|
+
self.class.footnote_index += 1
|
139
|
+
end
|
140
|
+
|
141
|
+
html.css("body").inner_html
|
142
|
+
end
|
143
|
+
|
144
|
+
def format(file)
|
145
|
+
case File.extname(file).downcase
|
146
|
+
when ".markdown", ".mkdn", ".md"
|
147
|
+
:markdown
|
148
|
+
when ".textile"
|
149
|
+
:textile
|
150
|
+
else
|
151
|
+
:html
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Parse layout file, making available all configuration entries.
|
156
|
+
#
|
157
|
+
def parse_layout(html)
|
158
|
+
toc = TOC::HTML.generate(html)
|
159
|
+
locals = config.merge({
|
160
|
+
:content => toc.content,
|
161
|
+
:toc => toc.to_html
|
162
|
+
})
|
163
|
+
render_template(Bookshelf.root_dir.join("templates/html/layout.erb"), locals)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Render all +files+ from a given chapter.
|
167
|
+
#
|
168
|
+
def render_chapter(files, plain_syntax = false)
|
169
|
+
String.new.tap do |chapter|
|
170
|
+
files.each do |file|
|
171
|
+
chapter << render_file(file, plain_syntax) << "\n\n"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|