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.
Files changed (74) hide show
  1. data/Gemfile.lock +14 -20
  2. data/README.rdoc +54 -23
  3. data/kitabu.gemspec +1 -1
  4. data/lib/kitabu.rb +11 -4
  5. data/lib/kitabu/cli.rb +49 -6
  6. data/lib/kitabu/dependency.rb +19 -0
  7. data/lib/kitabu/exporter.rb +4 -5
  8. data/lib/kitabu/generator.rb +12 -15
  9. data/lib/kitabu/parser.rb +27 -2
  10. data/lib/kitabu/parser/epub.rb +96 -83
  11. data/lib/kitabu/parser/html.rb +51 -16
  12. data/lib/kitabu/parser/mobi.rb +1 -4
  13. data/lib/kitabu/parser/pdf.rb +23 -20
  14. data/lib/kitabu/parser/txt.rb +1 -5
  15. data/lib/kitabu/syntax.rb +5 -3
  16. data/lib/kitabu/syntax/highlight.rb +22 -0
  17. data/lib/kitabu/toc.rb +4 -78
  18. data/lib/kitabu/toc/epub.rb +41 -0
  19. data/lib/kitabu/toc/html.rb +78 -0
  20. data/lib/kitabu/version.rb +1 -1
  21. data/spec/kitabu/cli/export_spec.rb +2 -2
  22. data/spec/kitabu/cli/new_spec.rb +1 -1
  23. data/spec/kitabu/cli/permalinks_spec.rb +1 -1
  24. data/spec/kitabu/cli/version_spec.rb +1 -1
  25. data/spec/kitabu/extensions/redcloth_spec.rb +6 -6
  26. data/spec/kitabu/extensions/string_spec.rb +1 -1
  27. data/spec/kitabu/parser/epub_spec.rb +5 -1
  28. data/spec/kitabu/parser/html_spec.rb +15 -15
  29. data/spec/kitabu/parser/pdf_spec.rb +4 -4
  30. data/spec/kitabu/syntax_spec.rb +78 -74
  31. data/spec/kitabu/{toc_spec.rb → toc/html_spec.rb} +5 -5
  32. data/spec/spec_helper.rb +3 -1
  33. data/spec/support/mybook/output/mybook.pdf.html +83 -0
  34. data/spec/support/mybook/templates/{cover.erb → epub/cover.erb} +1 -1
  35. data/spec/support/mybook/templates/{epub.erb → epub/page.erb} +1 -1
  36. data/spec/support/mybook/templates/epub/style.css +0 -0
  37. data/spec/support/mybook/templates/html/layout.css +353 -0
  38. data/spec/support/mybook/templates/html/layout.erb +50 -0
  39. data/spec/support/mybook/templates/html/syntax.css +58 -0
  40. data/spec/support/mybook/templates/html/user.css +1 -0
  41. data/spec/support/shared.rb +48 -15
  42. data/templates/cover.erb +1 -1
  43. data/templates/cover.png +0 -0
  44. data/templates/epub.css +1 -0
  45. data/templates/epub.erb +1 -1
  46. data/templates/sample.md +6 -0
  47. data/templates/syntax.css +58 -0
  48. metadata +32 -44
  49. data/spec/support/mybook/templates/epub.css +0 -1
  50. data/spec/support/mybook/templates/layout.css +0 -137
  51. data/spec/support/mybook/templates/layout.erb +0 -46
  52. data/spec/support/mybook/templates/syntax.css +0 -186
  53. data/spec/support/mybook/templates/user.css +0 -1
  54. data/templates/styles/active4d.css +0 -114
  55. data/templates/styles/all_hallows_eve.css +0 -72
  56. data/templates/styles/amy.css +0 -147
  57. data/templates/styles/blackboard.css +0 -88
  58. data/templates/styles/brilliance_black.css +0 -605
  59. data/templates/styles/brilliance_dull.css +0 -599
  60. data/templates/styles/cobalt.css +0 -149
  61. data/templates/styles/dawn.css +0 -121
  62. data/templates/styles/eiffel.css +0 -121
  63. data/templates/styles/espresso_libre.css +0 -109
  64. data/templates/styles/idle.css +0 -62
  65. data/templates/styles/iplastic.css +0 -80
  66. data/templates/styles/lazy.css +0 -73
  67. data/templates/styles/mac_classic.css +0 -123
  68. data/templates/styles/magicwb_amiga.css +0 -104
  69. data/templates/styles/pastels_on_dark.css +0 -188
  70. data/templates/styles/slush_poppies.css +0 -85
  71. data/templates/styles/spacecadet.css +0 -51
  72. data/templates/styles/sunburst.css +0 -180
  73. data/templates/styles/twilight.css +0 -137
  74. data/templates/styles/zenburnesque.css +0 -91
