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.
- 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,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\">↩</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
|