burr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +3 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE.md +30 -0
  6. data/README.md +180 -0
  7. data/Rakefile +118 -0
  8. data/bin/burr +9 -0
  9. data/burr.gemspec +36 -0
  10. data/generators/Gemfile.txt +3 -0
  11. data/generators/config.yml +55 -0
  12. data/generators/contents/chapter1.md +7 -0
  13. data/generators/contents/chapter2.md +7 -0
  14. data/generators/stylesheets/pdf.css +569 -0
  15. data/generators/stylesheets/site.css +1 -0
  16. data/lib/burr.rb +56 -0
  17. data/lib/burr/book.rb +289 -0
  18. data/lib/burr/cli.rb +64 -0
  19. data/lib/burr/converter.rb +19 -0
  20. data/lib/burr/core_ext/blank.rb +107 -0
  21. data/lib/burr/dependency.rb +28 -0
  22. data/lib/burr/eeepub_ext/maker.rb +131 -0
  23. data/lib/burr/exporter.rb +137 -0
  24. data/lib/burr/exporters/epub.rb +163 -0
  25. data/lib/burr/exporters/pdf.rb +95 -0
  26. data/lib/burr/exporters/site.rb +101 -0
  27. data/lib/burr/generator.rb +41 -0
  28. data/lib/burr/kramdown_ext/converter.rb +145 -0
  29. data/lib/burr/kramdown_ext/options.rb +38 -0
  30. data/lib/burr/kramdown_ext/parser.rb +65 -0
  31. data/lib/burr/liquid_ext/block.rb +58 -0
  32. data/lib/burr/liquid_ext/extends.rb +114 -0
  33. data/lib/burr/plugin.rb +70 -0
  34. data/lib/burr/plugins/aside.rb +44 -0
  35. data/lib/burr/plugins/codeblock.rb +42 -0
  36. data/lib/burr/plugins/figure.rb +62 -0
  37. data/lib/burr/plugins/link.rb +47 -0
  38. data/lib/burr/plugins/parser_plugin.rb +18 -0
  39. data/lib/burr/plugins/table.rb +42 -0
  40. data/lib/burr/plugins/toc.rb +105 -0
  41. data/lib/burr/ruby_version_check.rb +4 -0
  42. data/lib/burr/server.rb +27 -0
  43. data/lib/burr/ui.rb +46 -0
  44. data/lib/burr/version.rb +8 -0
  45. data/resources/locales/labels/en.yml +45 -0
  46. data/resources/locales/labels/zh_CN.yml +46 -0
  47. data/resources/locales/titles/en.yml +21 -0
  48. data/resources/locales/titles/zh_CN.yml +21 -0
  49. data/resources/templates/epub/_layout.liquid +17 -0
  50. data/resources/templates/epub/acknowledgement.liquid +10 -0
  51. data/resources/templates/epub/afterword.liquid +10 -0
  52. data/resources/templates/epub/appendix.liquid +10 -0
  53. data/resources/templates/epub/author.liquid +10 -0
  54. data/resources/templates/epub/chapter.liquid +10 -0
  55. data/resources/templates/epub/conclusion.liquid +10 -0
  56. data/resources/templates/epub/cover.liquid +9 -0
  57. data/resources/templates/epub/dedication.liquid +10 -0
  58. data/resources/templates/epub/edition.liquid +1 -0
  59. data/resources/templates/epub/epilogue.liquid +1 -0
  60. data/resources/templates/epub/foreword.liquid +10 -0
  61. data/resources/templates/epub/glossary.liquid +1 -0
  62. data/resources/templates/epub/introduction.liquid +1 -0
  63. data/resources/templates/epub/license.liquid +1 -0
  64. data/resources/templates/epub/lof.liquid +24 -0
  65. data/resources/templates/epub/lot.liquid +24 -0
  66. data/resources/templates/epub/part.liquid +4 -0
  67. data/resources/templates/epub/preface.liquid +10 -0
  68. data/resources/templates/epub/prologue.liquid +1 -0
  69. data/resources/templates/epub/table.liquid +7 -0
  70. data/resources/templates/epub/title.liquid +3 -0
  71. data/resources/templates/epub/toc.liquid +10 -0
  72. data/resources/templates/pdf/_item.liquid +6 -0
  73. data/resources/templates/pdf/acknowledgement.liquid +1 -0
  74. data/resources/templates/pdf/afterword.liquid +1 -0
  75. data/resources/templates/pdf/appendix.liquid +4 -0
  76. data/resources/templates/pdf/author.liquid +1 -0
  77. data/resources/templates/pdf/blank.liquid +3 -0
  78. data/resources/templates/pdf/book.liquid +42 -0
  79. data/resources/templates/pdf/chapter.liquid +4 -0
  80. data/resources/templates/pdf/code.liquid +3 -0
  81. data/resources/templates/pdf/conclusion.liquid +1 -0
  82. data/resources/templates/pdf/cover.liquid +6 -0
  83. data/resources/templates/pdf/dedication.liquid +3 -0
  84. data/resources/templates/pdf/edition.liquid +1 -0
  85. data/resources/templates/pdf/epilogue.liquid +1 -0
  86. data/resources/templates/pdf/foreword.liquid +1 -0
  87. data/resources/templates/pdf/glossary.liquid +1 -0
  88. data/resources/templates/pdf/introduction.liquid +1 -0
  89. data/resources/templates/pdf/license.liquid +1 -0
  90. data/resources/templates/pdf/lof.liquid +24 -0
  91. data/resources/templates/pdf/lot.liquid +24 -0
  92. data/resources/templates/pdf/part.liquid +4 -0
  93. data/resources/templates/pdf/preface.liquid +1 -0
  94. data/resources/templates/pdf/prologue.liquid +1 -0
  95. data/resources/templates/pdf/table.liquid +7 -0
  96. data/resources/templates/pdf/title.liquid +3 -0
  97. data/resources/templates/pdf/toc.liquid +4 -0
  98. data/resources/templates/site/_layout.liquid +27 -0
  99. data/resources/templates/site/author.liquid +13 -0
  100. data/resources/templates/site/chapter.liquid +13 -0
  101. data/resources/templates/site/foreword.liquid +13 -0
  102. data/resources/templates/site/preface.liquid +13 -0
  103. 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)
@@ -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