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.
@@ -0,0 +1,14 @@
1
+ module Bookshelf
2
+ module Parser
3
+ class Mobi < Base
4
+ def parse
5
+ spawn_command ["kindlegen", epub_file.to_s,]
6
+ true
7
+ end
8
+
9
+ def epub_file
10
+ root_dir.join("output/#{name}.epub")
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ module Bookshelf
2
+ module Parser
3
+ class PDF < Base
4
+ def parse
5
+ apply_footnotes!
6
+ spawn_command ["prince", with_footnotes_file.to_s, "-o", pdf_file.to_s]
7
+ end
8
+
9
+ def apply_footnotes!
10
+ html = Nokogiri::HTML(html_file.read)
11
+
12
+ # https://github.com/sparklemotion/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
+
19
+ html.css("p.footnote[id^='_fn']").each do |fn|
20
+ fn.node_name = "span"
21
+ fn.set_attribute("class", "fn")
22
+
23
+ html.css("[href='##{fn["id"]}']").each do |link|
24
+ link.add_next_sibling(fn)
25
+ end
26
+ end
27
+
28
+ File.open(with_footnotes_file, "w") {|f| f << html.to_xhtml}
29
+ end
30
+
31
+ def with_footnotes_file
32
+ Bookshelf.root_dir.join("output/#{name}.pdf.html")
33
+ end
34
+
35
+ def html_file
36
+ Bookshelf.root_dir.join("output/#{name}.html")
37
+ end
38
+
39
+ def pdf_file
40
+ Bookshelf.root_dir.join("output/#{name}.pdf")
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Bookshelf
2
+ module Parser
3
+ class Txt < Base
4
+ def parse
5
+ spawn_command ["html2text", "-style", "pretty", "-nobs", "-o", txt_file.to_s, html_file.to_s]
6
+ end
7
+
8
+ def html_file
9
+ root_dir.join("output/#{name}.html")
10
+ end
11
+
12
+ def txt_file
13
+ root_dir.join("output/#{name}.txt")
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,45 @@
1
+ module Bookshelf
2
+ class Stats
3
+ attr_reader :root_dir
4
+
5
+ def initialize(root_dir)
6
+ @root_dir = root_dir
7
+ end
8
+
9
+ def text
10
+ @text ||= html.text
11
+ end
12
+
13
+ def html
14
+ @html ||= Nokogiri::HTML(content)
15
+ end
16
+
17
+ def words
18
+ @words ||= text.split(" ").size
19
+ end
20
+
21
+ def chapters
22
+ @chapters ||= html.css(".chapter").size
23
+ end
24
+
25
+ def images
26
+ @images ||= html.css("img").size
27
+ end
28
+
29
+ def footnotes
30
+ @footnotes ||= html.css("p.footnote").size
31
+ end
32
+
33
+ def links
34
+ @links ||= html.css("[href^='http']").size
35
+ end
36
+
37
+ def code_blocks
38
+ @code_blocks ||= html.css("pre").size
39
+ end
40
+
41
+ def content
42
+ @content ||= Parser::HTML.new(root_dir).content
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Bookshelf
2
+ class Stream
3
+ attr_accessor :listener, :content
4
+ attr_reader :html
5
+
6
+ def initialize(content, listener)
7
+ @content = content
8
+ @listener = listener
9
+ @html = Nokogiri::HTML.parse(content)
10
+ end
11
+
12
+ def parse
13
+ traverse(html)
14
+ end
15
+
16
+ def traverse(node)
17
+ node.children.each do |child|
18
+ emit(child)
19
+ traverse(child)
20
+ end
21
+ end
22
+
23
+ def emit(node)
24
+ listener.send(:tag, node) if node.name =~ /h[1-6]/
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,124 @@
1
+ module Bookshelf
2
+ class Syntax
3
+ autoload :Highlight, "bookshelf/syntax/highlight"
4
+
5
+ attr_reader :io
6
+ attr_reader :lines
7
+ attr_reader :book_dir
8
+ attr_reader :format
9
+
10
+ # Render syntax blocks from specified source code.
11
+ #
12
+ # dir = Pathname.new(File.dirname(__FILE__))
13
+ # text = File.read(dir.join("text/some_file.textile"))
14
+ # Bookshelf::Syntax.render(dir, :textile, text)
15
+ #
16
+ def self.render(book_dir, format, source_code, raw = false)
17
+ source_code.gsub(/@@@(.*?)@@@/m) do |match|
18
+ new(book_dir, format, $1, raw).process
19
+ end
20
+ end
21
+
22
+ # Process each syntax block individually.
23
+ #
24
+ def initialize(book_dir, format, code, raw = false)
25
+ @format = format
26
+ @book_dir = book_dir
27
+ @io = StringIO.new(code)
28
+ @lines = io.readlines.collect(&:chomp)
29
+ @language = 'text' if raw
30
+ end
31
+
32
+ # Return unprocessed line codes.
33
+ #
34
+ def raw
35
+ lines[1..-1].join("\n")
36
+ end
37
+
38
+ # Return meta data from syntax annotation.
39
+ #
40
+ def meta
41
+ @meta ||= begin
42
+ line = lines.first.squish
43
+ _, language, file, modifier, reference = *line.match(/^([^ ]+)(?: ([^:#]+)(?:(:|#)(.*?))?)?$/)
44
+
45
+ if modifier == "#"
46
+ type = :block
47
+ elsif modifier == ":"
48
+ type = :range
49
+ elsif file
50
+ type = :file
51
+ else
52
+ type = :inline
53
+ end
54
+
55
+ {
56
+ :language => language,
57
+ :file => file,
58
+ :type => type,
59
+ :reference => reference
60
+ }
61
+ end
62
+ end
63
+
64
+ # Process syntax block, returning a +pre+ HTML tag.
65
+ #
66
+ def process
67
+ code = raw.to_s.strip_heredoc
68
+ code = process_file.gsub(/\n^.*?@(begin|end):.*?$/, "") if meta[:file]
69
+
70
+ code = Highlight.apply(code, language)
71
+
72
+ # escape for textile
73
+ code = %[<notextile>#{code}</notextile>] if format == :textile
74
+ code
75
+ end
76
+
77
+ private
78
+ # Process line range as in <tt>@@@ ruby some_file.rb:15,20 @@@</tt>.
79
+ #
80
+ def process_range(code)
81
+ starts, ends = meta[:reference].split(",").collect(&:to_i)
82
+ code = StringIO.new(code).readlines[starts-1..ends-1].join("\n").strip_heredoc.chomp
83
+ end
84
+
85
+ # Process block name as in <tt>@@@ ruby some_file.rb#some_block @@@</tt>.
86
+ #
87
+ def process_block(code)
88
+ code.gsub!(/\r\n/, "\n")
89
+ re = %r[@begin: *\b(#{meta[:reference]})\b *[^\n]*\n(.*?)\n[^\n]*@end: \1]im
90
+
91
+ if code.match(re)
92
+ $2.strip_heredoc
93
+ else
94
+ "[missing '#{meta[:reference]}' block name]"
95
+ end
96
+ end
97
+
98
+ # Process file and its relatives.
99
+ #
100
+ def process_file
101
+ file_path = book_dir.join("code/#{meta[:file]}")
102
+
103
+ if File.exist?(file_path)
104
+ code = File.read(file_path)
105
+
106
+ if meta[:type] == :range
107
+ process_range(code)
108
+ elsif meta[:type] == :block
109
+ process_block(code)
110
+ else
111
+ code
112
+ end
113
+ else
114
+ "[missing 'code/#{meta[:file]}' file]"
115
+ end
116
+ end
117
+
118
+ # Return the language used for this syntax block. Overrideable
119
+ # for epub generation.
120
+ def language
121
+ @language || meta[:language]
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,6 @@
1
+ module Bookshelf
2
+ module TOC
3
+ autoload :HTML, "bookshelf/toc/html"
4
+ autoload :Epub, "bookshelf/toc/epub"
5
+ end
6
+ end
@@ -0,0 +1,41 @@
1
+ module Bookshelf
2
+ module TOC
3
+ class Epub
4
+ attr_accessor :navigation
5
+
6
+ def initialize(navigation)
7
+ @navigation = navigation
8
+ end
9
+
10
+ def to_html
11
+ ERB.new(template).result OpenStruct.new(:navigation => navigation).instance_eval{ binding }
12
+ end
13
+
14
+ def template
15
+ <<-HTML.strip_heredoc.force_encoding("utf-8")
16
+ <?xml version="1.0" encoding="utf-8" ?>
17
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
18
+ <html xml:lang="en" xmlns="http://www.w3.org/1999/xhtml">
19
+ <head>
20
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
21
+ <link rel="stylesheet" type="text/css" href="epub.css"/>
22
+ <meta http-equiv="Content-Type" content="application/xhtml+xml; charset=utf-8" />
23
+ <title>Table of Contents</title>
24
+ </head>
25
+ <body>
26
+ <div id="toc">
27
+ <ul>
28
+ <% navigation.each do |nav| %>
29
+ <li>
30
+ <a href="<%= nav[:content] %>"><%= nav[:label] %></a>
31
+ </li>
32
+ <% end %>
33
+ </ul>
34
+ </div>
35
+ </body>
36
+ </html>
37
+ HTML
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,78 @@
1
+ module Bookshelf
2
+ module TOC
3
+ class HTML
4
+ # Return the table of contents in hash format.
5
+ #
6
+ attr_reader :toc
7
+
8
+ private_class_method :new
9
+ attr_reader :buffer # :nodoc:
10
+ attr_reader :attrs # :nodoc:
11
+ attr_accessor :content # :nodoc:
12
+
13
+ # Traverse every title and add a +id+ attribute.
14
+ # Return the modified content.
15
+ #
16
+ def self.normalize(content)
17
+ counter = {}
18
+ html = Nokogiri::HTML.parse(content)
19
+ html.search("h1, h2, h3, h4, h5, h6").each do |tag|
20
+ title = tag.inner_text
21
+ permalink = title.to_permalink
22
+
23
+ counter[permalink] ||= 0
24
+ counter[permalink] += 1
25
+
26
+ permalink = "#{permalink}-#{counter[permalink]}" if counter[permalink] > 1
27
+
28
+ tag.set_attribute("id", permalink)
29
+ end
30
+
31
+ html.css("body").to_xhtml.gsub(/<body>(.*?)<\/body>/m, "\\1")
32
+ end
33
+
34
+ # Traverse every title normalizing its content as a permalink.
35
+ #
36
+ def self.generate(content)
37
+ content = normalize(content)
38
+ listener = new
39
+ listener.content = content
40
+ Stream.new(content, listener).parse
41
+ listener
42
+ end
43
+
44
+ def initialize # :nodoc:
45
+ @toc = []
46
+ @counters = {}
47
+ end
48
+
49
+ def tag(node) # :nodoc:
50
+ toc << {
51
+ :level => node.name.gsub(/[^\d]/, "").to_i,
52
+ :text => node.text,
53
+ :permalink => node["id"]
54
+ }
55
+ end
56
+
57
+ # Return a hash with all normalized attributes.
58
+ #
59
+ def to_hash
60
+ {
61
+ :content => content,
62
+ :html => to_html,
63
+ :toc => toc
64
+ }
65
+ end
66
+
67
+ # Return the table of contents in HTML format.
68
+ #
69
+ def to_html
70
+ String.new.tap do |html|
71
+ toc.each do |options|
72
+ html << %[<div class="level#{options[:level]} #{options[:permalink]}"><a href="##{options[:permalink]}"><span>#{CGI.escape_html(options[:text])}</span></a></div>]
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,8 @@
1
+ module Bookshelf
2
+ module Version
3
+ MAJOR = 1
4
+ MINOR = 0
5
+ PATCH = 0
6
+ STRING = "#{MAJOR}.#{MINOR}.#{PATCH}"
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ notification :off
2
+ interactor :off
3
+
4
+ guard :shell do
5
+ watch %r[^(?!output|templates/scss)] do |m|
6
+ `bookshelf export`
7
+ end
8
+
9
+ watch %r[^templates/scss] do |m|
10
+ `sass --unix-newlines templates/scss:templates`
11
+ end
12
+ end
@@ -0,0 +1,44 @@
1
+ # The book's title. Will be used everywhere!
2
+ title: "[Your Book Title]"
3
+
4
+ # The book's language.
5
+ language: en
6
+
7
+ # Your book copyright info.
8
+ # Here's some examples:
9
+ #
10
+ # Copyright <%= @year %> by <%= @name %>.
11
+ # Copyright <%= @year %> by <%= @name %>. This work is licensed under MIT License.
12
+ # This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.
13
+ # This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported License.
14
+ # This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
15
+ #
16
+ copyright: "Copyright (C) <%= @year %> <%= @name %>."
17
+
18
+ # Who's publishing this book.
19
+ publisher: "<%= @name %>"
20
+
21
+ # When this book was published.
22
+ published_at: "<%= Date.today %>"
23
+
24
+ # Some book description.
25
+ subject: "[Your book description]"
26
+
27
+ # Some keywords that identify this book.
28
+ keywords: "[Your book keywords (comma-separated)]"
29
+
30
+ # Some unique identification. Works great with your domain
31
+ # like `http://yourbook.example.com`.
32
+ uid: "<%= @uid %>"
33
+
34
+ # Your book identification like ISBN or ISSN.
35
+ identifier:
36
+ id: "http://yourbook.example.com"
37
+ type: "URL" # can be ISBN, ISSN or URL
38
+
39
+ # This book authors.
40
+ authors:
41
+ - "<%= @name %>"
42
+
43
+ # The base URL from your source code.
44
+ base_url: http://example.com