burr 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.md +30 -0
- data/README.md +180 -0
- data/Rakefile +118 -0
- data/bin/burr +9 -0
- data/burr.gemspec +36 -0
- data/generators/Gemfile.txt +3 -0
- data/generators/config.yml +55 -0
- data/generators/contents/chapter1.md +7 -0
- data/generators/contents/chapter2.md +7 -0
- data/generators/stylesheets/pdf.css +569 -0
- data/generators/stylesheets/site.css +1 -0
- data/lib/burr.rb +56 -0
- data/lib/burr/book.rb +289 -0
- data/lib/burr/cli.rb +64 -0
- data/lib/burr/converter.rb +19 -0
- data/lib/burr/core_ext/blank.rb +107 -0
- data/lib/burr/dependency.rb +28 -0
- data/lib/burr/eeepub_ext/maker.rb +131 -0
- data/lib/burr/exporter.rb +137 -0
- data/lib/burr/exporters/epub.rb +163 -0
- data/lib/burr/exporters/pdf.rb +95 -0
- data/lib/burr/exporters/site.rb +101 -0
- data/lib/burr/generator.rb +41 -0
- data/lib/burr/kramdown_ext/converter.rb +145 -0
- data/lib/burr/kramdown_ext/options.rb +38 -0
- data/lib/burr/kramdown_ext/parser.rb +65 -0
- data/lib/burr/liquid_ext/block.rb +58 -0
- data/lib/burr/liquid_ext/extends.rb +114 -0
- data/lib/burr/plugin.rb +70 -0
- data/lib/burr/plugins/aside.rb +44 -0
- data/lib/burr/plugins/codeblock.rb +42 -0
- data/lib/burr/plugins/figure.rb +62 -0
- data/lib/burr/plugins/link.rb +47 -0
- data/lib/burr/plugins/parser_plugin.rb +18 -0
- data/lib/burr/plugins/table.rb +42 -0
- data/lib/burr/plugins/toc.rb +105 -0
- data/lib/burr/ruby_version_check.rb +4 -0
- data/lib/burr/server.rb +27 -0
- data/lib/burr/ui.rb +46 -0
- data/lib/burr/version.rb +8 -0
- data/resources/locales/labels/en.yml +45 -0
- data/resources/locales/labels/zh_CN.yml +46 -0
- data/resources/locales/titles/en.yml +21 -0
- data/resources/locales/titles/zh_CN.yml +21 -0
- data/resources/templates/epub/_layout.liquid +17 -0
- data/resources/templates/epub/acknowledgement.liquid +10 -0
- data/resources/templates/epub/afterword.liquid +10 -0
- data/resources/templates/epub/appendix.liquid +10 -0
- data/resources/templates/epub/author.liquid +10 -0
- data/resources/templates/epub/chapter.liquid +10 -0
- data/resources/templates/epub/conclusion.liquid +10 -0
- data/resources/templates/epub/cover.liquid +9 -0
- data/resources/templates/epub/dedication.liquid +10 -0
- data/resources/templates/epub/edition.liquid +1 -0
- data/resources/templates/epub/epilogue.liquid +1 -0
- data/resources/templates/epub/foreword.liquid +10 -0
- data/resources/templates/epub/glossary.liquid +1 -0
- data/resources/templates/epub/introduction.liquid +1 -0
- data/resources/templates/epub/license.liquid +1 -0
- data/resources/templates/epub/lof.liquid +24 -0
- data/resources/templates/epub/lot.liquid +24 -0
- data/resources/templates/epub/part.liquid +4 -0
- data/resources/templates/epub/preface.liquid +10 -0
- data/resources/templates/epub/prologue.liquid +1 -0
- data/resources/templates/epub/table.liquid +7 -0
- data/resources/templates/epub/title.liquid +3 -0
- data/resources/templates/epub/toc.liquid +10 -0
- data/resources/templates/pdf/_item.liquid +6 -0
- data/resources/templates/pdf/acknowledgement.liquid +1 -0
- data/resources/templates/pdf/afterword.liquid +1 -0
- data/resources/templates/pdf/appendix.liquid +4 -0
- data/resources/templates/pdf/author.liquid +1 -0
- data/resources/templates/pdf/blank.liquid +3 -0
- data/resources/templates/pdf/book.liquid +42 -0
- data/resources/templates/pdf/chapter.liquid +4 -0
- data/resources/templates/pdf/code.liquid +3 -0
- data/resources/templates/pdf/conclusion.liquid +1 -0
- data/resources/templates/pdf/cover.liquid +6 -0
- data/resources/templates/pdf/dedication.liquid +3 -0
- data/resources/templates/pdf/edition.liquid +1 -0
- data/resources/templates/pdf/epilogue.liquid +1 -0
- data/resources/templates/pdf/foreword.liquid +1 -0
- data/resources/templates/pdf/glossary.liquid +1 -0
- data/resources/templates/pdf/introduction.liquid +1 -0
- data/resources/templates/pdf/license.liquid +1 -0
- data/resources/templates/pdf/lof.liquid +24 -0
- data/resources/templates/pdf/lot.liquid +24 -0
- data/resources/templates/pdf/part.liquid +4 -0
- data/resources/templates/pdf/preface.liquid +1 -0
- data/resources/templates/pdf/prologue.liquid +1 -0
- data/resources/templates/pdf/table.liquid +7 -0
- data/resources/templates/pdf/title.liquid +3 -0
- data/resources/templates/pdf/toc.liquid +4 -0
- data/resources/templates/site/_layout.liquid +27 -0
- data/resources/templates/site/author.liquid +13 -0
- data/resources/templates/site/chapter.liquid +13 -0
- data/resources/templates/site/foreword.liquid +13 -0
- data/resources/templates/site/preface.liquid +13 -0
- metadata +232 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
# steal from https://github.com/silas/liquid-blocks
|
2
|
+
|
3
|
+
module Burr
|
4
|
+
class BlockDrop < ::Liquid::Drop
|
5
|
+
def initialize(block)
|
6
|
+
@block = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def super
|
10
|
+
@block.call_super(@context)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class BlockTag < ::Liquid::Block
|
15
|
+
Syntax = /(\w)+/
|
16
|
+
|
17
|
+
attr_accessor :parent
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
def initialize(tag_name, markup, tokens)
|
21
|
+
if markup =~ Syntax
|
22
|
+
@name = $1
|
23
|
+
else
|
24
|
+
raise Liquid::SyntaxError.new("Syntax Error in 'block' - Valid syntax: block [name]")
|
25
|
+
end
|
26
|
+
|
27
|
+
super if tokens
|
28
|
+
end
|
29
|
+
|
30
|
+
def render(context)
|
31
|
+
context.stack do
|
32
|
+
context['block'] = BlockDrop.new(self)
|
33
|
+
|
34
|
+
render_all(@nodelist, context)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_parent(nodelist)
|
39
|
+
if parent
|
40
|
+
parent.add_parent(nodelist)
|
41
|
+
else
|
42
|
+
self.parent = BlockTag.new(@tag_name, @name, nil)
|
43
|
+
parent.nodelist = nodelist
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def call_super(context)
|
48
|
+
if parent
|
49
|
+
parent.render(context)
|
50
|
+
else
|
51
|
+
''
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Liquid::Template.register_tag(:block, Burr::BlockTag)
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# steal from https://github.com/silas/liquid-blocks
|
2
|
+
|
3
|
+
module Burr
|
4
|
+
class ExtendsTag < ::Liquid::Block
|
5
|
+
Syntax = /(#{Liquid::QuotedFragment}+)/
|
6
|
+
|
7
|
+
def initialize(tag_name, markup, tokens)
|
8
|
+
if markup =~ Syntax
|
9
|
+
@template_name = $1
|
10
|
+
else
|
11
|
+
raise Liquid::SyntaxError.new("Syntax Error in 'extends' - Valid syntax: extends [template]")
|
12
|
+
end
|
13
|
+
|
14
|
+
super
|
15
|
+
|
16
|
+
@blocks = @nodelist.inject({}) do |m, node|
|
17
|
+
m[node.name] = node if node.is_a?(Burr::BlockTag); m
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(tokens)
|
22
|
+
parse_all(tokens)
|
23
|
+
end
|
24
|
+
|
25
|
+
def render(context)
|
26
|
+
template = load_template(context)
|
27
|
+
parent_blocks = find_blocks(template.root)
|
28
|
+
|
29
|
+
@blocks.each do |name, block|
|
30
|
+
if pb = parent_blocks[name]
|
31
|
+
pb.parent = block.parent
|
32
|
+
pb.add_parent(pb.nodelist)
|
33
|
+
pb.nodelist = block.nodelist
|
34
|
+
else
|
35
|
+
if is_extending?(template)
|
36
|
+
template.root.nodelist << block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
template.render(context)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def parse_all(tokens)
|
47
|
+
@nodelist ||= []
|
48
|
+
@nodelist.clear
|
49
|
+
|
50
|
+
while token = tokens.shift
|
51
|
+
case token
|
52
|
+
when /^#{Liquid::TagStart}/
|
53
|
+
if token =~ /^#{Liquid::TagStart}\s*(\w+)\s*(.*)?#{Liquid::TagEnd}$/
|
54
|
+
# fetch the tag from registered blocks
|
55
|
+
if tag = Liquid::Template.tags[$1]
|
56
|
+
@nodelist << tag.new($1, $2, tokens)
|
57
|
+
else
|
58
|
+
# this tag is not registered with the system
|
59
|
+
# pass it to the current block for special handling or error reporting
|
60
|
+
unknown_tag($1, $2, tokens)
|
61
|
+
end
|
62
|
+
else
|
63
|
+
raise Liquid::SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{Liquid::TagEnd.inspect}"
|
64
|
+
end
|
65
|
+
when /^#{Liquid::VariableStart}/
|
66
|
+
@nodelist << create_variable(token)
|
67
|
+
when ''
|
68
|
+
# pass
|
69
|
+
else
|
70
|
+
@nodelist << token
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_template(context)
|
76
|
+
base = File.join('templates', context.registers[:book].format, "_#{ @template_name[1..-2] }.liquid")
|
77
|
+
default = File.join(context.registers[:book].gem_dir, 'resources', base)
|
78
|
+
custom = File.join(context.registers[:book].root_dir, base)
|
79
|
+
|
80
|
+
if File.exist?(custom)
|
81
|
+
path = custom
|
82
|
+
elsif !File.exist?(custom) && File.exist?(default)
|
83
|
+
path = default
|
84
|
+
else
|
85
|
+
raise "#{ @template_name[1..-2] } missing!"
|
86
|
+
end
|
87
|
+
|
88
|
+
Liquid::Template.parse(File.read(path))
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_blocks(node, blocks={})
|
92
|
+
if node.respond_to?(:nodelist) && !node.nodelist.nil?
|
93
|
+
node.nodelist.inject(blocks) do |b, node|
|
94
|
+
if node.is_a?(Burr::BlockTag)
|
95
|
+
b[node.name] = node
|
96
|
+
else
|
97
|
+
find_blocks(node, b)
|
98
|
+
end
|
99
|
+
|
100
|
+
b
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
blocks
|
105
|
+
end
|
106
|
+
|
107
|
+
def is_extending?(template)
|
108
|
+
template.root.nodelist.any? { |node| node.is_a?(ExtendsTag) }
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
Liquid::Template.register_tag(:extends, Burr::ExtendsTag)
|
data/lib/burr/plugin.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
module Burr
|
2
|
+
|
3
|
+
# steal from jekyll: https://github.com/mojombo/jekyll/blob/master/lib/jekyll/plugin.rb
|
4
|
+
class Plugin
|
5
|
+
|
6
|
+
PRIORITIES = {
|
7
|
+
:lowest => -100,
|
8
|
+
:low => -10,
|
9
|
+
:normal => 0,
|
10
|
+
:high => 10,
|
11
|
+
:highest => 100
|
12
|
+
}
|
13
|
+
|
14
|
+
VALIDS = [:before_parse, :after_parse, :before_decorate, :after_decorate]
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
# Install a hook so that subclasses are recorded. This method is only
|
19
|
+
# ever called by Ruby itself.
|
20
|
+
def inherited(base)
|
21
|
+
subclasses << base
|
22
|
+
subclasses.sort!
|
23
|
+
end
|
24
|
+
|
25
|
+
# The list of Classes that have been subclassed.
|
26
|
+
#
|
27
|
+
# @return An array of Class objects.
|
28
|
+
def subclasses
|
29
|
+
@subclasses ||= []
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get or set the priority of this plugin. When called without an
|
33
|
+
# argument it returns the priority. When an argument is given, it will
|
34
|
+
# set the priority.
|
35
|
+
#
|
36
|
+
# @param [Symbol] priority The priority (default: nil). Valid options are:
|
37
|
+
# :lowest, :low, :normal, :high, :highest
|
38
|
+
# @return The Symbol priority.
|
39
|
+
def priority(priority=nil)
|
40
|
+
@priority ||= nil
|
41
|
+
if priority && PRIORITIES.has_key?(priority)
|
42
|
+
@priority = priority
|
43
|
+
end
|
44
|
+
@priority || :normal
|
45
|
+
end
|
46
|
+
|
47
|
+
# Spaceship is priority [higher -> lower]
|
48
|
+
#
|
49
|
+
# other - The class to be compared.
|
50
|
+
#
|
51
|
+
# Returns -1, 0, 1.
|
52
|
+
def <=>(other)
|
53
|
+
PRIORITIES[other.priority] <=> PRIORITIES[self.priority]
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_accessor :book
|
59
|
+
|
60
|
+
# Initialize a new plugin. This should be overridden by the subclass.
|
61
|
+
#
|
62
|
+
# book - The book object.
|
63
|
+
#
|
64
|
+
# Returns a new instance.
|
65
|
+
def initialize(book)
|
66
|
+
@book = book
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Burr
|
2
|
+
class AsidePlugin < Plugin
|
3
|
+
|
4
|
+
# Do something after parsed the item content.
|
5
|
+
def after_parse
|
6
|
+
add_label
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Adds label and id to Aside block.
|
12
|
+
#
|
13
|
+
def add_label
|
14
|
+
item = self.book.current_item
|
15
|
+
return unless self.book.config['formats']["#{self.book.format}"]['label']['elements'].include?('aside')
|
16
|
+
|
17
|
+
counter = 1
|
18
|
+
number = if item['number']
|
19
|
+
item['number']
|
20
|
+
else
|
21
|
+
item['element']
|
22
|
+
end
|
23
|
+
|
24
|
+
dom = ::Nokogiri::HTML::DocumentFragment.parse(item['content'])
|
25
|
+
dom.css('div.aside').each do |aside|
|
26
|
+
# add id
|
27
|
+
aside['id'] = "aside-#{number}-#{counter}"
|
28
|
+
|
29
|
+
# add label
|
30
|
+
caption = aside.css('h4').first
|
31
|
+
unless caption.blank?
|
32
|
+
label = self.book.render_label('aside', { 'item' => { 'number' => number, 'counter' => counter } })
|
33
|
+
caption_html = "<span>#{label}</span>#{caption.inner_html}"
|
34
|
+
caption.children = ::Nokogiri::HTML.fragment(caption_html, 'utf-8')
|
35
|
+
end
|
36
|
+
|
37
|
+
counter += 1
|
38
|
+
end
|
39
|
+
|
40
|
+
item['content'] = dom.to_xhtml
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Burr
|
2
|
+
class CodeblockPlugin < Plugin
|
3
|
+
|
4
|
+
# Do something after parsed the item content.
|
5
|
+
def after_parse
|
6
|
+
add_label
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Adds label and id to code block.
|
12
|
+
#
|
13
|
+
def add_label
|
14
|
+
item = self.book.current_item
|
15
|
+
return unless self.book.config['formats']["#{self.book.format}"]['label']['elements'].include?('codeblock')
|
16
|
+
|
17
|
+
counter = 1
|
18
|
+
number = if item['number']
|
19
|
+
item['number']
|
20
|
+
else
|
21
|
+
item['element']
|
22
|
+
end
|
23
|
+
|
24
|
+
dom = ::Nokogiri::HTML::DocumentFragment.parse(item['content'])
|
25
|
+
dom.css('.codeblock.has-caption').each do |codeblock|
|
26
|
+
# add id
|
27
|
+
codeblock.set_attribute('id', "codeblock-#{number}-#{counter}")
|
28
|
+
|
29
|
+
# add label
|
30
|
+
caption = codeblock.css('.caption').first
|
31
|
+
label = self.book.render_label('codeblock', { 'item' => { 'number' => number, 'counter' => counter } })
|
32
|
+
caption_html = "<span>#{label}</span>#{caption.inner_html}"
|
33
|
+
caption.children = ::Nokogiri::HTML.fragment(caption_html, 'utf-8')
|
34
|
+
|
35
|
+
counter += 1
|
36
|
+
end
|
37
|
+
|
38
|
+
item['content'] = dom.to_xhtml
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Burr
|
2
|
+
class FigurePlugin < Plugin
|
3
|
+
|
4
|
+
# Do something after parsed the item content.
|
5
|
+
def after_parse
|
6
|
+
fix_src_for_pdf if self.book.format == 'pdf'
|
7
|
+
|
8
|
+
add_label
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
# Replaces figure src for pdf.
|
14
|
+
#
|
15
|
+
# 'figures/sample.png' => '<root_dir>/figures/sample.png'
|
16
|
+
#
|
17
|
+
def fix_src_for_pdf
|
18
|
+
item = self.book.current_item
|
19
|
+
prefix = File.join(self.book.outputs_dir, 'site')
|
20
|
+
dom = ::Nokogiri::HTML::DocumentFragment.parse(item['content'])
|
21
|
+
dom.search('img').each do |img|
|
22
|
+
ori_src = img.get_attribute('src')
|
23
|
+
new_src = File.join(prefix, ori_src)
|
24
|
+
img.set_attribute('src', new_src)
|
25
|
+
end
|
26
|
+
item['content'] = dom.to_xhtml
|
27
|
+
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Adds label and id to figure block.
|
32
|
+
#
|
33
|
+
def add_label
|
34
|
+
item = self.book.current_item
|
35
|
+
return unless self.book.config['formats']["#{self.book.format}"]['label']['elements'].include?('figure')
|
36
|
+
|
37
|
+
counter = 1
|
38
|
+
number = if item['number']
|
39
|
+
item['number']
|
40
|
+
else
|
41
|
+
item['element']
|
42
|
+
end
|
43
|
+
|
44
|
+
dom = ::Nokogiri::HTML::DocumentFragment.parse(item['content'])
|
45
|
+
dom.css('div.figure').each do |figure|
|
46
|
+
# add id
|
47
|
+
figure.set_attribute('id', "figure-#{number}-#{counter}")
|
48
|
+
|
49
|
+
# add label
|
50
|
+
caption = figure.css('.caption').first
|
51
|
+
label = self.book.render_label('figure', { 'item' => { 'number' => number, 'counter' => counter } })
|
52
|
+
caption_html = "<span>#{label}</span>#{caption.inner_html}"
|
53
|
+
caption.children = ::Nokogiri::HTML.fragment(caption_html, 'utf-8')
|
54
|
+
|
55
|
+
counter += 1
|
56
|
+
end
|
57
|
+
|
58
|
+
item['content'] = dom.to_xhtml
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Burr
|
2
|
+
class LinkPlugin < Plugin
|
3
|
+
|
4
|
+
# Do something after parsed the item content.
|
5
|
+
def after_parse
|
6
|
+
extend_url if %w(site epub).include?(self.book.format)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Extends cross link url.
|
12
|
+
#
|
13
|
+
def extend_url
|
14
|
+
item = self.book.current_item
|
15
|
+
dom = ::Nokogiri::HTML::DocumentFragment.parse(item['content'])
|
16
|
+
dom.css('a[href]').each do |link|
|
17
|
+
href = link['href']
|
18
|
+
next unless cross_link?(href)
|
19
|
+
|
20
|
+
parts = href.split('-')
|
21
|
+
|
22
|
+
next if parts[0].start_with?('#fn')
|
23
|
+
|
24
|
+
if href.start_with?('#chapter')
|
25
|
+
new_href = "chapter#{parts[1]}.html"
|
26
|
+
elsif href.start_with?('#section', '#figure', '#codeblock', '#table', '#aside')
|
27
|
+
new_href = "chapter#{parts[1]}.html#{parts.join('-')}"
|
28
|
+
else
|
29
|
+
new_href = "#{href[1..-1]}.html"
|
30
|
+
end
|
31
|
+
|
32
|
+
link['href'] = new_href
|
33
|
+
end
|
34
|
+
|
35
|
+
item['content'] = dom.to_xhtml
|
36
|
+
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def cross_link?(url)
|
41
|
+
return true if url.start_with?('#')
|
42
|
+
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Burr
|
2
|
+
class ParserPlugin < Plugin
|
3
|
+
|
4
|
+
# Do something after parsed the item content.
|
5
|
+
def after_parse
|
6
|
+
#replace_br
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
# Replace <br> by <br/> (it causes problems for epub books)
|
12
|
+
def replace_br
|
13
|
+
item = self.book.current_item
|
14
|
+
item['content'].gsub!('<br>', '<br />')
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|