bookshelf 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|