kitabu 1.0.0.rc1 → 1.0.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +14 -20
- data/README.rdoc +54 -23
- data/kitabu.gemspec +1 -1
- data/lib/kitabu.rb +11 -4
- data/lib/kitabu/cli.rb +49 -6
- data/lib/kitabu/dependency.rb +19 -0
- data/lib/kitabu/exporter.rb +4 -5
- data/lib/kitabu/generator.rb +12 -15
- data/lib/kitabu/parser.rb +27 -2
- data/lib/kitabu/parser/epub.rb +96 -83
- data/lib/kitabu/parser/html.rb +51 -16
- data/lib/kitabu/parser/mobi.rb +1 -4
- data/lib/kitabu/parser/pdf.rb +23 -20
- data/lib/kitabu/parser/txt.rb +1 -5
- data/lib/kitabu/syntax.rb +5 -3
- data/lib/kitabu/syntax/highlight.rb +22 -0
- data/lib/kitabu/toc.rb +4 -78
- data/lib/kitabu/toc/epub.rb +41 -0
- data/lib/kitabu/toc/html.rb +78 -0
- data/lib/kitabu/version.rb +1 -1
- data/spec/kitabu/cli/export_spec.rb +2 -2
- data/spec/kitabu/cli/new_spec.rb +1 -1
- data/spec/kitabu/cli/permalinks_spec.rb +1 -1
- data/spec/kitabu/cli/version_spec.rb +1 -1
- data/spec/kitabu/extensions/redcloth_spec.rb +6 -6
- data/spec/kitabu/extensions/string_spec.rb +1 -1
- data/spec/kitabu/parser/epub_spec.rb +5 -1
- data/spec/kitabu/parser/html_spec.rb +15 -15
- data/spec/kitabu/parser/pdf_spec.rb +4 -4
- data/spec/kitabu/syntax_spec.rb +78 -74
- data/spec/kitabu/{toc_spec.rb → toc/html_spec.rb} +5 -5
- data/spec/spec_helper.rb +3 -1
- data/spec/support/mybook/output/mybook.pdf.html +83 -0
- data/spec/support/mybook/templates/{cover.erb → epub/cover.erb} +1 -1
- data/spec/support/mybook/templates/{epub.erb → epub/page.erb} +1 -1
- data/spec/support/mybook/templates/epub/style.css +0 -0
- data/spec/support/mybook/templates/html/layout.css +353 -0
- data/spec/support/mybook/templates/html/layout.erb +50 -0
- data/spec/support/mybook/templates/html/syntax.css +58 -0
- data/spec/support/mybook/templates/html/user.css +1 -0
- data/spec/support/shared.rb +48 -15
- data/templates/cover.erb +1 -1
- data/templates/cover.png +0 -0
- data/templates/epub.css +1 -0
- data/templates/epub.erb +1 -1
- data/templates/sample.md +6 -0
- data/templates/syntax.css +58 -0
- metadata +32 -44
- data/spec/support/mybook/templates/epub.css +0 -1
- data/spec/support/mybook/templates/layout.css +0 -137
- data/spec/support/mybook/templates/layout.erb +0 -46
- data/spec/support/mybook/templates/syntax.css +0 -186
- data/spec/support/mybook/templates/user.css +0 -1
- data/templates/styles/active4d.css +0 -114
- data/templates/styles/all_hallows_eve.css +0 -72
- data/templates/styles/amy.css +0 -147
- data/templates/styles/blackboard.css +0 -88
- data/templates/styles/brilliance_black.css +0 -605
- data/templates/styles/brilliance_dull.css +0 -599
- data/templates/styles/cobalt.css +0 -149
- data/templates/styles/dawn.css +0 -121
- data/templates/styles/eiffel.css +0 -121
- data/templates/styles/espresso_libre.css +0 -109
- data/templates/styles/idle.css +0 -62
- data/templates/styles/iplastic.css +0 -80
- data/templates/styles/lazy.css +0 -73
- data/templates/styles/mac_classic.css +0 -123
- data/templates/styles/magicwb_amiga.css +0 -104
- data/templates/styles/pastels_on_dark.css +0 -188
- data/templates/styles/slush_poppies.css +0 -85
- data/templates/styles/spacecadet.css +0 -51
- data/templates/styles/sunburst.css +0 -180
- data/templates/styles/twilight.css +0 -137
- data/templates/styles/zenburnesque.css +0 -91
data/lib/kitabu/parser/epub.rb
CHANGED
@@ -1,50 +1,23 @@
|
|
1
1
|
module Kitabu
|
2
2
|
module Parser
|
3
|
-
class Epub <
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
<title>Table of Contents</title>
|
13
|
-
</head>
|
14
|
-
<body>
|
15
|
-
<div id='toc'>
|
16
|
-
<ul>
|
17
|
-
HEAD
|
18
|
-
|
19
|
-
TAIL = <<-TAIL
|
20
|
-
</ul>
|
21
|
-
</div>
|
22
|
-
</body>
|
23
|
-
</html>
|
24
|
-
TAIL
|
25
|
-
|
26
|
-
def generate_html(nav)
|
27
|
-
HEAD +
|
28
|
-
nav.map { |element|
|
29
|
-
"<li><a href='#{element[:content]}'>#{element[:label]}</a></li>"
|
30
|
-
}.join +
|
31
|
-
TAIL
|
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
|
+
})
|
32
12
|
end
|
33
|
-
|
34
|
-
def generate_file(*args)
|
35
|
-
filename = "tmp/toc.html"
|
36
|
-
File.open(filename, 'w') { |file| file.write generate_html(*args) }
|
37
|
-
filename
|
38
|
-
end
|
39
|
-
module_function :generate_file, :generate_html
|
40
13
|
end
|
41
14
|
|
42
|
-
|
15
|
+
def epub
|
16
|
+
@epub ||= EeePub::Maker.new
|
17
|
+
end
|
43
18
|
|
44
|
-
def
|
45
|
-
|
46
|
-
FileUtils.mkdir_p('tmp')
|
47
|
-
@epub = EeePub::Maker.new
|
19
|
+
def html
|
20
|
+
@html ||= Nokogiri::HTML(html_path.read)
|
48
21
|
end
|
49
22
|
|
50
23
|
def parse
|
@@ -57,75 +30,115 @@ TAIL
|
|
57
30
|
epub.identifier config[:identifier][:id], :scheme => config[:identifier][:type]
|
58
31
|
epub.cover_page cover_image
|
59
32
|
|
60
|
-
|
61
|
-
|
62
|
-
filenames = collect_filenames(sections)
|
33
|
+
write_sections!
|
34
|
+
write_toc!
|
63
35
|
|
64
|
-
epub.files
|
65
|
-
epub.nav
|
66
|
-
|
67
|
-
epub.toc_page Toc.generate_file collect_nav(sections, filenames)
|
36
|
+
epub.files sections.map(&:filepath) + assets
|
37
|
+
epub.nav navigation
|
38
|
+
epub.toc_page toc_path
|
68
39
|
|
69
40
|
epub.save(epub_path)
|
41
|
+
|
70
42
|
true
|
71
43
|
rescue Exception
|
72
44
|
p $!, $@
|
73
45
|
false
|
74
46
|
end
|
75
47
|
|
76
|
-
def
|
77
|
-
|
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
|
78
54
|
end
|
79
55
|
|
80
|
-
def
|
81
|
-
|
82
|
-
|
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
|
+
end
|
84
|
+
|
85
|
+
FileUtils.mkdir_p(tmp_dir)
|
86
|
+
|
87
|
+
# Save file to disk.
|
88
|
+
#
|
89
|
+
File.open(section.filepath, "w") do |file|
|
90
|
+
file << render_chapter(section.html.css("body").inner_html)
|
91
|
+
end
|
92
|
+
end
|
83
93
|
end
|
84
94
|
|
85
|
-
def
|
86
|
-
|
87
|
-
|
88
|
-
html = Nokogiri(content)
|
89
|
-
title = html.css("h2").first.text
|
90
|
-
|
91
|
-
[
|
92
|
-
title,
|
93
|
-
render_template(
|
94
|
-
root_dir.join("templates/epub/page.erb"),
|
95
|
-
{:chapter_title => title, :content => content, :language => config[:language]}
|
96
|
-
)
|
97
|
-
]
|
95
|
+
def render_chapter(content)
|
96
|
+
locals = config.merge(:content => content)
|
97
|
+
render_template(template_path, locals)
|
98
98
|
end
|
99
99
|
|
100
|
-
def
|
101
|
-
|
100
|
+
def assets
|
101
|
+
@assets ||= begin
|
102
|
+
assets = Dir[root_dir.join("templates/epub/*.css")]
|
103
|
+
assets += Dir[root_dir.join("images/**/*.{jpg,png,gif}")]
|
104
|
+
assets << cover_image if cover_image
|
105
|
+
assets
|
106
|
+
end
|
102
107
|
end
|
103
108
|
|
104
|
-
def
|
105
|
-
|
106
|
-
|
109
|
+
def cover_image
|
110
|
+
path = Dir[root_dir.join("templates/epub/cover.{jpg,png,gif}").to_s].first
|
111
|
+
return path if path && File.exist?(path)
|
112
|
+
end
|
113
|
+
|
114
|
+
def navigation
|
107
115
|
sections.map do |section|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
filename
|
116
|
+
{
|
117
|
+
:label => section.html.css("h2:first-of-type").text,
|
118
|
+
:content => section.filename
|
119
|
+
}
|
113
120
|
end
|
114
121
|
end
|
115
|
-
|
116
|
-
def
|
117
|
-
|
118
|
-
{:label => section[0], :content => File.basename(filename)}
|
119
|
-
end
|
122
|
+
|
123
|
+
def template_path
|
124
|
+
root_dir.join("templates/epub/page.erb")
|
120
125
|
end
|
121
126
|
|
122
|
-
def
|
123
|
-
|
127
|
+
def html_path
|
128
|
+
root_dir.join("output/#{name}.html")
|
124
129
|
end
|
125
130
|
|
126
131
|
def epub_path
|
127
132
|
root_dir.join("output/#{name}.epub")
|
128
133
|
end
|
134
|
+
|
135
|
+
def tmp_dir
|
136
|
+
root_dir.join("output/tmp")
|
137
|
+
end
|
138
|
+
|
139
|
+
def toc_path
|
140
|
+
tmp_dir.join("toc.html")
|
141
|
+
end
|
129
142
|
end
|
130
143
|
end
|
131
144
|
end
|
data/lib/kitabu/parser/html.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Kitabu
|
2
2
|
module Parser
|
3
|
-
class
|
3
|
+
class HTML < Base
|
4
4
|
# Supported Markdown libraries
|
5
5
|
#
|
6
6
|
MARKDOWN_LIBRARIES = %w[Maruku BlueCloth PEGMarkdown Redcarpet RDiscount]
|
@@ -17,11 +17,20 @@ module Kitabu
|
|
17
17
|
#
|
18
18
|
EXTENSIONS = %w[md mkdn markdown textile html]
|
19
19
|
|
20
|
+
class << self
|
21
|
+
# The footnote index control. We have to manipulate footnotes
|
22
|
+
# because each chapter starts from 1, so we have duplicated references.
|
23
|
+
#
|
24
|
+
attr_accessor :footnote_index
|
25
|
+
end
|
26
|
+
|
20
27
|
# Parse all files and save the parsed content
|
21
28
|
# to <tt>output/book_name.html</tt>.
|
22
29
|
#
|
23
30
|
def parse
|
24
|
-
|
31
|
+
reset_footnote_index!
|
32
|
+
|
33
|
+
File.open(root_dir.join("output/#{name}.html"), "w") do |file|
|
25
34
|
file << parse_layout(content)
|
26
35
|
end
|
27
36
|
true
|
@@ -29,6 +38,10 @@ module Kitabu
|
|
29
38
|
false
|
30
39
|
end
|
31
40
|
|
41
|
+
def reset_footnote_index!
|
42
|
+
self.class.footnote_index = 1
|
43
|
+
end
|
44
|
+
|
32
45
|
# Return all chapters wrapped in a <tt>div.chapter</tt> tag.
|
33
46
|
#
|
34
47
|
def content
|
@@ -44,12 +57,6 @@ module Kitabu
|
|
44
57
|
end
|
45
58
|
end
|
46
59
|
|
47
|
-
# Return the configuration file.
|
48
|
-
#
|
49
|
-
def config
|
50
|
-
Kitabu.config(root_dir)
|
51
|
-
end
|
52
|
-
|
53
60
|
# Return a list of all recognized files.
|
54
61
|
#
|
55
62
|
def entries
|
@@ -96,7 +103,7 @@ module Kitabu
|
|
96
103
|
|
97
104
|
content = Kitabu::Syntax.render(root_dir, file_format, File.read(file), plain_syntax)
|
98
105
|
|
99
|
-
case file_format
|
106
|
+
content = case file_format
|
100
107
|
when :markdown
|
101
108
|
markdown.new(content).to_html
|
102
109
|
when :textile
|
@@ -104,6 +111,40 @@ module Kitabu
|
|
104
111
|
else
|
105
112
|
content
|
106
113
|
end
|
114
|
+
|
115
|
+
render_footnotes(content, plain_syntax)
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_footnotes(content, plain_syntax = false)
|
119
|
+
html = Nokogiri::HTML(content)
|
120
|
+
footnotes = html.css("p[id^='fn']")
|
121
|
+
|
122
|
+
return content if footnotes.empty?
|
123
|
+
|
124
|
+
reset_footnote_index! unless self.class.footnote_index
|
125
|
+
|
126
|
+
footnotes.each do |fn|
|
127
|
+
index = self.class.footnote_index
|
128
|
+
actual_index = fn["id"].gsub(/[^\d]/, "")
|
129
|
+
|
130
|
+
fn.set_attribute("id", "fn#{index}")
|
131
|
+
|
132
|
+
html.css("a[href='#fn#{actual_index}']").each do |link|
|
133
|
+
link.set_attribute("href", "#fn#{index}")
|
134
|
+
end
|
135
|
+
|
136
|
+
html.css("a[href='#fnr#{actual_index}']").each do |link|
|
137
|
+
link.set_attribute("href", "#fnr#{index}")
|
138
|
+
end
|
139
|
+
|
140
|
+
html.css("[id=fnr#{actual_index}]").each do |tag|
|
141
|
+
tag.set_attribute("id", "fnr#{index}")
|
142
|
+
end
|
143
|
+
|
144
|
+
self.class.footnote_index += 1
|
145
|
+
end
|
146
|
+
|
147
|
+
html.css("body").inner_html
|
107
148
|
end
|
108
149
|
|
109
150
|
def format(file)
|
@@ -120,7 +161,7 @@ module Kitabu
|
|
120
161
|
# Parse layout file, making available all configuration entries.
|
121
162
|
#
|
122
163
|
def parse_layout(html)
|
123
|
-
toc =
|
164
|
+
toc = TOC::HTML.generate(html)
|
124
165
|
locals = config.merge({
|
125
166
|
:content => toc.content,
|
126
167
|
:toc => toc.to_html,
|
@@ -138,12 +179,6 @@ module Kitabu
|
|
138
179
|
nil
|
139
180
|
end
|
140
181
|
|
141
|
-
# Render a eRb template using +locals+ as data seed.
|
142
|
-
#
|
143
|
-
def render_template(file, locals = {})
|
144
|
-
ERB.new(File.read(file)).result OpenStruct.new(locals).instance_eval{ binding }
|
145
|
-
end
|
146
|
-
|
147
182
|
# Render all +files+ from a given chapter.
|
148
183
|
#
|
149
184
|
def render_chapter(files, plain_syntax = false)
|
data/lib/kitabu/parser/mobi.rb
CHANGED
data/lib/kitabu/parser/pdf.rb
CHANGED
@@ -1,32 +1,35 @@
|
|
1
1
|
module Kitabu
|
2
2
|
module Parser
|
3
|
-
class
|
3
|
+
class PDF < Base
|
4
4
|
def parse
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
transformed_html_file = File.new('output/prince_friendly_footnote_file.html', 'w')
|
9
|
-
transformed_html_file.write prince_friendly_footnotes
|
5
|
+
apply_footnotes!
|
6
|
+
spawn_command ["prince", with_footnotes_file.to_s, "-o", pdf_file.to_s]
|
7
|
+
end
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
def apply_footnotes!
|
10
|
+
html = Nokogiri::HTML(html_file.read)
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
# https://github.com/tenderlove/nokogiri/issues/339
|
13
|
+
html.css("html").first.tap do |element|
|
14
|
+
next unless element
|
15
|
+
element.delete("xmlns")
|
16
|
+
element.delete("xml:lang")
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
prince_friendly_footnote = <<-FN
|
23
|
-
#{node.text}<span class='fn'><a href='#{node['href']}'>#{node['href']}</a></span>
|
24
|
-
FN
|
19
|
+
html.css("p.footnote[id^='fn']").each do |fn|
|
20
|
+
fn.node_name = "span"
|
21
|
+
fn.set_attribute("class", "fn")
|
25
22
|
|
26
|
-
|
23
|
+
html.css("[href='##{fn["id"]}']").each do |link|
|
24
|
+
link.add_next_sibling(fn)
|
25
|
+
end
|
27
26
|
end
|
28
27
|
|
29
|
-
|
28
|
+
File.open(with_footnotes_file, "w") {|f| f << html.to_xhtml}
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_footnotes_file
|
32
|
+
root_dir.join("output/#{name}.pdf.html")
|
30
33
|
end
|
31
34
|
|
32
35
|
def html_file
|
data/lib/kitabu/parser/txt.rb
CHANGED
@@ -2,11 +2,7 @@ module Kitabu
|
|
2
2
|
module Parser
|
3
3
|
class Txt < Base
|
4
4
|
def parse
|
5
|
-
|
6
|
-
puts command.join(' ')
|
7
|
-
|
8
|
-
Process.wait Process.spawn(*command)
|
9
|
-
true
|
5
|
+
spawn_command ["html2text", "-style", "pretty", "-o", txt_file.to_s, html_file.to_s]
|
10
6
|
end
|
11
7
|
|
12
8
|
def html_file
|
data/lib/kitabu/syntax.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module Kitabu
|
2
2
|
class Syntax
|
3
|
+
autoload :Highlight, "kitabu/syntax/highlight"
|
4
|
+
|
3
5
|
attr_reader :io
|
4
6
|
attr_reader :lines
|
5
7
|
attr_reader :root_dir
|
@@ -65,7 +67,7 @@ module Kitabu
|
|
65
67
|
code = raw.to_s.strip_heredoc
|
66
68
|
code = process_file.gsub(/\n^.*?@(begin|end):.*?$/, "") if meta[:file]
|
67
69
|
|
68
|
-
code =
|
70
|
+
code = Highlight.apply(code, language)
|
69
71
|
|
70
72
|
# escape for textile
|
71
73
|
code = %[<notextile>#{code}</notextile>] if format == :textile
|
@@ -117,8 +119,8 @@ module Kitabu
|
|
117
119
|
#
|
118
120
|
def config
|
119
121
|
Kitabu.config(root_dir)
|
120
|
-
end
|
121
|
-
|
122
|
+
end
|
123
|
+
|
122
124
|
# Return the language used for this syntax block. Overrideable
|
123
125
|
# for epub generation.
|
124
126
|
def language
|