@@ -1,50 +1,23 @@
1
1
  module Kitabu
2
2
  module Parser
3
- class Epub < Html
4
-
5
- module Toc
6
- HEAD = <<-HEAD
7
- <?xml version='1.0' encoding='utf-8' ?>
8
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
9
- <html xml:lang='en' xmlns='http://www.w3.org/1999/xhtml'>
10
- <head>
11
- <meta content='application/xhtml+xml; charset=utf-8' http-equiv='Content-Type' />
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
- attr_accessor :epub
15
+ def epub
16
+ @epub ||= EeePub::Maker.new
17
+ end
43
18
 
44
- def initialize(*args)
45
- super
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
- assets = collect_assets
61
- sections = collect_sections
62
- filenames = collect_filenames(sections)
33
+ write_sections!
34
+ write_toc!
63
35
 
64
- epub.files filenames + assets
65
- epub.nav collect_nav(sections, filenames)
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 collect_assets
77
- [cover_image, File.join(root_dir, "templates", "epub", "style.css")].compact
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 cover_image
81
- path = root_dir.join('images/cover-epub.jpg')
82
- return path if File.exist?(path)
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 chapter(entry)
86
- files = chapter_files(entry)
87
- content = replace_paths(render_chapter(files, true))
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 collect_sections
101
- entries.map { |entry| chapter(entry) }
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 collect_filenames(sections)
105
- index = 0
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
- index += 1
109
-
110
- filename = "tmp/section_#{index}.html"
111
- File.open(filename, 'w') { |file| file.write section[1] }
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 collect_nav(sections, filenames)
117
- [sections, filenames].transpose.map do |section, filename|
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 replace_paths(text)
123
- text.gsub(%r[src="(.*?)"]) {|m| %[src="#{File.basename($1)}"]}
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
@@ -1,6 +1,6 @@
1
1
  module Kitabu
2
2
  module Parser
3
- class Html < Base
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
- File.open(root_dir.join("output/#{name}.html"), "w+") do |file|
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 = Toc.generate(html)
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)
@@ -2,10 +2,7 @@ module Kitabu
2
2
  module Parser
3
3
  class Mobi < Base
4
4
  def parse
5
- command = ["kindlegen", epub_file.to_s,]
6
- puts command
7
-
8
- Process.wait Process.spawn(*command)
5
+ spawn_command ["kindlegen", epub_file.to_s,]
9
6
  true
10
7
  end
11
8
 
@@ -1,32 +1,35 @@
1
1
  module Kitabu
2
2
  module Parser
3
- class Pdf < Base
3
+ class PDF < Base
4
4
  def parse
5
- content = File.read(html_file)
6
- prince_friendly_footnotes = transform_footnotes(content)
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
- command = ["prince", transformed_html_file.path, "-o", pdf_file.to_s]
12
- Process.wait Process.spawn(*command)
9
+ def apply_footnotes!
10
+ html = Nokogiri::HTML(html_file.read)
13
11
 
14
- transformed_html_file.close
15
- File.unlink(transformed_html_file)
16
- true
17
- end
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
- def transform_footnotes(content)
20
- doc = Nokogiri::HTML(content)
21
- doc.css('a').each do |node|
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
- node.replace(prince_friendly_footnote)
23
+ html.css("[href='##{fn["id"]}']").each do |link|
24
+ link.add_next_sibling(fn)
25
+ end
27
26
  end
28
27
 
29
- doc.to_xhtml
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
@@ -2,11 +2,7 @@ module Kitabu
2
2
  module Parser
3
3
  class Txt < Base
4
4
  def parse
5
- command = ["html2text", "-style", "pretty", "-o", txt_file.to_s, html_file.to_s]
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
@@ -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 = Pygments.highlight(code, :lexer => language)
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