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,28 @@
1
+ module Burr
2
+ module Dependency
3
+
4
+ # Checks if PrinceXML installed.
5
+ #
6
+ # Returns true if installed, otherwise false.
7
+ def self.prince_installed?
8
+ installed? 'prince'
9
+ end
10
+
11
+ def self.kindlegen_installed?
12
+ installed? 'kindlegen'
13
+ end
14
+
15
+ # Checks if Dependent libx installed.
16
+ #
17
+ # Returns true if installed, otherwise false.
18
+ def self.installed?(cmd)
19
+ return true if which(cmd)
20
+ false
21
+ end
22
+
23
+ # Finds the executable.
24
+ def self.which(cmd)
25
+ system "which #{cmd} > /dev/null 2>&1"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,131 @@
1
+ # Custom the eeepub maker.
2
+
3
+ require 'tmpdir'
4
+ require 'fileutils'
5
+
6
+ module Burr
7
+ class EpubMaker
8
+ [
9
+ :title,
10
+ :creator,
11
+ :publisher,
12
+ :date,
13
+ :language,
14
+ :subject,
15
+ :description,
16
+ :rights,
17
+ :relation
18
+ ].each do |name|
19
+ class_eval <<-DELIM
20
+ def #{name}(value)
21
+ @#{name}s ||= []
22
+ @#{name}s << value
23
+ end
24
+ DELIM
25
+ end
26
+
27
+ [
28
+ :uid,
29
+ :files,
30
+ :nav,
31
+ :cover,
32
+ :ncx_file,
33
+ :opf_file,
34
+ :guide
35
+ ].each do |name|
36
+ define_method(name) do |arg|
37
+ instance_variable_set("@#{name}", arg)
38
+ end
39
+ end
40
+
41
+ def identifier(id, options)
42
+ @identifiers ||= []
43
+ @identifiers << {:value => id, :scheme => options[:scheme], :id => options[:id]}
44
+ end
45
+
46
+ # @param [Proc] block the block for initialize
47
+ def initialize(&block)
48
+ @files ||= []
49
+ @nav ||= []
50
+ @ncx_file ||= 'toc.ncx'
51
+ @opf_file ||= 'content.opf'
52
+
53
+ instance_eval(&block) if block_given?
54
+ end
55
+
56
+ # Save as ePub file
57
+ #
58
+ # @param [String] filename the ePub file name to save
59
+ def save(filename)
60
+ create_epub.save(filename)
61
+ end
62
+
63
+ # instead of saving to file, output the file contents.
64
+ # important for serving on-the-fly doc creation from
65
+ # web interface where we don't want to allow file system
66
+ # writes (Heroku, et al.)
67
+ def render
68
+ create_epub.render
69
+ end
70
+
71
+ private
72
+
73
+ def create_epub
74
+ @uid ||= 'BookId'
75
+ unique_identifier = @identifiers.select{ |i| i[:id] == @uid }.first
76
+ unless unique_identifier
77
+ unique_identifier = @identifiers.first
78
+ unique_identifier[:id] = @uid
79
+ end
80
+ dir = Dir.mktmpdir
81
+ @files.each do |file|
82
+ case file
83
+ when String
84
+ FileUtils.cp(file, dir)
85
+ when Hash
86
+ file_path, dir_path = *file.first
87
+ dest_dir = File.join(dir, dir_path)
88
+ FileUtils.mkdir_p(dest_dir)
89
+ FileUtils.cp(file_path, dest_dir)
90
+ end
91
+ end
92
+
93
+ ::EeePub::NCX.new(
94
+ :uid => @identifiers.select{ |i| i[:id] == @uid }.first,
95
+ :title => @titles[0],
96
+ :nav => @nav
97
+ ).save(File.join(dir, @ncx_file))
98
+
99
+ ::EeePub::OPF.new(
100
+ :title => @titles,
101
+ :unique_identifier => @uid,
102
+ :identifier => @identifiers,
103
+ :creator => @creators,
104
+ :publisher => @publishers,
105
+ :date => @dates,
106
+ :language => @languages,
107
+ :subject => @subjects,
108
+ :description => @descriptions,
109
+ :rights => @rightss,
110
+ :cover => @cover,
111
+ :relation => @relations,
112
+ :manifest => @files.map{|file|
113
+ case file
114
+ when String
115
+ File.basename(file)
116
+ when Hash
117
+ file_path, dir_path = *file.first
118
+ File.join(dir_path, File.basename(file_path))
119
+ end
120
+ },
121
+ :ncx => @ncx_file,
122
+ :guide => @guide
123
+ ).save(File.join(dir, @opf_file))
124
+
125
+ ::EeePub::OCF.new(
126
+ :dir => dir,
127
+ :container => @opf_file
128
+ )
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,137 @@
1
+ module Burr
2
+ class Exporter
3
+
4
+ FRONTMATTER = %w(acknowledgement author cover dedication edition foreword
5
+ introduction license preface prologue title toc)
6
+ BODYMATTER = %w(appendix blank chapter conclusion part)
7
+ BACKMATTER = %w(afterword epilogue glossary lof lot)
8
+
9
+ attr_accessor :book, :config
10
+ attr_accessor :frontmatter, :bodymatter, :backmatter
11
+
12
+ def initialize(book)
13
+ @book = book
14
+ @config = book.config
15
+
16
+ @frontmatter = []
17
+ @bodymatter = []
18
+ @backmatter = []
19
+
20
+ prepare_output_dir
21
+ end
22
+
23
+ # Run all hooks of a type.
24
+ #
25
+ # type - The plugin name in Symbol, valid options are: :before_parse, :after_parse
26
+ # :before_decorate, :after_decorate.
27
+ #
28
+ # Returns nothing.
29
+ def run_plugins_of_type(type)
30
+ type = type.to_sym
31
+
32
+ Burr::Plugin.subclasses.each do |k|
33
+ k_obj = k.new(self.book)
34
+ Burr::Plugin::VALIDS.each do |h|
35
+ k_obj.send(type) if k_obj.respond_to?(h) && h == type
36
+ end
37
+ end
38
+
39
+ nil
40
+ end
41
+
42
+ # Run exporter.
43
+ #
44
+ # The details should implement in subclass.
45
+ def run
46
+ self.load_contents
47
+ self.parse_contents
48
+ self.decorate_contents
49
+ self.assemble_book
50
+ end
51
+
52
+ # Load book contents
53
+ def load_contents
54
+ special_elements = %w(cover toc blank)
55
+
56
+ self.config['contents'].each do |content_config|
57
+ item = initialize_item(content_config)
58
+
59
+ # if the element defines its own content file (usually: `chapter`, `appendix`)
60
+ if !item['file'].blank?
61
+ content_file = File.join(self.book.contents_dir, item['file'])
62
+
63
+ # check that content file exists and is readable
64
+ if !File.readable? content_file
65
+ raise <<-MESSAGE % content_config['file'], item['element'], "outputs/#{content_config['file']}"
66
+ The '%s' content associated with '%s' element doesn't exist\n
67
+ or is not readable.\n\n
68
+ Check that '%s'\n
69
+ file exists and check its permissions.
70
+ MESSAGE
71
+ end
72
+
73
+ item['original'] = File.read(content_file)
74
+ elsif item['file'].blank? && special_elements.include?(item['element'])
75
+ item['skip'] = true
76
+ else
77
+ # look for a default content defined by burr for this element
78
+ # e.g. `cover.md`, `license.md`, `title.md`
79
+ default_content_file = File.join(self.book.contents_dir, "#{item['element']}.md")
80
+ if File.exist?(default_content_file)
81
+ item['original'] = File.read(default_content_file)
82
+ else
83
+ self.book.ui.error("Missing file for #{item['element']}")
84
+ exit 1
85
+ end
86
+ end
87
+
88
+ self.frontmatter << item if item['matter'] == 'frontmatter'
89
+ self.bodymatter << item if item['matter'] == 'bodymatter'
90
+ self.backmatter << item if item['matter'] == 'backmatter'
91
+ self.book.items << item
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def initialize_item(configs)
98
+ item = {
99
+ 'element' => '', # the type of this content (`chapter', `appendix', `toc', `license', ...)
100
+ 'number' => '', # the number/letter of the content (useful for `chapter', `part' and `appendix')
101
+ 'c_title' => '', # the title of the content defined in `config.yml' (usually only `part' defines it)
102
+ 'title' => '', # the `title' of this file, the first h1 in `content'
103
+ 'original' => '', # original content as written by book author
104
+ 'content' => '', # transformed content of the element (HTML usually)
105
+ 'file' => '', # the name of this item contents file (it's a relative path from book's `contents/')
106
+ 'toc' => [], # the table of contents of this element
107
+ 'skip' => false, # some elements, like `toc', do not need to covert, so just skip
108
+ }
109
+
110
+ item.merge!(configs)
111
+
112
+ # set the matter
113
+ if FRONTMATTER.include?(item['element'])
114
+ item['matter'] = 'frontmatter'
115
+ elsif BODYMATTER.include?(item['element'])
116
+ item['matter'] = 'bodymatter'
117
+ elsif BACKMATTER.include?(item['element'])
118
+ item['matter'] = 'backmatter'
119
+ else
120
+ self.book.ui.error("Element #{ item['element'] } not defined!")
121
+ exit 1
122
+ end
123
+
124
+ item
125
+ end
126
+
127
+ # If the outpus directory for current format not exists, create it!
128
+ #
129
+ def prepare_output_dir
130
+ dir = File.join(self.book.outputs_dir, self.book.format)
131
+ if !File.exist?(dir)
132
+ FileUtils.mkdir_p dir
133
+ end
134
+ end
135
+
136
+ end
137
+ end
@@ -0,0 +1,163 @@
1
+ module Burr
2
+ class Epub < 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
+ # 'blank' element not include in epub
12
+ next if item['element'] == 'blank'
13
+
14
+ self.run_plugins_of_type(:before_parse)
15
+
16
+ unless item['skip']
17
+ item['content'] = Burr::Converter.new(self.book).convert(item['original'])
18
+ end
19
+
20
+ self.run_plugins_of_type(:after_parse)
21
+
22
+ parsed_items << self.book.current_item
23
+ end
24
+ self.book.items = parsed_items
25
+ end
26
+
27
+ # Decorate the contents with template
28
+ #
29
+ def decorate_contents
30
+ decorated_items = []
31
+ self.book.items.each do |item|
32
+ self.book.current_item = item
33
+ self.run_plugins_of_type(:before_decorate)
34
+ self.run_plugins_of_type(:after_decorate)
35
+ decorated_items << self.book.current_item
36
+ end
37
+ self.book.items = decorated_items
38
+ end
39
+
40
+ def assemble_book
41
+ # 1. create html files
42
+ special_elements = %w(blank)
43
+ base = File.join(self.book.outputs_dir, 'epub')
44
+ included_files = []
45
+ tmp_files = []
46
+
47
+ self.book.items.each do |item|
48
+ next if special_elements.include?(item['element'])
49
+
50
+ basename = if item['file'].blank?
51
+ item['element']
52
+ else
53
+ item['file'].split('.')[0..-2].join('.')
54
+ end
55
+ html_path = File.join(base, "#{ basename }.html")
56
+ included_files << html_path
57
+ tmp_files << html_path
58
+
59
+ File.open(html_path, 'w') do |f|
60
+ f.puts self.book.render(self.book.template_for(item['element']), { 'item' => item, 'toc' => html_toc })
61
+ end
62
+ end
63
+
64
+ # 2. add other files
65
+ included_files << File.join(base, 'style.css')
66
+ Dir.glob(File.join(self.book.outputs_dir, 'site', 'figures', '*.*')) do |figure|
67
+ included_files << { figure => 'figures' }
68
+ end
69
+ included_files << File.join(base, 'cover.jpg')
70
+
71
+ # 3. build epub file
72
+ config = self.book.config
73
+ nav = ncx_toc
74
+ guide = build_guide
75
+
76
+ epub = Burr::EpubMaker.new do
77
+ title config['title']
78
+ creator config['translator'].blank? ? config['author'] : config['translator']
79
+ publisher config['publisher']
80
+ date config['pubdate']
81
+ identifier config['identifier'], :scheme => config['id_scheme'], :id => config['slug']
82
+ uid config['slug']
83
+ language config['language']
84
+ cover 'cover.jpg'
85
+
86
+ files included_files
87
+ nav nav
88
+ guide guide
89
+ end
90
+
91
+ epub.save(File.join(base, "#{self.book.config['slug']}-#{Time.new.strftime('%Y%m%d')}.epub"))
92
+
93
+ # 4. remove useless files
94
+ tmp_files.each do |file|
95
+ FileUtils.remove_entry(file)
96
+ end
97
+ end
98
+
99
+ private
100
+
101
+ def ncx_toc
102
+ nav = []
103
+ self.book.items.each do |item|
104
+ special_elements = %w(cover toc blank)
105
+ next if special_elements.include?(item['element'])
106
+ level_1 = item['toc'].first
107
+ next unless level_1['level'] == 1
108
+ basename = if item['file'].blank?
109
+ item['element']
110
+ else
111
+ item['file'].split('.')[0..-2].join('.')
112
+ end
113
+ html_path = "#{ basename }.html"
114
+ nav_label = if level_1['label'].blank?
115
+ "#{level_1['title']}"
116
+ else
117
+ "#{level_1['label']} #{level_1['title']}"
118
+ end
119
+ nav << { :label => nav_label, :content => html_path }
120
+ end
121
+
122
+ nav
123
+ end
124
+
125
+ def html_toc
126
+ html = '<ol class="toc-list">'
127
+ self.book.items.each do |item|
128
+ # editions define the *tocable* items
129
+ if self.book.config['formats']['epub']['toc']['elements'].include?(item['element'])
130
+ # item has several elements in its toc
131
+ if item['toc'].size > 0
132
+ item['toc'].each do |entry|
133
+ if entry['level'] <= self.book.config['formats'][self.book.format]['toc']['deep']
134
+ anchor = "#{item['element']}#{entry['id'].split('-')[1]}.html"
135
+ html << <<-LI1
136
+ <li class="#{ item['matter'] } #{ item['element'] } level-#{ entry['level'] }">
137
+ <a href="#{ anchor }##{ entry['id']}">#{ entry['label'] } #{ entry['title'] }</a>
138
+ </li>
139
+ LI1
140
+ end
141
+ end
142
+ end
143
+
144
+ # empty or special item (anything different from 'chapter' and 'appendix')
145
+ elsif !%w(cover blank toc).include?(item['element'])
146
+ html << <<-LI2
147
+ <li class="#{ item['matter'] } #{ item['element'] } level-1">
148
+ <a href="#{ item['id'] }.html">#{ item['title'] }</a>
149
+ </li>
150
+ LI2
151
+ end
152
+ end
153
+ html << '</ol>'
154
+ end
155
+
156
+ def build_guide
157
+ o = []
158
+ o << { :type => 'toc', :href => "toc.html", :title => 'Table of Contents' }
159
+ o << { :type => 'cover', :href => "cover.html", :title => 'Cover' }
160
+ end
161
+
162
+ end
163
+ end