burr 0.0.2

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.
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,95 @@
1
+ module Burr
2
+ class PDF < Exporter
3
+
4
+ # Convert original contents into HTML
5
+ #
6
+ def parse_contents
7
+ parsed_items = []
8
+ self.book.items.each do |item|
9
+ self.book.current_item = item
10
+
11
+ self.run_plugins_of_type(:before_parse)
12
+
13
+ unless item['skip']
14
+ item['content'] = Burr::Converter.new(self.book).convert(item['original'])
15
+ end
16
+
17
+ self.run_plugins_of_type(:after_parse)
18
+
19
+ parsed_items << self.book.current_item
20
+ end
21
+ self.book.items = parsed_items
22
+ end
23
+
24
+ # Decorate the contents with template
25
+ #
26
+ def decorate_contents
27
+
28
+ decorated_items = []
29
+
30
+ self.book.items.each do |item|
31
+ self.book.current_item = item
32
+
33
+ self.run_plugins_of_type(:before_decorate)
34
+
35
+ item['content'] = self.book.render(self.book.template_for(item['element']), { 'item' => item, 'toc' => toc_html })
36
+
37
+ self.run_plugins_of_type(:after_decorate)
38
+
39
+ decorated_items << self.book.current_item
40
+ end
41
+
42
+ self.book.items = decorated_items
43
+ end
44
+
45
+ def assemble_book
46
+ base = File.join(self.book.outputs_dir, 'pdf')
47
+ html_path = File.join(base, "#{self.book.slug}.html")
48
+ pdf_path = File.join(base, "#{self.book.slug}-#{Time.new.strftime('%Y%m%d')}.pdf")
49
+
50
+ html = self.book.render(self.book.template_for('book'), {
51
+ 'frontmatter' => self.frontmatter,
52
+ 'bodymatter' => self.bodymatter,
53
+ 'backmatter' => self.backmatter
54
+ })
55
+ File.open(html_path, 'w') { |f| f.puts html }
56
+
57
+ system "prince #{html_path} -o #{pdf_path}"
58
+
59
+ File.unlink html_path
60
+ end
61
+
62
+ private
63
+
64
+ def toc_html
65
+ html = '<ol class="toc-list">'
66
+ self.book.items.each do |item|
67
+ # editions define the *tocable* items
68
+ if self.book.config['formats']['pdf']['toc']['elements'].include?(item['element'])
69
+ # item has several elements in its toc
70
+ if item['toc'].size > 0
71
+ item['toc'].each do |entry|
72
+ if entry['level'] <= self.book.config['formats'][self.book.format]['toc']['deep']
73
+ html << <<-LI1
74
+ <li class="#{ item['matter'] } #{ item['element'] } level-#{ entry['level'] }">
75
+ <a href="\##{ entry['id'] }">#{ entry['label'] } #{ entry['title'] }</a>
76
+ </li>
77
+ LI1
78
+ end
79
+ end
80
+ end
81
+
82
+ # empty or special item (anything different from 'chapter' and 'appendix')
83
+ elsif !%w(cover blank toc).include?(item['element'])
84
+ html << <<-LI2
85
+ <li class="#{ item['matter'] } #{ item['element'] } level-1">
86
+ <a href="\##{ item['id'] }">#{ item['label'] } #{ item['title'] }</a>
87
+ </li>
88
+ LI2
89
+ end
90
+ end
91
+ html << '</ol>'
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,101 @@
1
+ module Burr
2
+ class Site < Exporter
3
+
4
+ # Convert original contents into HTML
5
+ #
6
+ def parse_contents
7
+ parsed_items = []
8
+ self.book.items.each do |item|
9
+ self.book.current_item = item
10
+
11
+ self.run_plugins_of_type(:before_parse)
12
+
13
+ unless item['skip']
14
+ item['content'] = Burr::Converter.new(self.book).convert(item['original'])
15
+ end
16
+
17
+ self.run_plugins_of_type(:after_parse)
18
+
19
+ parsed_items << self.book.current_item
20
+ end
21
+ self.book.items = parsed_items
22
+ end
23
+
24
+ # Decorate the contents with template
25
+ #
26
+ def decorate_contents
27
+ decorated_items = []
28
+ self.book.items.each do |item|
29
+ self.book.current_item = item
30
+ self.run_plugins_of_type(:before_decorate)
31
+ self.run_plugins_of_type(:after_decorate)
32
+ decorated_items << self.book.current_item
33
+ end
34
+ self.book.items = decorated_items
35
+ end
36
+
37
+ def assemble_book
38
+ [nil, *flatten_items(self.book.items), nil].each_cons(3) do |pre, item, nxt|
39
+ item['toc'] = item_toc_html(item['toc'])
40
+
41
+ unless pre.nil?
42
+ item['pre'] = ''
43
+ item['pre'] << "#{pre['label']}" unless pre['label'].blank?
44
+ item['pre'] << "#{pre['title']}"
45
+ item['pre_url'] = get_html_path_of(pre['element'], pre['file'])
46
+ end
47
+
48
+ unless nxt.nil?
49
+ item['nxt'] = ''
50
+ item['nxt'] << "#{nxt['label']}" unless nxt['label'].blank?
51
+ item['nxt'] << "#{nxt['title']}"
52
+ item['nxt_url'] = get_html_path_of(nxt['element'], nxt['file'])
53
+ end
54
+
55
+ File.open(get_html_path_of(item['element'], item['file'], false), 'w') do |f|
56
+ f.puts self.book.render(self.book.template_for(item['element']), { 'item' => item })
57
+ end
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # Deletes item if item['element'] includes in %w(cover toc blank).
64
+ #
65
+ def flatten_items(items)
66
+ items.delete_if { |item| %w(cover toc blank).include?(item['element']) }
67
+ end
68
+
69
+ # Gets toc HTML for a single item.
70
+ #
71
+ def item_toc_html(toc)
72
+ # do not need the first element(level 1) in output
73
+ toc.shift
74
+
75
+ html = '<ol class="toc">'
76
+ toc.each do |entry|
77
+ next if entry['level'] - 1 > self.book.config['formats']['site']['toc']['deep']
78
+ html << <<-LI
79
+ <li class="level-#{ entry['level'] }">
80
+ <a href="\##{ entry['id'] }">#{ entry['label'] } #{ entry['title'] }</a>
81
+ </li>
82
+ LI
83
+ end
84
+ html << '</ol>'
85
+ end
86
+
87
+ def get_html_path_of(element, path, relative = true)
88
+ base = File.join(self.book.outputs_dir, 'site')
89
+ basename = if path.blank?
90
+ element
91
+ else
92
+ path.split('.')[0..-2].join('.')
93
+ end
94
+
95
+ return "/#{ basename }.html" if relative
96
+
97
+ File.join(base, "#{ basename }.html")
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,41 @@
1
+ module Burr
2
+ class Generator < Thor::Group
3
+
4
+ include Thor::Actions
5
+
6
+ def self.source_root
7
+ File.dirname(__FILE__) + '/../../generators'
8
+ end
9
+
10
+ def copy_gemfile
11
+ copy_file 'Gemfile.txt', 'Gemfile'
12
+ end
13
+
14
+ def copy_config_file
15
+ copy_file 'config.yml'
16
+ end
17
+
18
+ def copy_contents
19
+ directory 'contents'
20
+ end
21
+
22
+ def outputs_dir
23
+ empty_directory 'outputs/pdf'
24
+ empty_directory 'outputs/site'
25
+ empty_directory 'outputs/epub'
26
+ empty_directory 'outputs/mobi'
27
+
28
+ empty_directory 'outputs/site/figures'
29
+ end
30
+
31
+ def copy_stylesheets
32
+ copy_file 'stylesheets/pdf.css', 'outputs/pdf/style.css'
33
+ copy_file 'stylesheets/site.css', 'outputs/site/style.css'
34
+ end
35
+
36
+ def caches_dir
37
+ empty_directory 'caches/code'
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,145 @@
1
+ require 'burr/kramdown_ext/parser'
2
+ require 'pygments'
3
+
4
+ module Kramdown
5
+ module Converter
6
+ class Bshtml < Html
7
+
8
+ attr_accessor :book
9
+
10
+ def initialize(root, options)
11
+ super
12
+ @book = options[:register]
13
+ end
14
+
15
+ # Converts paragraph contents image with caption and normal paragraph.
16
+ #
17
+ def convert_p(el, indent)
18
+ if el.children.size == 1 && el.children.first.type == :img && !el.children.first.attr['caption'].nil?
19
+ convert_image_with_caption(el, indent)
20
+ else
21
+ super
22
+ end
23
+ end
24
+
25
+ # Converts the codeblock to HTML, using pygments to highlight.
26
+ #
27
+ def convert_codeblock(el, indent)
28
+ attr = el.attr.dup
29
+ lang = extract_code_language!(attr)
30
+ lang = 'text' if lang.blank?
31
+
32
+ pyg_opts = {
33
+ :encoding => 'utf-8',
34
+ :cssclass => "highlight type-#{lang}"
35
+ }
36
+ caption = attr['caption']
37
+ file = attr['file']
38
+ code = ::Pygments.highlight(el.value, :lexer => lang, :options => pyg_opts).chomp << "\n"
39
+ output = '<div class="codeblock'
40
+ output << ' has-caption' if caption
41
+ output << '">'
42
+ if caption
43
+ caption_el = ::Kramdown::Parser::Bsmarkdown.parse(caption).first
44
+ caption_html = inner(caption_el.children.first, 0)
45
+ output << "<p class=\"caption\">#{caption_html}</p>"
46
+ end
47
+ output << "<p class=\"file\"><code>#{file}</code></p>" if file
48
+ output << "#{code}</div>"
49
+ end
50
+
51
+ # Converts headers
52
+ #
53
+ def convert_header(el, indent)
54
+ attr = el.attr.dup
55
+ item = self.book.current_item
56
+ if @options[:auto_ids] && !attr['id']
57
+ attr['id'] = generate_id(el.options[:raw_text])
58
+ end
59
+ #@toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el)
60
+ unless attr['class'] == 'skip-toc'
61
+ item['toc'] << {
62
+ 'level' => el.options[:level],
63
+ 'title' => el.options[:raw_text]
64
+ }
65
+ end
66
+
67
+ level = output_header_level(el.options[:level])
68
+ format_as_block_html("h#{level}", attr, inner(el, indent), indent)
69
+ end
70
+
71
+ def convert_footnote(el, indent)
72
+ if self.book.format == 'pdf'
73
+ inline = format_as_span_html('span', { 'class'=> 'footnote', 'id' => "fn-#{ el.options[:name] }"}, inner(el.value, 0))
74
+ inline.sub!(/\s*<p>/, '').sub!(/<\/p>\n/, '')
75
+ else
76
+ number = @footnote_counter
77
+ @footnote_counter += 1
78
+ @footnotes << [el.options[:name], el.value]
79
+ "<sup class=\"footnote\" id=\"fnref-#{el.options[:name]}\"><a href=\"#fn-#{el.options[:name]}\" rel=\"footnote\">#{number}</a></sup>"
80
+ end
81
+ end
82
+
83
+ ::Kramdown::Parser::Bsmarkdown::BOXES.each do |box|
84
+ define_method("convert_#{box}_box") { |el, indent| <<-EOF }
85
+ #{' '*indent}<div class="#{box} box">\n#{inner(el, indent)}#{' '*indent}</div>\n
86
+ EOF
87
+ end
88
+
89
+ alias :orin_convert_table :convert_table
90
+
91
+ alias :convert_thead :orin_convert_table
92
+ alias :convert_tbody :orin_convert_table
93
+ alias :convert_tfoot :orin_convert_table
94
+ alias :convert_tr :orin_convert_table
95
+
96
+ def convert_table(el, indent)
97
+ caption = el.attr.delete('caption')
98
+ output = '<div class="table'
99
+ if caption
100
+ caption_el = ::Kramdown::Parser::Bsmarkdown.parse(caption).first
101
+ caption_html = inner(caption_el.children.first, 0)
102
+ output << " has-caption\"><p class=\"caption\">#{caption_html}</p>"
103
+ else
104
+ output << '">'
105
+ end
106
+ output << format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent)
107
+ output << '</div>'
108
+ end
109
+
110
+ # Return a HTML ordered list with the footnote content for the used footnotes.
111
+ def footnote_content
112
+ ol = Element.new(:ol)
113
+ ol.attr['start'] = @footnote_start if @footnote_start != 1
114
+ @footnotes.each do |name, data|
115
+ li = Element.new(:li, nil, {'id' => "fn-#{name}"})
116
+ li.children = Marshal.load(Marshal.dump(data.children))
117
+ ol.children << li
118
+
119
+ ref = Element.new(:raw, "<a href=\"#fnref-#{name}\" rel=\"reference\">&#8617;</a>")
120
+ if li.children.last.type == :p
121
+ para = li.children.last
122
+ else
123
+ li.children << (para = Element.new(:p))
124
+ end
125
+ para.children << ref
126
+ end
127
+ (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0))
128
+ end
129
+
130
+ private
131
+
132
+ def convert_image_with_caption(el, indent)
133
+ img_el = el.children.first
134
+ caption = img_el.attr.delete('caption')
135
+ img_html = convert_img(img_el, indent)
136
+
137
+ caption_el = ::Kramdown::Parser::Bsmarkdown.parse(caption).first
138
+ caption_html = inner(caption_el.children.first, 0)
139
+
140
+ "<div class=\"figure\">#{img_html}<p class=\"caption\">#{caption_html}</p></div>"
141
+ end
142
+
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,38 @@
1
+ module Kramdown
2
+ module Options
3
+
4
+ # Parse the given value +data+ as if it was a value for the option +name+ and return the parsed
5
+ # value with the correct type.
6
+ #
7
+ # If +data+ already has the correct type, it is just returned. Otherwise it is converted to a
8
+ # String and then to the correct type.
9
+ def self.parse(name, data)
10
+ raise ArgumentError, "No option named #{name} defined" if !@options.has_key?(name)
11
+ if !(@options[name].type === data)
12
+ data = data.to_s
13
+ data = if @options[name].type == String
14
+ data
15
+ elsif @options[name].type == Integer
16
+ Integer(data) rescue raise Kramdown::Error, "Invalid integer value for option '#{name}': '#{data}'"
17
+ elsif @options[name].type == Float
18
+ Float(data) rescue raise Kramdown::Error, "Invalid float value for option '#{name}': '#{data}'"
19
+ elsif @options[name].type == Symbol
20
+ data.strip!
21
+ data = data[1..-1] if data[0] == ?:
22
+ (data.empty? || data == 'nil' ? nil : data.to_sym)
23
+ elsif @options[name].type == Boolean
24
+ data.downcase.strip != 'false' && !data.empty?
25
+ else
26
+ data
27
+ end
28
+ end
29
+ data = @options[name].validator[data] if @options[name].validator
30
+ data
31
+ end
32
+
33
+ define(:register, Object, nil, 'Pass in another object.') do |r|
34
+ r
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,65 @@
1
+ module Kramdown
2
+ module Parser
3
+ class Bsmarkdown < Kramdown
4
+
5
+ BOXES = %w(aside discussion error information question tip warning)
6
+
7
+ def initialize(source, options)
8
+ super
9
+ @block_parsers.unshift(:gfm_codeblock_fenced, *BOXES.map{ |b| :"#{b}_box" })
10
+ end
11
+
12
+ GFM_FENCED_CODEBLOCK_START = /^`{3,}/
13
+ GFM_FENCED_CODEBLOCK_MATCH = /^(`{3,})\s*?(\w+)?\s*?\n(.*?)^\1`*\s*?\n/m
14
+
15
+ # Parser the GitHub Flavored Markdown fenced code block.
16
+ #
17
+ # Examples
18
+ #
19
+ # ```ruby
20
+ # def hello
21
+ # puts 'Hello'
22
+ # end
23
+ # ```
24
+ #
25
+ def parse_gfm_codeblock_fenced
26
+ if @src.check(GFM_FENCED_CODEBLOCK_MATCH)
27
+ @src.pos += @src.matched_size
28
+ el = new_block_el(:codeblock, @src[3])
29
+ lang = @src[2].to_s.strip
30
+ el.attr['class'] = "language-#{lang}" unless lang.empty?
31
+ @tree.children << el
32
+ true
33
+ else
34
+ false
35
+ end
36
+ end
37
+ define_parser(:gfm_codeblock_fenced, GFM_FENCED_CODEBLOCK_START)
38
+
39
+ ASIDE_BOX_START = /^#{OPT_SPACE}A> ?/u
40
+ DISCUSSION_BOX_START = /^#{OPT_SPACE}D> ?/
41
+ ERROR_BOX_START = /^#{OPT_SPACE}E> ?/
42
+ INFORMATION_BOX_START = /^#{OPT_SPACE}I> ?/
43
+ QUESTION_BOX_START = /^#{OPT_SPACE}Q> ?/
44
+ TIP_BOX_START = /^#{OPT_SPACE}T> ?/
45
+ WARNING_BOX_START = /^#{OPT_SPACE}W> ?/
46
+
47
+ BOXES.each do |box|
48
+ define_method("parse_#{box}_box") do
49
+ result = @src.scan(PARAGRAPH_MATCH)
50
+ while !@src.match?(self.class::LAZY_END)
51
+ result << @src.scan(PARAGRAPH_MATCH)
52
+ end
53
+ result.gsub!(self.class.const_get("#{box.upcase}_BOX_START"), '')
54
+
55
+ el = new_block_el(:"#{box}_box")
56
+ @tree.children << el
57
+ parse_blocks(el, result)
58
+ true
59
+ end
60
+ define_parser(:"#{box}_box", self.const_get("#{box.upcase}_BOX_START"))
61
+ end
62
+
63
+ end
64
+ end
65
+ end