├── .gitignore ├── .ruby-version ├── Gemfile ├── LICENSE.md ├── README.md ├── Rakefile ├── bin └── burr ├── burr.gemspec ├── generators ├── Gemfile.txt ├── config.yml ├── contents │ ├── chapter1.md │ └── chapter2.md └── stylesheets │ ├── pdf.css │ └── site.css ├── lib ├── burr.rb └── burr │ ├── book.rb │ ├── cli.rb │ ├── converter.rb │ ├── core_ext │ └── blank.rb │ ├── dependency.rb │ ├── eeepub_ext │ └── maker.rb │ ├── exporter.rb │ ├── exporters │ ├── epub.rb │ ├── pdf.rb │ └── site.rb │ ├── generator.rb │ ├── kramdown_ext │ ├── converter.rb │ ├── options.rb │ └── parser.rb │ ├── liquid_ext │ ├── block.rb │ └── extends.rb │ ├── plugin.rb │ ├── plugins │ ├── aside.rb │ ├── codeblock.rb │ ├── figure.rb │ ├── link.rb │ ├── parser_plugin.rb │ ├── table.rb │ └── toc.rb │ ├── rouge_ext │ └── html_formatter.rb │ ├── ruby_version_check.rb │ ├── server.rb │ ├── ui.rb │ └── version.rb └── resources ├── locales ├── labels │ ├── en.yml │ └── zh_CN.yml └── titles │ ├── en.yml │ └── zh_CN.yml └── templates ├── epub ├── _layout.liquid ├── acknowledgement.liquid ├── afterword.liquid ├── appendix.liquid ├── author.liquid ├── chapter.liquid ├── conclusion.liquid ├── cover.liquid ├── dedication.liquid ├── edition.liquid ├── epilogue.liquid ├── foreword.liquid ├── glossary.liquid ├── introduction.liquid ├── license.liquid ├── lof.liquid ├── lot.liquid ├── part.liquid ├── preface.liquid ├── prologue.liquid ├── table.liquid ├── title.liquid └── toc.liquid ├── pdf ├── _item.liquid ├── acknowledgement.liquid ├── afterword.liquid ├── appendix.liquid ├── author.liquid ├── blank.liquid ├── book.liquid ├── chapter.liquid ├── code.liquid ├── conclusion.liquid ├── cover.liquid ├── dedication.liquid ├── edition.liquid ├── epilogue.liquid ├── foreword.liquid ├── glossary.liquid ├── introduction.liquid ├── license.liquid ├── lof.liquid ├── lot.liquid ├── part.liquid ├── preface.liquid ├── prologue.liquid ├── table.liquid ├── title.liquid └── toc.liquid └── site ├── _layout.liquid ├── author.liquid ├── chapter.liquid ├── foreword.liquid └── preface.liquid /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | *.gem 3 | pkg/ 4 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.0.0-p247 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # License # 2 | 3 | ## Application Code ## 4 | 5 | Copyright (c) 2012 Andor Chen 6 | 7 | Some ideas and minor portions of code inspired by easybook project: 8 | Copyright (c) 2012 Javier Eguiluz (details: https://github.com/javiereguiluz/easybook/blob/master/LICENSE.md) 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy of 11 | this software and associated documentation files (the "Software"), to deal in 12 | the Software without restriction, including without limitation the rights to 13 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 14 | of the Software, and to permit persons to whom the Software is furnished to do 15 | so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | ## Dependency gems ## 29 | 30 | See their own license files 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # burr 2 | 3 | [![Dependency Status](https://gemnasium.com/AndorChen/burr.png)](https://gemnasium.com/AndorChen/burr) 4 | 5 | burr 是一个电子书制作工具(命令行)。使用 Markdown 编写书籍内容,burr 可以将其转换成 PDF,ePub 和 Mobi 格式电子书。还能生成 HTML 格式,提供书籍在线阅读。 6 | 7 | ## 注意 8 | 9 | 这个项目我已经停止开发! 10 | 11 | ## 目的 12 | 13 | 一份文稿,四种输出。 14 | 15 | ## 示例 16 | 17 | 《Ruby on Rails 教程》这本书的电子书,以及在线阅读版就是使用 burr 制作的。 18 | 19 | 20 | 21 | ## 特性 22 | 23 | ### 整体 24 | 25 | - 章节自动编号; 26 | - 图片自动编号; 27 | - 表格自动编号; 28 | - 代码片段自动编号; 29 | 30 | ### PDF 格式 31 | 32 | - 自动生成目录; 33 | - 自动生成书签; 34 | - 自动生成交叉引用; 35 | - 样式可定制; 36 | 37 | ### ePub 格式 38 | 39 | - 元信息完整; 40 | - 支持图书封面; 41 | - 兼容各主要阅读器(多看,Kindle 等); 42 | - 自动生成目录; 43 | - 自动生成交叉引用; 44 | - 样式可定制; 45 | 46 | ### mobi 格式 47 | 48 | - 元信息完整; 49 | - 支持图书封面; 50 | - 兼容各主要阅读器(Kindle 等); 51 | - 自动生成目录; 52 | - 自动生成交叉引用; 53 | 54 | ### HTML 在线阅读 55 | 56 | - 自动生成各章内容; 57 | - 自动生成各章目录; 58 | - 自动生成交叉引用; 59 | - 样式可定制; 60 | 61 | ## 安装 62 | 63 | ### 依赖程序 64 | 65 | burr 只是一个 wrapper,电子书都是通过其他程序生成的。其中 PDF 使用 [PrinceXML](http://www.princexml.com/),ePub 使用 [eeepub](https://github.com/jugyo/eeepub),mobi 使用 [kindlegen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211)。所以在使用 burr 之前,请确保安装了这些程序。具体的安装过程请参照相应程序的安装说明。 66 | 67 | ### burr 本身 68 | 69 | burr 是一个 Ruby gem,可以像其他 gem 一样安装。但是由于没有推送到 Rubygems.org,所以无法使用 `gem install` 命令安装。 70 | 71 | 在项目的 `Gemfile` 中加入以下代码: 72 | 73 | ```ruby 74 | gem 'burr' 75 | ``` 76 | 77 | 然后执行 `bundle` 命令安装。 78 | 79 | ## 使用方法 80 | 81 | ### 生成新项目 82 | 83 | 执行 `burr new [path]` 命令会生成一个新项目,生成的目录结构如下: 84 | 85 | ```text 86 | - Gemfile 87 | - config.yml # 项目设置 88 | - contents # 书稿文件夹 89 | |- contents/chapter1.md 90 | |- contents/chapter2.md 91 | - outputs # 电子书输出文件夹 92 | |- pdf/ 93 | |- style.css 94 | |- site/ 95 | |- figures/ # 书中所用图片 96 | |- style.css 97 | |- epub/ 98 | |- mobi/ 99 | |- caches/ # 缓存文件夹,暂时未用 100 | |- code/ 101 | ``` 102 | 103 | ### 生成电子书 104 | 105 | ```sh 106 | $ burr export pdf 107 | $ burr export epub 108 | $ burr export mobi 109 | $ burr export site 110 | ``` 111 | 112 | ### 帮助 113 | 114 | 更多命令请执行 `burr help` 命令查看。 115 | 116 | ## 原理 117 | 118 | 1. 使用 Markdown 语法([kramdown](http://kramdown.rubyforge.org/index.html))撰写文稿; 119 | 2. burr 根据 `config.yml` 中的设置,套用模板将 Markdown 转换成 HTML 文档; 120 | 3. 电子书生成工具将 HTML 文档转换成电子书。 121 | 122 | ## 文稿格式 123 | 124 | burr 使用 kramdown 的语法,并做了适当扩展。 125 | 126 | ### burr 的扩展 127 | 128 | #### 附加信息(来自 Leanpub) 129 | 130 | ```text 131 | A> #### 旁注标题 132 | A> 133 | A> 注意 > 符号后面要留一个空格。 134 | A> 135 | A> 如果旁注中有脚注,一定要写在旁注内。[^fn-1] 136 | A> 137 | A> [^fn-1]: 这是一个脚注。 138 | ``` 139 | 140 | ```text 141 | W> #### 警告 142 | W> 143 | W> 这是一则警告:侵权必究! 144 | ``` 145 | 146 | ```text 147 | T> #### 小贴士 148 | T> 149 | T> 夏天空调温度不要开的过低哟。 150 | ``` 151 | 152 | 其他附加信息类型,请参考 [Leanpub 的帮助文档](https://leanpub.com/help/manual#leanpub-auto-asidessidebars)。 153 | 154 | #### 代码块 155 | 156 | kramdown 原生支持的代码块由 `~~~` 分隔,但我更习惯使用 GitHub 的句法,所以 burr 提供了对后者的支持。除此之外,因为计算机书籍经常会为代码块加入说明及所在文件位置,所以 burr 利用 kramdown 的 [Block Inline Attribute Lists](http://kramdown.rubyforge.org/syntax.html#block-ials) 实现了这一功能,使用方法如下: 157 | 158 | 159 | ```ruby 160 | def hello 161 | puts "Hello, burr!" 162 | end 163 | ``` 164 | {:caption="Ruby 方法定义示例" file="/path/to/file.rb"} 165 | 166 | 代码高亮通过 [rouge](http://rubygems.org/gems/rouge) 实现。 167 | 168 | ### 图片题注 169 | 170 | ```text 171 | ![alt text](path/to/image.jpg){:caption="示例图片"} 172 | ``` 173 | 174 | ## 作者 175 | 176 | [Andor Chen](http://about.ac) 177 | 178 | ## 发布协议 179 | 180 | [MIT](LICENSE.md) 181 | 182 | ## 致谢 183 | 184 | 在 burr 开发中借鉴了 [easybook](https://github.com/javiereguiluz/easybook/) 的很多思路,特此感谢。 185 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $:.unshift(File.join(File.dirname(__FILE__), *%w[lib])) 2 | 3 | require 'rake' 4 | require 'rdoc' 5 | require 'date' 6 | 7 | require 'burr' 8 | 9 | ############################################################################# 10 | # 11 | # Helper functions 12 | # 13 | ############################################################################# 14 | 15 | def name 16 | @name ||= Dir['*.gemspec'].first.split('.').first 17 | end 18 | 19 | def version 20 | Burr::Version::STRING 21 | end 22 | 23 | def date 24 | Date.today.to_s 25 | end 26 | 27 | def rubyforge_project 28 | name 29 | end 30 | 31 | def gemspec_file 32 | "#{name}.gemspec" 33 | end 34 | 35 | def gem_file 36 | "#{name}-#{version}.gem" 37 | end 38 | 39 | ############################################################################# 40 | # 41 | # Standard tasks 42 | # 43 | ############################################################################# 44 | 45 | task :default => [:test, :features] 46 | 47 | require 'rake/testtask' 48 | Rake::TestTask.new(:test) do |test| 49 | test.libs << 'lib' << 'test' 50 | test.pattern = 'test/**/test_*.rb' 51 | test.verbose = true 52 | end 53 | 54 | desc "Generate RCov test coverage and open in your browser" 55 | task :coverage do 56 | require 'rcov' 57 | sh "rm -fr coverage" 58 | sh "rcov test/test_*.rb" 59 | sh "open coverage/index.html" 60 | end 61 | 62 | require 'rdoc/task' 63 | Rake::RDocTask.new do |rdoc| 64 | rdoc.rdoc_dir = 'rdoc' 65 | rdoc.title = "#{name} #{version}" 66 | rdoc.rdoc_files.include('README*') 67 | rdoc.rdoc_files.include('lib/**/*.rb') 68 | end 69 | 70 | desc "Open an irb session preloaded with this library" 71 | task :console do 72 | sh "irb -rubygems -r ./lib/#{name}.rb" 73 | end 74 | 75 | ############################################################################# 76 | # 77 | # Custom tasks (add your own tasks here) 78 | # 79 | ############################################################################# 80 | 81 | begin 82 | require 'cucumber/rake/task' 83 | Cucumber::Rake::Task.new(:features) do |t| 84 | t.cucumber_opts = "--format progress" 85 | end 86 | rescue LoadError 87 | desc 'Cucumber rake task not available' 88 | task :features do 89 | abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' 90 | end 91 | end 92 | 93 | ############################################################################# 94 | # 95 | # Packaging tasks 96 | # 97 | ############################################################################# 98 | 99 | desc 'Release the gem, push to github and rubygems.org' 100 | task :release => :build do 101 | unless `git branch` =~ /^\* master$/ 102 | puts "You must be on the master branch to release!" 103 | exit! 104 | end 105 | sh "git commit --allow-empty -m 'Release #{version}'" 106 | sh "git tag v#{version}" 107 | sh "git push origin master" 108 | sh "git push origin v#{version}" 109 | sh "gem push pkg/#{name}-#{version}.gem" 110 | end 111 | 112 | desc 'Build the gem and then move to /pkg' 113 | task :build do 114 | sh "mkdir -p pkg" 115 | sh "gem build #{gemspec_file}" 116 | sh "mv #{gem_file} pkg" 117 | end 118 | 119 | -------------------------------------------------------------------------------- /bin/burr: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | $:.unshift File.expand_path('../../lib', __FILE__) 4 | 5 | require 'burr/ruby_version_check' 6 | 7 | require 'burr' 8 | 9 | Burr::Cli.start 10 | -------------------------------------------------------------------------------- /burr.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path('../lib', __FILE__) 2 | 3 | require 'burr/version' 4 | 5 | Gem::Specification.new do |s| 6 | s.specification_version = 2 if s.respond_to? :specification_version= 7 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= 8 | 9 | s.name = 'burr' 10 | s.version = Burr::Version::STRING 11 | s.license = 'MIT' 12 | s.date = '2013-04-21' 13 | 14 | s.summary = "电子书制作工具" 15 | s.description = "使用 Markdown 编写书籍内容,通过 burr 将其转换成 PDF,ePub 和 Mobi 格式电子书。" 16 | 17 | s.authors = ["Andor Chen"] 18 | s.email = 'andor.chen.27@gmail.com' 19 | s.homepage = 'https://github.com/AndorChen/burr' 20 | 21 | s.require_paths = %w[lib] 22 | 23 | s.executables = ["burr"] 24 | 25 | s.rdoc_options = ["--charset=UTF-8"] 26 | s.extra_rdoc_files = %w[README.md LICENSE.md] 27 | 28 | s.add_runtime_dependency('nokogiri', '~> 1.6.0') 29 | s.add_runtime_dependency('thor', '~> 0.19.0') 30 | s.add_runtime_dependency('liquid', '~> 2.6.1') 31 | s.add_runtime_dependency('kramdown', '~> 1.3.3') 32 | s.add_runtime_dependency('rouge', '~> 1.3.3') 33 | s.add_runtime_dependency('eeepub', '~> 0.8.1') 34 | 35 | s.files = `git ls-files`.split($/) 36 | end 37 | -------------------------------------------------------------------------------- /generators/Gemfile.txt: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | 3 | gem 'burr', '~> 0.0.1' 4 | -------------------------------------------------------------------------------- /generators/config.yml: -------------------------------------------------------------------------------- 1 | title: 'Sample Book' 2 | subtitle: '' 3 | slug: '' 4 | author: 'Your Name' 5 | translator: '' 6 | edition: 'First edition' 7 | language: 'zh_CN' 8 | publisher: '' 9 | pubdate: '' 10 | isbn: '' 11 | identifier: '' # only for epub 12 | id_scheme: '' # only for epub 13 | 14 | contents: 15 | # available content types: acknowledgement, afterword, appendix, author, 16 | # blank, chapter, conclusion, cover, dedication, edition, epilogue, foreword, 17 | # glossary, introduction, license, lof (list of figures), lot (list of 18 | # tables), part, preface, prologue, title, toc (table of contents) 19 | - { element: cover } 20 | - { element: toc } 21 | - { element: chapter, number: 1, file: chapter1.md } 22 | - { element: chapter, number: 2, file: chapter2.md } 23 | 24 | formats: 25 | pdf: 26 | label: 27 | deep: 3 28 | elements: ['appendix', 'chapter', 'part'] # labels also available for: "figure", "table", "codeblock" 29 | toc: 30 | deep: 2 31 | elements: ['appendix', 'chapter', 'part'] 32 | 33 | site: 34 | label: 35 | deep: 3 36 | elements: ['appendix', 'chapter'] # labels also available for: "figure", "table", "codeblock" 37 | toc: 38 | deep: 2 # toc in single page 39 | elements: ['appendix', 'chapter'] 40 | 41 | epub: 42 | label: 43 | deep: 3 44 | elements: ['appendix', 'chapter', 'part'] # labels also available for: "figure", "table", "codeblock" 45 | toc: 46 | deep: 1 47 | elements: ['appendix', 'chapter', 'part'] 48 | 49 | mobi: 50 | label: 51 | deep: 3 52 | elements: ['appendix', 'chapter', 'part'] # labels also available for: "figure", "table", "codeblock" 53 | toc: 54 | deep: 1 55 | elements: ['appendix', 'chapter', 'part'] 56 | -------------------------------------------------------------------------------- /generators/contents/chapter1.md: -------------------------------------------------------------------------------- 1 | # The title of the first chapter # 2 | 3 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 4 | 5 | ## Ut enim ad minim veniam ## 6 | 7 | Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 8 | -------------------------------------------------------------------------------- /generators/contents/chapter2.md: -------------------------------------------------------------------------------- 1 | # The title of the second chapter # 2 | 3 | Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 4 | 5 | ## Ut enim ad minim veniam ## 6 | 7 | Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 8 | -------------------------------------------------------------------------------- /generators/stylesheets/pdf.css: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | 3 | html { 4 | margin: 0; 5 | font: 10pt/1.5 times, simsun, serif; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | } 11 | 12 | h1, h2, h3, h4, h5, h6 { 13 | font-family: Helvetica, simhei, sans-serif; 14 | font-weight: bold; 15 | margin: 1em 0 .5em 0; 16 | /*page-break-after: avoid;*/ 17 | } 18 | 19 | h1 { 20 | border-bottom: 3pt solid #ccc; 21 | margin: 0 0 2em 0; 22 | padding: 2em 0 .5em 0; 23 | font-size: 20pt; 24 | text-align: right; 25 | string-set: header content(); 26 | } 27 | 28 | h2 { 29 | font-size: 18pt; 30 | } 31 | 32 | h3 { 33 | font-size: 16pt; 34 | } 35 | 36 | h4 { 37 | font-size: 14pt; 38 | prince-bookmark-level: none; 39 | } 40 | 41 | h5 { 42 | font-size: 12pt; 43 | prince-bookmark-level: none; 44 | } 45 | 46 | h6 { 47 | font-size: 10pt; 48 | prince-bookmark-level: none; 49 | } 50 | 51 | q::before { 52 | content: "\201C"; 53 | } 54 | 55 | q::after { 56 | content: "\201D"; 57 | } 58 | 59 | p { margin: 1em 0; } 60 | 61 | pre { 62 | margin: 1em 1.3em; 63 | white-space: pre; 64 | white-space: pre-wrap; 65 | word-wrap: break-word; 66 | } 67 | 68 | code { 69 | font: Courier, monospace; 70 | } 71 | 72 | a { 73 | color: #06c; 74 | text-decoration: none; 75 | } 76 | 77 | img { 78 | prince-image-resolution: 110dpi; 79 | } 80 | 81 | /* cross-references */ 82 | 83 | a.pageref::after { content: " on page " target-counter(attr(href), page); } 84 | a.chapref::before { content: " Chapter " target-counter(attr(href), chapter) ", "; } 85 | a.figref { content: " Figure " target-counter(attr(href), figure); } 86 | a.tableref { content: " Table " target-counter(attr(href), figure); } 87 | 88 | /* sidenotes */ 89 | 90 | .sidenote { 91 | float: left; 92 | clear: left; 93 | margin: 0 0 1em -41%; 94 | width: 37%; 95 | font-size: 0.9em; 96 | font-style: normal; 97 | text-indent: 0; 98 | text-align: right; 99 | page-break-inside: avoid; 100 | } 101 | 102 | /* sidebars */ 103 | 104 | div.sidebar { 105 | float: top-next; 106 | margin: 1.2em 0 1.2em 0; 107 | border: thin solid; 108 | background: #CCC; 109 | padding: 0.5em 1em; 110 | page-break-inside: avoid; 111 | column-count: 2; 112 | column-gap: 1.5em; 113 | } 114 | 115 | div.sidebar h2 { 116 | margin-top: 0; 117 | } 118 | 119 | /* figures and tables*/ 120 | 121 | div.figure { 122 | margin: 1em 0; 123 | counter-increment: figure; 124 | } 125 | 126 | div.figure img { 127 | display: block; 128 | margin: 0 auto; 129 | } 130 | 131 | div.figure .caption, div.table .caption { 132 | text-align: center; 133 | } 134 | 135 | div.figure .caption span { 136 | font-weight: bold; 137 | } 138 | 139 | div.table .caption::before { 140 | content: "Table " counter(table) ": "; 141 | font-weight: bold; 142 | } 143 | 144 | div.table { 145 | margin: 1em 0; 146 | counter-increment: table; 147 | } 148 | 149 | div.table th { 150 | text-align: left; 151 | } 152 | 153 | table th, table td { 154 | text-align: left; 155 | padding-right: 1em; 156 | } 157 | 158 | table.lined td, table.lined th { 159 | border-top: none; 160 | border-bottom: thin dotted; 161 | padding-top: 0.2em; 162 | padding-bottom: 0.2em; 163 | } 164 | 165 | 166 | @page { 167 | margin: 27mm 16mm 27mm 16mm; 168 | size: A4; 169 | 170 | @footnotes { 171 | border-top: thin solid black; 172 | padding-top: 0.3em; 173 | } 174 | } 175 | 176 | 177 | /* define default page and names pages: cover, blank, frontmatter */ 178 | 179 | @page :left { 180 | @top-left { 181 | font: 11pt Times, simsun, serif; 182 | content: string(booktitle); 183 | vertical-align: bottom; 184 | padding-bottom: 2em; 185 | } 186 | 187 | @bottom-left { 188 | font: 11pt Times, simsun, serif; 189 | content: counter(page); 190 | padding-top: 2em; 191 | vertical-align: top; 192 | } 193 | } 194 | 195 | @page :right { 196 | @top-right { 197 | font: 11pt Times, simsun, serif; 198 | content: string(header, first); 199 | vertical-align: bottom; 200 | padding-bottom: 2em; 201 | } 202 | 203 | @bottom-right { 204 | font: 11pt Times, simsun, serif; 205 | content: counter(page); 206 | text-align: right; 207 | vertical-align: top; 208 | padding-top: 2em; 209 | } 210 | } 211 | 212 | @page cover { margin: 0; } 213 | 214 | @page frontmatter :left { 215 | @top-left { 216 | font: 11pt Times, simsun, serif; 217 | content: string(title); 218 | vertical-align: bottom; 219 | padding-bottom: 2em; 220 | } 221 | 222 | @bottom-left { 223 | font: 11pt Times, simsun, serif; 224 | content: counter(page, lower-roman); 225 | padding-top: 2em; 226 | vertical-align: top; 227 | } 228 | } 229 | 230 | @page frontmatter :right { 231 | @top-right { 232 | font: 11pt Times, simsun, serif; 233 | content: string(header, first); 234 | vertical-align: bottom; 235 | padding-bottom: 2em; 236 | } 237 | 238 | @bottom-right { 239 | font: 11pt Times, simsun, serif; 240 | content: counter(page, lower-roman); 241 | text-align: right; 242 | vertical-align: top; 243 | padding-top: 2em; 244 | } 245 | } 246 | 247 | @page blank :left { 248 | @top-left { content: normal } 249 | @bottom-left { content: normal } 250 | } 251 | 252 | @page blank :right { 253 | @top-right { content: normal } 254 | @bottom-right { content: normal } 255 | } 256 | 257 | /* page number */ 258 | 259 | div.bodymatter { 260 | counter-reset: page 1; 261 | } 262 | 263 | /* footnotes */ 264 | 265 | .footnote { 266 | display: none; /* default rule */ 267 | display: prince-footnote; /* prince-specific rules */ 268 | position: footnote; 269 | counter-increment: footnote; 270 | font-size: 9pt; 271 | line-height: 1.4; 272 | footnote-style-position: inside; 273 | } 274 | 275 | .footnote::footnote-call { 276 | vertical-align: super; 277 | font-size: 8pt; 278 | } 279 | 280 | .footnote::footnote-marker { 281 | color: green; 282 | } 283 | 284 | .chapter { 285 | counter-reset: footnote; 286 | } 287 | 288 | 289 | /* 290 | A book consists of different types of sections. We propose to use 291 | DIV elements with these class names: 292 | 293 | cover 294 | halftitlepage: contains the title of the book 295 | titlepage: contains the title of the book, name of author(s) and publisher 296 | imprint: left page with copyright, publisher, library printing information 297 | dedication: right page with short dedication 298 | foreword: written by someone other than the author(s) 299 | toc: table of contents 300 | preface: preface, including acknowledgements 301 | chapter: each chapter is given its own DIV element 302 | references: contains list of references 303 | appendix: each appendix is given its own 304 | bibliography 305 | glossary 306 | index 307 | colophon: describes how the book was produced 308 | backcover 309 | 310 | A book will use several of the types listed above, but few books 311 | will use all of them. 312 | */ 313 | 314 | /* which section uses which named page */ 315 | 316 | div.halftitlepage, div.titlepage, div.imprint, div.dedication, div.blank { page: blank } 317 | div.foreword, div.toc, div.preface { page: frontmatter } 318 | 319 | 320 | /* page breaks */ 321 | 322 | div.cover, div.halftitlepage, div.titlepage { page-break-before: right } 323 | div.imprint { page-break-before: always } 324 | div.dedication, div.foreword, div.toc, div.preface, div.chapter, div.reference, 325 | div.appendix, div.bibliography, div.glossary, div.index, div.colophon { 326 | page-break-before: always 327 | } 328 | div.backcover { page-break-before: left } 329 | 330 | /* the front cover; this code is probably not very reusable by other books */ 331 | 332 | div.cover { page: cover; } 333 | 334 | div.cover img { 335 | position: absolute; 336 | width: 7in; 337 | height: 9.25in; 338 | left: 0; 339 | top: 0; 340 | z-index: -1; 341 | } 342 | 343 | div.cover h1 { 344 | border-bottom: none; 345 | position: absolute; 346 | left: 0; 347 | top: 1cm; 348 | color: #000; 349 | font-size: 32pt; 350 | font-weight: normal; 351 | text-align: center; 352 | width: 100%; 353 | prince-bookmark-level: none; 354 | string-set: booktitle content(); 355 | } 356 | 357 | div.cover h2 { 358 | position: absolute; 359 | right: 2cm; 360 | top: 5cm; 361 | color: #000; 362 | font-size: 16pt; 363 | font-weight: normal; 364 | padding: 0.2em 5em 0.2em 1em; 365 | letter-spacing: 0.15em; 366 | prince-bookmark-level: none; 367 | } 368 | 369 | div.cover h3 { 370 | position: absolute; 371 | left: 0; 372 | top: 7cm; 373 | color: #000; 374 | font-size: 14pt; 375 | font-weight: normal; 376 | text-align: center; 377 | width: 100%; 378 | prince-bookmark-level: none; 379 | } 380 | 381 | div.cover p { 382 | position: absolute; 383 | left: 2cm; 384 | bottom: 1.5cm; 385 | font-size: 14pt; 386 | color: black; 387 | text-transform: uppercase; 388 | } 389 | 390 | div.cover p.edition { 391 | left: 15cm; 392 | top: 2cm; 393 | font-size: 16pt; 394 | } 395 | 396 | /* blank page */ 397 | div.blank p { 398 | text-align: center; 399 | } 400 | 401 | /* titlepage, halftitlepage */ 402 | 403 | div.titlepage h1, div.halftitlepage h1 { margin-bottom: 2em; } 404 | div.titlepage h2, div.halftitlepage h2 { font-size: 1.2em; margin-bottom: 3em; } 405 | div.titlepage h3, div.halftitlepage h3 { font-size: 1em; margin-bottom: 3em; } 406 | div.titlepage p, div.halftitlepage p { 407 | font-size: 1.4em; 408 | font-weight: bold; 409 | margin: 0; 410 | padding: 0; 411 | } 412 | 413 | /* imprint */ 414 | div.imprint p { 415 | text-indent: 0; 416 | margin-bottom: 1em; 417 | } 418 | div.imprint img { 419 | width: 200px; height: 157px; 420 | } 421 | 422 | /* TOC */ 423 | 424 | ol.toc-list { 425 | list-style-type: none; 426 | margin: 0; 427 | padding: 0; 428 | } 429 | ol.toc-list li.level-1 { 430 | font-weight: bold; 431 | } 432 | ol.toc-list li.level-2 { 433 | margin-left: 3.5em; 434 | font-weight: normal; 435 | } 436 | ol.toc-list li.level-3 { 437 | margin-left: 4.5em; 438 | font-weight: normal; 439 | } 440 | ol.toc-list a::after { 441 | content: leader('.') target-counter(attr(href), page); 442 | font-style: normal; 443 | } 444 | ol.toc-list li.frontmatter a::after { 445 | content: leader('.') target-counter(attr(href), page, lower-roman); 446 | font-style: normal; 447 | } 448 | ol.toc-list > li.backmatter a::after { 449 | content: leader('.') target-counter(attr(href), page); 450 | font-style: normal; 451 | } 452 | 453 | ol.toc-list a { 454 | color: #000; 455 | } 456 | 457 | /* Aside */ 458 | 459 | div.aside { 460 | background: #eee; 461 | padding: 1em; 462 | } 463 | 464 | div.aside h4 { 465 | background: #ccc; 466 | font-size: 10pt; 467 | margin: 0; 468 | padding: .5em 1em; 469 | text-align: center; 470 | } 471 | 472 | /* index */ 473 | 474 | ol.index { 475 | list-style-type: none; 476 | margin: 0; padding: 0; 477 | column-count: 2; 478 | column-gap: 1em; 479 | } 480 | 481 | ul.index a::after { content: ", " target-counter(attr(href), page); } 482 | 483 | /* codeblock */ 484 | 485 | .codeblock .caption { 486 | margin: 0 0 .5em; 487 | } 488 | 489 | .codeblock .caption span { 490 | font-weight: bold; 491 | } 492 | 493 | .codeblock .highlight { 494 | background: #eee; 495 | padding: 0.5em; 496 | } 497 | 498 | .codeblock .highlight pre { 499 | margin: 0; 500 | } 501 | 502 | .highlight .c{color:#999988;font-style:italic} 503 | .highlight .err{color:#a61717;background-color:#e3d2d2} 504 | .highlight .k{font-weight:bold} 505 | .highlight .o{font-weight:bold} 506 | .highlight .cm{color:#999988;font-style:italic} 507 | .highlight .cp{color:#999999;font-weight:bold} 508 | .highlight .c1{color:#999988;font-style:italic} 509 | .highlight .cs{color:#999999;font-weight:bold;font-style:italic} 510 | .highlight .gd{color:#000000;background-color:#ffdddd} 511 | .highlight .gd .x{color:#000000;background-color:#ffaaaa} 512 | .highlight .ge{font-style:italic} 513 | .highlight .gr{color:#aa0000} 514 | .highlight .gh{color:#999999} 515 | .highlight .gi{color:#000000;background-color:#ddffdd} 516 | .highlight .gi .x{color:#000000;background-color:#aaffaa} 517 | .highlight .go{color:#888888} 518 | .highlight .gp{color:#555555} 519 | .highlight .gs{font-weight:bold} 520 | .highlight .gu{color:#800080;font-weight:bold} 521 | .highlight .gt{color:#aa0000} 522 | .highlight .kc{font-weight:bold} 523 | .highlight .kd{font-weight:bold} 524 | .highlight .kn{font-weight:bold} 525 | .highlight .kp{font-weight:bold} 526 | .highlight .kr{font-weight:bold} 527 | .highlight .kt{color:#445588;font-weight:bold} 528 | .highlight .m{color:#009999} 529 | .highlight .s{color:#d14} 530 | .highlight .na{color:#008080} 531 | .highlight .nb{color:#0086B3} 532 | .highlight .nc{color:#445588;font-weight:bold} 533 | .highlight .no{color:#008080} 534 | .highlight .ni{color:#800080} 535 | .highlight .ne{color:#990000;font-weight:bold} 536 | .highlight .nf{color:#990000;font-weight:bold} 537 | .highlight .nn{color:#555555} 538 | .highlight .nt{color:#000080} 539 | .highlight .nv{color:#008080} 540 | .highlight .ow{font-weight:bold} 541 | .highlight .w{color:#bbbbbb} 542 | .highlight .mf{color:#009999} 543 | .highlight .mh{color:#009999} 544 | .highlight .mi{color:#009999} 545 | .highlight .mo{color:#009999} 546 | .highlight .sb{color:#d14} 547 | .highlight .sc{color:#d14} 548 | .highlight .sd{color:#d14} 549 | .highlight .s2{color:#d14} 550 | .highlight .se{color:#d14} 551 | .highlight .sh{color:#d14} 552 | .highlight .si{color:#d14} 553 | .highlight .sx{color:#d14} 554 | .highlight .sr{color:#009926} 555 | .highlight .s1{color:#d14} 556 | .highlight .ss{color:#990073} 557 | .highlight .bp{color:#999999} 558 | .highlight .vc{color:#008080} 559 | .highlight .vg{color:#008080} 560 | .highlight .vi{color:#008080} 561 | .highlight .il{color:#009999} 562 | .highlight .gc{color:#999;background-color:#EAF2F5} 563 | 564 | @media screen, handheld { 565 | html { margin: 1em; font: 14px Helvetica, simhei, sans-serif; } 566 | h1 { margin-bottom: 0.5em } 567 | div.cover, div.halftitlepage, 568 | div.dedication, div.foreword, div.toc, div.index { display: none } 569 | } 570 | -------------------------------------------------------------------------------- /generators/stylesheets/site.css: -------------------------------------------------------------------------------- 1 | @charset 'utf-8'; 2 | -------------------------------------------------------------------------------- /lib/burr.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) 2 | 3 | # Require all of the Ruby files in the given directory. 4 | # 5 | # path - The String relative path from here to the directory. 6 | # 7 | # Returns nothing. 8 | def require_all(path) 9 | glob = File.join(File.dirname(__FILE__), path, '*.rb') 10 | Dir[glob].each do |f| 11 | require f 12 | end 13 | end 14 | 15 | require 'nokogiri' 16 | require 'kramdown' 17 | require 'liquid' 18 | require 'thor' 19 | require 'thor/group' 20 | require 'eeepub' 21 | 22 | require 'yaml' 23 | 24 | require_all 'burr/core_ext' 25 | require_all 'burr/kramdown_ext' 26 | require 'burr/cli' 27 | require 'burr/dependency' 28 | require 'burr/generator' 29 | require 'burr/version' 30 | require 'burr/ui' 31 | require 'burr/converter' 32 | require 'burr/exporter' 33 | require_all 'burr/exporters' 34 | require 'burr/plugin' 35 | require_all 'burr/plugins' 36 | require_all 'burr/liquid_ext' 37 | require_all 'burr/eeepub_ext' 38 | require 'burr/book' 39 | require 'burr/server' 40 | 41 | Encoding.default_internal = 'utf-8' 42 | Encoding.default_external = 'utf-8' 43 | 44 | module Burr 45 | 46 | def self.configuration 47 | path = "#{Dir.pwd}/config.yml" 48 | 49 | begin 50 | YAML.load_file path 51 | rescue => e 52 | puts "#{e.message}" 53 | end 54 | end 55 | 56 | end 57 | -------------------------------------------------------------------------------- /lib/burr/book.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | class Book 3 | 4 | attr_accessor :config, :format, :ui 5 | attr_accessor :gem_dir, :root_dir, :outputs_dir, :caches_dir, :contents_dir, :plugins_dir, :templates_dir 6 | attr_accessor :items, :toc, :images, :tables, :current_item 7 | attr_accessor :labels, :titles, :ids 8 | attr_accessor :uid, :slug 9 | 10 | def initialize(config, format) 11 | @config = config 12 | @format = format 13 | @ui = Burr::UI.new 14 | 15 | # directory location 16 | @gem_dir = File.expand_path('../../../' ,__FILE__) 17 | @root_dir = Dir.pwd 18 | @outputs_dir = "#{@root_dir}/outputs" 19 | @caches_dir = "#{@root_dir}/caches" 20 | @contents_dir = "#{@root_dir}/contents" 21 | @plugins_dir = "#{@root_dir}/plugins" 22 | @templates_dir = "#{@root_dir}/templates" 23 | 24 | # publishing process variables 25 | @items = [] 26 | @toc = '' 27 | @images = [] 28 | @tables = [] 29 | @current_item = [] 30 | 31 | # labels and titles 32 | book_labels # @labels = {}, @ids = [] 33 | book_titles # @titles = {} 34 | 35 | # book information 36 | book_uid # @uid = '' 37 | book_slug # @slug = '' 38 | end 39 | 40 | # Export site files in outputs/site 41 | # 42 | def export_site 43 | exporter = Burr::Site.new(self) 44 | self.ui.confirm "Start exporting site files...." 45 | exporter.run 46 | self.ui.confirm "Exported site!" 47 | end 48 | 49 | # Export PDF files in outputs/pdf 50 | # 51 | def export_pdf 52 | exporter = Burr::PDF.new(self) 53 | self.ui.confirm "Start exporting pdf file...." 54 | exporter.run 55 | self.ui.confirm "Exported PDF!" 56 | end 57 | 58 | # Export Epub files in outputs/epub 59 | # 60 | def export_epub 61 | exporter = Burr::Epub.new(self) 62 | self.ui.confirm "Start exporting epub file...." 63 | exporter.run 64 | self.ui.confirm "Exported Epub!" 65 | end 66 | 67 | # Export Mobi files in outputs/mobi 68 | # 69 | def export_mobi 70 | dest = File.join(self.outputs_dir, 'mobi') 71 | FileUtils.mkdir_p(dest) unless File.exist?(dest) 72 | 73 | self.ui.confirm "Start exporting mobi file...." 74 | 75 | FileUtils.cd(File.join(self.outputs_dir, 'epub')) do 76 | base = "#{self.config['slug']}-#{Time.new.strftime('%Y%m%d')}" 77 | epub = "#{base}.epub" 78 | mobi = "#{base}.mobi" 79 | unless File.exist?(epub) 80 | self.ui.error('Please export Epub first!') 81 | exit 1 82 | end 83 | 84 | system "kindlegen #{epub} -c2" 85 | FileUtils.cp(mobi, dest) 86 | FileUtils.rm(mobi) 87 | end 88 | 89 | self.ui.confirm "Exported Mobi!" 90 | end 91 | 92 | # Export all formats 93 | # 94 | def export_all 95 | self.export_pdf 96 | self.export_epub 97 | self.export_mobi 98 | end 99 | 100 | # Gets the template file for an element. 101 | # 102 | # - element The element name, such as 'chapter', 'appendix' 103 | # 104 | # Returns The absolute path of this element's template file. 105 | # 106 | def template_for(element) 107 | base = File.join('templates', self.format, "#{element}.liquid") 108 | default = File.join(self.gem_dir, 'resources', base) 109 | custom = File.join(self.root_dir, base) 110 | 111 | if File.exist?(custom) 112 | custom 113 | elsif !File.exist?(custom) && File.exist?(default) 114 | default 115 | else 116 | self.ui.error("ERROR: Template #{self.format}/#{element}.liquid not found!") 117 | exit 1 118 | end 119 | end 120 | 121 | # Get the stylesheet file for a format. 122 | # 123 | # @param [String] format The format name, could be 'pdf', 'epub', 'site' and 'mobi' 124 | # @return [String] The absolute path to this format's stylesheet 125 | def stylesheet_for(format) 126 | if %w(pdf epub site mobi).include?(format) 127 | css = File.join(self.outputs_dir, format, "style.css") 128 | 129 | if File.exist?(css) 130 | css 131 | else 132 | self.ui.error("ERROR: Not found stylesheet for format #{format}.") 133 | exit 1 134 | end 135 | else 136 | self.ui.error("ERROR: #{format} is not support!") 137 | exit 1 138 | end 139 | end 140 | 141 | # Shortcut method to get the label of any element type. 142 | # 143 | # element - The element type (`chapter', `foreword', ...) in String format. 144 | # variables - A variables Hash used to render the label. 145 | # 146 | # Returns the label String of the element or an empty String. 147 | def render_label(element, variables = {}) 148 | c_labels = self.labels.include?(element) ? self.labels[element] : '' 149 | # some elements (mostly chapters and appendices) have a different label for each level (h1, ..., h6) 150 | if c_labels.is_a? Array 151 | index = variables['item']['level'] - 1 152 | if index == 0 153 | label = c_labels[0] 154 | else 155 | label = c_labels[1] 156 | end 157 | else 158 | label = c_labels 159 | end 160 | 161 | self.render_string(label, variables) 162 | end 163 | 164 | # Shortcut method to get the id of headings. 165 | # 166 | # variables - A variables Hash used to render the id. 167 | # 168 | # Returns the id String of the heading. 169 | def render_id(variables = {}) 170 | index = variables['item']['level'] - 1 171 | if index == 0 172 | id = self.ids[0] 173 | else 174 | id = self.ids[1] 175 | end 176 | 177 | self.render_string(id, variables) 178 | end 179 | 180 | # Renders any string as a Liquid template. 181 | # 182 | # @param [String] text The original content to render 183 | # @param [Array] variables Optional variables passed to the template 184 | def render_string(text, variables = {}) 185 | registers = { :registers => { :book => self } } 186 | Liquid::Template.parse(text).render(variables, registers) 187 | end 188 | 189 | 190 | # Renders any template (currently only supports Liquid templates). 191 | # 192 | # @param [String] template The template name, without the extension 193 | # @param [Hash] parameters Optional variables passed to the template 194 | # @param [String] target Optional output file path. If set, the rendered template is saved in this file. 195 | # @return [String] The rendered content 196 | def render(template, parameters = {}, target = nil) 197 | defaults = { 198 | 'config' => self.config, 199 | 'format' => self.format, 200 | 'generator' => { 'name' => 'Burr', 'version' => Burr::Version::STRING } 201 | } 202 | text = File.read(template) 203 | registers = { :registers => { :book => self } } 204 | content = Liquid::Template.parse(text).render(defaults.merge(parameters), registers) 205 | 206 | if target 207 | File.open(target, 'wb') { |f| f.puts content } 208 | end 209 | 210 | content 211 | end 212 | 213 | # Makes the liquid tags live. 214 | # 215 | # Returns Hash. 216 | def to_liquid 217 | #{ 'book' => self } 218 | end 219 | 220 | private 221 | 222 | # Generates `@labels' for chapters, appendixes, etc. 223 | # 224 | # Returns nil. 225 | def book_labels 226 | base = File.join('locales', 'labels', "#{self.config['language']}.yml") 227 | default = File.join(self.gem_dir, 'resources', base) 228 | custom = File.join(self.root_dir, base) 229 | 230 | labels = YAML::load_file(default) 231 | 232 | #books can define their own labels files 233 | if File.exist? custom 234 | custom_labels = YAML::load_file(custom) 235 | labels.merge!(custom_labels) 236 | end 237 | 238 | self.labels = labels 239 | self.ids = labels['id'] 240 | nil 241 | end 242 | 243 | # Generates `@titles' for chapters, appendixes, etc. 244 | # 245 | # Returns nil. 246 | def book_titles 247 | base = File.join('locales', 'titles', "#{self.config['language']}.yml") 248 | default = File.join(self.gem_dir, 'resources', base) 249 | custom = File.join(self.root_dir, base) 250 | 251 | titles = YAML::load_file(default) 252 | 253 | #books can define their own titles files 254 | if File.exist? custom 255 | custom_titles = YAML::load_file(custom) 256 | return titles.merge(custom_titles) 257 | end 258 | 259 | self.titles = titles 260 | nil 261 | end 262 | 263 | # Generates a unique `@uid' for the book. 264 | # 265 | # Returns nil. 266 | def book_uid 267 | if @config['isbn'] 268 | @uid = @config['isbn'] 269 | else 270 | @uid = Digest::MD5.hexdigest("#{Time.now}--#{rand}") 271 | end 272 | end 273 | 274 | # Generates a `@slug' for the book. 275 | # 276 | # Uses the current directory name as slug, if no `slug' in `config.yml' provided. 277 | # 278 | # Returns String of the book's slug. 279 | def book_slug 280 | if @config['slug'] 281 | @slug = @config['slug'] 282 | else 283 | dir = File.basename(self.root_dir) 284 | @slug = CGI.escape(dir) 285 | end 286 | end 287 | 288 | end 289 | end 290 | -------------------------------------------------------------------------------- /lib/burr/cli.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | class Cli < Thor 3 | 4 | desc 'new [PATH]', 'Create a new book' 5 | def new(path) 6 | generator = Burr::Generator.new 7 | generator.destination_root = path 8 | generator.invoke_all 9 | end 10 | 11 | desc 'export [FORMAT]', 'Export a book format, or all formats' 12 | def export(format) 13 | valid = %w(site pdf epub mobi all) 14 | 15 | if valid.include?(format) 16 | book = Burr::Book.new(config, format) 17 | case format 18 | when 'site' 19 | book.export_site 20 | when 'pdf' 21 | unless Dependency.prince_installed? 22 | book.ui.warn "Please install PrinceXML first." 23 | exit 1 24 | end 25 | book.export_pdf 26 | when 'epub' 27 | book.export_epub 28 | when 'mobi' 29 | unless Dependency.kindlegen_installed? 30 | book.ui.warn "Please install kindelgen first." 31 | exit 1 32 | end 33 | book.export_mobi 34 | when 'all' 35 | puts 'pending' 36 | # book.export_all 37 | end 38 | else 39 | raise "ERROR: invalid format. Formats: #{valid.join(', ')}." 40 | end 41 | end 42 | 43 | desc 'server', 'Site preview' 44 | def server 45 | Burr::Server.start! 46 | end 47 | 48 | desc 'version', 'Show the burr version' 49 | def version 50 | puts Burr::Version::STRING 51 | end 52 | 53 | private 54 | 55 | def config 56 | @config ||= Burr.configuration 57 | end 58 | 59 | def book_root 60 | @root ||= Dir.pwd 61 | end 62 | 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/burr/converter.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | class Converter 3 | 4 | attr_accessor :book 5 | 6 | def initialize(book) 7 | @book = book 8 | end 9 | 10 | def convert(text) 11 | ::Kramdown::Document.new(text, 12 | :input => 'Bsmarkdown', 13 | :auto_ids => false, 14 | :register => self.book 15 | ).to_bshtml 16 | end 17 | 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/burr/core_ext/blank.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | # This file is steal from Rails 4 | 5 | class Object 6 | # An object is blank if it's false, empty, or a whitespace string. 7 | # For example, '', ' ', +nil+, [], and {} are all blank. 8 | # 9 | # This simplifies: 10 | # 11 | # if address.nil? || address.empty? 12 | # 13 | # ...to: 14 | # 15 | # if address.blank? 16 | def blank? 17 | respond_to?(:empty?) ? empty? : !self 18 | end 19 | 20 | # An object is present if it's not blank?. 21 | def present? 22 | !blank? 23 | end 24 | 25 | # Returns object if it's present? otherwise returns +nil+. 26 | # object.presence is equivalent to object.present? ? object : nil. 27 | # 28 | # This is handy for any representation of objects where blank is the same 29 | # as not present at all. For example, this simplifies a common check for 30 | # HTTP POST/query parameters: 31 | # 32 | # state = params[:state] if params[:state].present? 33 | # country = params[:country] if params[:country].present? 34 | # region = state || country || 'US' 35 | # 36 | # ...becomes: 37 | # 38 | # region = params[:state].presence || params[:country].presence || 'US' 39 | def presence 40 | self if present? 41 | end 42 | end 43 | 44 | class NilClass 45 | # +nil+ is blank: 46 | # 47 | # nil.blank? # => true 48 | def blank? 49 | true 50 | end 51 | end 52 | 53 | class FalseClass 54 | # +false+ is blank: 55 | # 56 | # false.blank? # => true 57 | def blank? 58 | true 59 | end 60 | end 61 | 62 | class TrueClass 63 | # +true+ is not blank: 64 | # 65 | # true.blank? # => false 66 | def blank? 67 | false 68 | end 69 | end 70 | 71 | class Array 72 | # An array is blank if it's empty: 73 | # 74 | # [].blank? # => true 75 | # [1,2,3].blank? # => false 76 | alias_method :blank?, :empty? 77 | end 78 | 79 | class Hash 80 | # A hash is blank if it's empty: 81 | # 82 | # {}.blank? # => true 83 | # { key: 'value' }.blank? # => false 84 | alias_method :blank?, :empty? 85 | end 86 | 87 | class String 88 | # A string is blank if it's empty or contains whitespaces only: 89 | # 90 | # ''.blank? # => true 91 | # ' '.blank? # => true 92 | # ' '.blank? # => true 93 | # ' something here '.blank? # => false 94 | def blank? 95 | self !~ /[^[:space:]]/ 96 | end 97 | end 98 | 99 | class Numeric #:nodoc: 100 | # No number is blank: 101 | # 102 | # 1.blank? # => false 103 | # 0.blank? # => false 104 | def blank? 105 | false 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/burr/dependency.rb: -------------------------------------------------------------------------------- 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 29 | -------------------------------------------------------------------------------- /lib/burr/eeepub_ext/maker.rb: -------------------------------------------------------------------------------- 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 132 | -------------------------------------------------------------------------------- /lib/burr/exporter.rb: -------------------------------------------------------------------------------- 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 138 | -------------------------------------------------------------------------------- /lib/burr/exporters/epub.rb: -------------------------------------------------------------------------------- 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 = '
    ' 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 |
  1. 137 | #{ entry['label'] } #{ entry['title'] } 138 |
  2. 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 |
  3. 148 | #{ item['title'] } 149 |
  4. 150 | LI2 151 | end 152 | end 153 | html << '
' 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 164 | -------------------------------------------------------------------------------- /lib/burr/exporters/pdf.rb: -------------------------------------------------------------------------------- 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 = '
    ' 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 |
  1. 75 | #{ entry['label'] } #{ entry['title'] } 76 |
  2. 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 |
  3. 86 | #{ item['label'] } #{ item['title'] } 87 |
  4. 88 | LI2 89 | end 90 | end 91 | html << '
' 92 | end 93 | 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/burr/exporters/site.rb: -------------------------------------------------------------------------------- 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 = '
    ' 76 | toc.each do |entry| 77 | next if entry['level'] - 1 > self.book.config['formats']['site']['toc']['deep'] 78 | html << <<-LI 79 |
  1. 80 | #{ entry['label'] } #{ entry['title'] } 81 |
  2. 82 | LI 83 | end 84 | html << '
' 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 102 | -------------------------------------------------------------------------------- /lib/burr/generator.rb: -------------------------------------------------------------------------------- 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 42 | -------------------------------------------------------------------------------- /lib/burr/kramdown_ext/converter.rb: -------------------------------------------------------------------------------- 1 | require 'burr/kramdown_ext/parser' 2 | require 'burr/rouge_ext/html_formatter' 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 | 31 | caption = attr['caption'] 32 | file = attr['file'] 33 | code = rouge_highlight(el.value, lang) 34 | output = '
' 37 | if caption 38 | caption_el = ::Kramdown::Parser::Bsmarkdown.parse(caption).first 39 | caption_html = inner(caption_el.children.first, 0) 40 | output << "

#{caption_html}

" 41 | end 42 | output << "

#{file}

" if file 43 | output << "#{code}
" 44 | end 45 | 46 | # Converts headers 47 | # 48 | def convert_header(el, indent) 49 | attr = el.attr.dup 50 | item = self.book.current_item 51 | if @options[:auto_ids] && !attr['id'] 52 | attr['id'] = generate_id(el.options[:raw_text]) 53 | end 54 | #@toc << [el.options[:level], attr['id'], el.children] if attr['id'] && in_toc?(el) 55 | unless attr['class'] == 'skip-toc' 56 | item['toc'] << { 57 | 'level' => el.options[:level], 58 | 'title' => el.options[:raw_text] 59 | } 60 | end 61 | 62 | level = output_header_level(el.options[:level]) 63 | format_as_block_html("h#{level}", attr, inner(el, indent), indent) 64 | end 65 | 66 | def convert_footnote(el, indent) 67 | if self.book.format == 'pdf' 68 | inline = format_as_span_html('span', { 'class'=> 'footnote', 'id' => "fn-#{ el.options[:name] }"}, inner(el.value, 0)) 69 | inline.sub!(/\s*

/, '').sub!(/<\/p>\n/, '') 70 | else 71 | number = @footnote_counter 72 | @footnote_counter += 1 73 | @footnotes << [el.options[:name], el.value] 74 | "#{number}" 75 | end 76 | end 77 | 78 | ::Kramdown::Parser::Bsmarkdown::BOXES.each do |box| 79 | define_method("convert_#{box}_box") { |el, indent| <<-EOF } 80 | #{' '*indent}

\n#{inner(el, indent)}#{' '*indent}
\n 81 | EOF 82 | end 83 | 84 | alias :orin_convert_table :convert_table 85 | 86 | alias :convert_thead :orin_convert_table 87 | alias :convert_tbody :orin_convert_table 88 | alias :convert_tfoot :orin_convert_table 89 | alias :convert_tr :orin_convert_table 90 | 91 | def convert_table(el, indent) 92 | caption = el.attr.delete('caption') 93 | output = '

#{caption_html}

" 98 | else 99 | output << '">' 100 | end 101 | output << format_as_indented_block_html(el.type, el.attr, inner(el, indent), indent) 102 | output << '
' 103 | end 104 | 105 | # Return a HTML ordered list with the footnote content for the used footnotes. 106 | def footnote_content 107 | ol = Element.new(:ol) 108 | ol.attr['start'] = @footnote_start if @footnote_start != 1 109 | @footnotes.each do |name, data| 110 | li = Element.new(:li, nil, {'id' => "fn-#{name}"}) 111 | li.children = Marshal.load(Marshal.dump(data.children)) 112 | ol.children << li 113 | 114 | ref = Element.new(:raw, "") 115 | if li.children.last.type == :p 116 | para = li.children.last 117 | else 118 | li.children << (para = Element.new(:p)) 119 | end 120 | para.children << ref 121 | end 122 | (ol.children.empty? ? '' : format_as_indented_block_html('div', {:class => "footnotes"}, convert(ol, 2), 0)) 123 | end 124 | 125 | private 126 | 127 | def convert_image_with_caption(el, indent) 128 | img_el = el.children.first 129 | caption = img_el.attr.delete('caption') 130 | img_html = convert_img(img_el, indent) 131 | 132 | caption_el = ::Kramdown::Parser::Bsmarkdown.parse(caption).first 133 | caption_html = inner(caption_el.children.first, 0) 134 | 135 | "
#{img_html}

#{caption_html}

" 136 | end 137 | 138 | def rouge_highlight(text, lexer, &b) 139 | lexer = ::Rouge::Lexer.find(lexer) unless lexer.respond_to? :lex 140 | lexer = ::Rouge::Lexers::PlainText unless lexer 141 | 142 | formatter = ::Rouge::Formatters::BHTML.new(css_class: "highlight type-#{lexer.tag}") 143 | 144 | formatter.format(lexer.lex(text), &b) 145 | end 146 | 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/burr/kramdown_ext/options.rb: -------------------------------------------------------------------------------- 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 39 | -------------------------------------------------------------------------------- /lib/burr/kramdown_ext/parser.rb: -------------------------------------------------------------------------------- 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 66 | -------------------------------------------------------------------------------- /lib/burr/liquid_ext/block.rb: -------------------------------------------------------------------------------- 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) 59 | -------------------------------------------------------------------------------- /lib/burr/liquid_ext/extends.rb: -------------------------------------------------------------------------------- 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) 115 | -------------------------------------------------------------------------------- /lib/burr/plugin.rb: -------------------------------------------------------------------------------- 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 71 | -------------------------------------------------------------------------------- /lib/burr/plugins/aside.rb: -------------------------------------------------------------------------------- 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 = "#{label}#{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 45 | -------------------------------------------------------------------------------- /lib/burr/plugins/codeblock.rb: -------------------------------------------------------------------------------- 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 = "#{label}#{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 43 | -------------------------------------------------------------------------------- /lib/burr/plugins/figure.rb: -------------------------------------------------------------------------------- 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' => '/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 = "#{label}#{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 63 | -------------------------------------------------------------------------------- /lib/burr/plugins/link.rb: -------------------------------------------------------------------------------- 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 48 | -------------------------------------------------------------------------------- /lib/burr/plugins/parser_plugin.rb: -------------------------------------------------------------------------------- 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
by
(it causes problems for epub books) 12 | def replace_br 13 | item = self.book.current_item 14 | item['content'].gsub!('
', '
') 15 | end 16 | 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/burr/plugins/table.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | class TablePlugin < 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 table 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?('table') 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('.table.has-caption').each do |table| 26 | # add id 27 | table.set_attribute('id', "table-#{number}-#{counter}") 28 | 29 | # add label 30 | caption = table.css('.caption').first 31 | label = self.book.render_label('table', { 'item' => { 'number' => number, 'counter' => counter } }) 32 | caption_html = "#{label}#{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 43 | -------------------------------------------------------------------------------- /lib/burr/plugins/toc.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | class TocPlugin < Plugin 3 | 4 | def after_parse 5 | parse_toc 6 | end 7 | 8 | private 9 | 10 | def parse_toc 11 | item = self.book.current_item 12 | 13 | if item['toc'].size > 0 14 | # strip title from the parsed content 15 | # 16 | first_heading = item['toc'].first 17 | # only

headings can be the title of the content 18 | if first_heading['level'] == 1 19 | # the

heading must be the first line to consider it a title 20 | item['content'].sub!(/^\n+(.*)/, '\1') 21 | item['title'] = "#{first_heading['title']}" 22 | end 23 | 24 | item['id'] = "#{item['element']}" 25 | item['id'] << "-#{item['number']}" unless item['number'].blank? 26 | 27 | # add labels 28 | # 29 | current_format_labels = self.book.config['formats']["#{self.book.format}"]['label']['elements'] 30 | current_format_labels = [] if current_format_labels.nil? 31 | counters = [ 32 | item['number'], 33 | 0, 34 | 0, 35 | 0, 36 | 0, 37 | 0 38 | ] 39 | 40 | parsed_toc = [] 41 | 42 | item['toc'].each do |t| 43 | level = t['level'] 44 | title = t['title'] 45 | 46 | counters[level-1] += 1 if level > 1 47 | # Reset the counters for the higher heading levels 48 | (level..5).each { |x| counters[x] = 0 } 49 | sliced_counters = counters[0..(level-1)] 50 | 51 | parameters = { 52 | 'level' => level, 53 | 'number' => item['number'], 54 | 'element' => item['element'], 55 | 'counters' => sliced_counters 56 | } 57 | 58 | # format config allows labels for this element type (`labels' option) 59 | if current_format_labels.include?(item['element']) && level <= self.book.config['formats']["#{self.book.format}"]['label']['deep'] 60 | label = self.book.render_label(item['element'], { 'item' => parameters }) 61 | else 62 | label = '' 63 | end 64 | 65 | id = self.book.render_id({ 'item' => parameters }) 66 | 67 | parsed_toc << { 68 | 'id' => id, 69 | 'level' => level, 70 | 'title' => title, 71 | 'label' => label 72 | } 73 | end 74 | 75 | item['toc'] = parsed_toc 76 | 77 | item['label'] = item['toc'][0]['label'] 78 | 79 | # add labels to content 80 | # 81 | item['toc'].each do |tt| 82 | # the parsed title can be different from the toc entry title 83 | # that's the case for the titles with markup code inside (* ` ** etc.) 84 | # thus, the replacement must be done based on a fuzzy title that 85 | # doesn't include the title text 86 | fuzzy_title = Regexp.new("#{tt['title']}") 87 | labeled_title = sprintf("%s%s\n", 88 | tt['level'], 89 | tt['id'], 90 | tt['label'], 91 | '' != tt['label'] ? " #{tt['title']}" : tt['title'], 92 | tt['level'] 93 | ) 94 | item['content'].sub!(fuzzy_title, labeled_title) 95 | end 96 | end 97 | 98 | # ensure that the item has a title (using the default title if necessary) 99 | item['title'] = self.book.titles[item['element']] if item['title'].blank? 100 | 101 | nil 102 | end 103 | 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/burr/rouge_ext/html_formatter.rb: -------------------------------------------------------------------------------- 1 | require 'cgi' 2 | require 'rouge' 3 | 4 | module Rouge 5 | module Formatters 6 | # Transforms a token stream into HTML output. 7 | class BHTML < Formatter 8 | tag 'bhtml' 9 | 10 | # @option opts [String] :css_class ('highlight') 11 | # @option opts [true/false] :line_numbers (false) 12 | # @option opts [Rouge::CSSTheme] :inline_theme (nil) 13 | # @option opts [true/false] :wrap (true) 14 | # 15 | # Initialize with options. 16 | # 17 | # If `:inline_theme` is given, then instead of rendering the 18 | # tokens as tags with CSS classes, the styles according to 19 | # the given theme will be inlined in "style" attributes. This is 20 | # useful for formats in which stylesheets are not available. 21 | # 22 | # Content will be wrapped in a tag 23 | # with the given `:css_class` unless `:wrap` is set to `false`. 24 | def initialize(opts={}) 25 | @css_class = opts.fetch(:css_class, 'highlight') 26 | @line_numbers = opts.fetch(:line_numbers, false) 27 | @inline_theme = opts.fetch(:inline_theme, nil) 28 | @wrap = opts.fetch(:wrap, true) 29 | end 30 | 31 | # @yield the html output. 32 | def stream(tokens, &b) 33 | if @line_numbers 34 | stream_tableized(tokens, &b) 35 | else 36 | stream_untableized(tokens, &b) 37 | end 38 | end 39 | 40 | private 41 | def stream_untableized(tokens, &b) 42 | yield "
" if @wrap
 43 |         tokens.each do |tok, val|
 44 |           span(tok, val, &b)
 45 |         end
 46 |         yield "
\n" if @wrap 47 | end 48 | 49 | def stream_tableized(tokens) 50 | num_lines = 0 51 | last_val = '' 52 | formatted = '' 53 | 54 | tokens.each do |tok, val| 55 | last_val = val 56 | num_lines += val.scan(/\n/).size 57 | span(tok, val) { |str| formatted << str } 58 | end 59 | 60 | # add an extra line for non-newline-terminated strings 61 | if last_val[-1] != "\n" 62 | num_lines += 1 63 | span(Token::Tokens::Text::Whitespace, "\n") { |str| formatted << str } 64 | end 65 | 66 | # generate a string of newline-separated line numbers for the gutter 67 | numbers = num_lines.times.map do |x| 68 | %<
#{x+1}
> 69 | end.join 70 | 71 | yield "
" if @wrap 72 | yield '' 73 | 74 | # the "gl" class applies the style for Generic.Lineno 75 | yield '' 78 | 79 | yield '' 84 | 85 | yield "
' 76 | yield numbers 77 | yield '' 80 | yield '
'
 81 |         yield formatted
 82 |         yield '
' 83 | yield '
\n" 86 | yield "
\n" if @wrap 87 | end 88 | 89 | TABLE_FOR_ESCAPE_HTML = { 90 | '&' => '&', 91 | '<' => '<', 92 | '>' => '>', 93 | } 94 | 95 | def span(tok, val) 96 | val = val.gsub(/[&<>]/, TABLE_FOR_ESCAPE_HTML) 97 | shortname = tok.shortname or raise "unknown token: #{tok.inspect} for #{val.inspect}" 98 | 99 | if shortname.empty? 100 | yield val 101 | else 102 | if @inline_theme 103 | rules = @inline_theme.style_for(tok).rendered_rules 104 | 105 | yield "#{val}" 106 | else 107 | yield "#{val}" 108 | end 109 | end 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/burr/ruby_version_check.rb: -------------------------------------------------------------------------------- 1 | if RUBY_VERSION < '1.9.3' 2 | $stderr.puts "Your Ruby version(#{RUBY_VERSION}) is NOT supported, please upgrade!" 3 | exit 1 4 | end 5 | -------------------------------------------------------------------------------- /lib/burr/server.rb: -------------------------------------------------------------------------------- 1 | require 'webrick' 2 | 3 | module Burr 4 | module Server 5 | 6 | include WEBrick 7 | 8 | def self.start! 9 | destination = File.join(Dir.pwd, '/outputs/site') 10 | 11 | # recreate NondisclosureName under utf-8 circumstance 12 | fh_option = WEBrick::Config::FileHandler 13 | fh_option[:NondisclosureName] = ['.ht*','~*'] 14 | 15 | s = HTTPServer.new( 16 | :Port => 8000, 17 | :BindAddress => '0.0.0.0', 18 | ) 19 | 20 | s.mount('/', HTTPServlet::FileHandler, destination, fh_option) 21 | t = Thread.new { s.start } 22 | trap("INT") { s.shutdown } 23 | t.join() 24 | end 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/burr/ui.rb: -------------------------------------------------------------------------------- 1 | # Much of these code stole from bundler. 2 | 3 | module Burr 4 | class UI 5 | 6 | attr_accessor :shell 7 | 8 | def initialize 9 | Thor::Base.shell = Thor::Shell::Basic if !STDOUT.tty? 10 | @shell = Thor::Base.shell.new 11 | end 12 | 13 | def info(msg, newline = nil) 14 | tell_me(msg, nil, newline) 15 | end 16 | 17 | def confirm(msg, newline = nil) 18 | tell_me(msg, :green, newline) 19 | end 20 | 21 | def warn(msg, newline = nil) 22 | tell_me(msg, :yellow, newline) 23 | end 24 | 25 | def error(msg, newline = nil) 26 | tell_me(msg, :red, newline) 27 | end 28 | 29 | def debug(msg, newline = nil) 30 | tell_me(msg, nil, newline) if debug? 31 | end 32 | 33 | def trace(e, newline = nil) 34 | msg = ["#{e.class}: #{e.message}", *e.backtrace].join("\n") 35 | tell_me(msg, nil, newline) 36 | end 37 | 38 | private 39 | 40 | # valimism 41 | def tell_me(msg, color = nil, newline = nil) 42 | newline.nil? ? @shell.say(msg, color) : @shell.say(msg, color, newline) 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/burr/version.rb: -------------------------------------------------------------------------------- 1 | module Burr 2 | module Version 3 | MAJOR = '0' 4 | MINOR = '0' 5 | PATCH = '3' 6 | STRING = "#{MAJOR}.#{MINOR}.#{PATCH}" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /resources/locales/labels/en.yml: -------------------------------------------------------------------------------- 1 | id: 2 | - '{{ item.element }}-{{ item.number }}' 3 | - 'section-{{ item.counters | join:"-" }}' # section-1-1 4 | 5 | appendix: 6 | - 'Appendix {{ item.number }}' 7 | - '{{ item.counters | join:"." }}' # A.1 8 | 9 | chapter: 10 | - 'Chapter {{ item.number }}' 11 | - '{{ item.counters | join:"." }}' # 1.1 12 | 13 | part: 'Part {{ item.number }}' 14 | figure: 'Figure {{ element.number }}.{{ item.number }}' 15 | table: 'Table {{ element.number }}.{{ item.number }}' 16 | codeblock: 'Sample {{ item.number }}.{{ item.counter }}' 17 | 18 | acknowledgement: '' 19 | afterword: '' 20 | author: '' 21 | conclusion: '' 22 | cover: '' 23 | dedication: '' 24 | edition: '' 25 | epilogue: '' 26 | foreword: '' 27 | glossary: '' 28 | introduction: '' 29 | preface: '' 30 | prologue: '' 31 | title: '' 32 | toc: '' 33 | 34 | # Navigation elements 35 | index: 'Index' 36 | next: 'Next' 37 | previous: 'Previous' 38 | 39 | # Special 40 | date_format: 'm/d/Y' 41 | datetime_format: 'm/d/Y H:i' 42 | 43 | # Other 44 | by: 'by' 45 | page: 'page' 46 | -------------------------------------------------------------------------------- /resources/locales/labels/zh_CN.yml: -------------------------------------------------------------------------------- 1 | id: 2 | - '{{ item.element }}-{{ item.number }}' 3 | - 'section-{{ item.counters | join:"-" }}' # section-1-1 4 | 5 | appendix: 6 | - '附录 {{ item.number }}' 7 | - '{{ item.counters | join:"." }}' # A.1 8 | 9 | chapter: 10 | - '第 {{ item.number }} 章' 11 | - '{{ item.counters | join:"." }}' # 1.1 12 | 13 | part: '第 {{ item.number }} 部分:' 14 | figure: '图 {{ item.number }}.{{ item.counter }}:' 15 | table: '表格 {{ item.number }}.{{ item.counter }}:' 16 | codeblock: '代码 {{ item.number }}.{{ item.counter }}:' 17 | aside: '旁注 {{ item.number }}.{{ item.counter }}:' 18 | 19 | acknowledgement: '' 20 | afterword: '' 21 | author: '' 22 | conclusion: '' 23 | cover: '' 24 | dedication: '' 25 | edition: '' 26 | epilogue: '' 27 | foreword: '' 28 | glossary: '' 29 | introduction: '' 30 | preface: '' 31 | prologue: '' 32 | title: '' 33 | toc: '' 34 | 35 | # Navigation elements 36 | index: '索引' 37 | next: '下一页' 38 | previous: '上一页' 39 | 40 | # Special 41 | date_format: 'm/d/Y' 42 | datetime_format: 'm/d/Y H:i' 43 | 44 | # Other 45 | by: 'by' 46 | page: 'page' 47 | -------------------------------------------------------------------------------- /resources/locales/titles/en.yml: -------------------------------------------------------------------------------- 1 | # English translations of default titles 2 | 3 | acknowledgement: 'Acknowledgements' 4 | afterword: 'Afterword' 5 | author: 'About the author' 6 | blank: 'This Page Intentionally Left Blank' 7 | conclusion: 'Conclusion' 8 | cover: 'Cover' 9 | dedication: 'Dedication' 10 | edition: 'About this edition' 11 | epilogue: 'Epilogue' 12 | foreword: 'Foreword' 13 | glossary: 'Glossary' 14 | introduction: 'Introduction' 15 | license: 'License' 16 | lof: 'List of Figures' 17 | lot: 'List of Tables' 18 | preface: 'Preface' 19 | prologue: 'Prologue' 20 | title: 'Title' 21 | toc: 'Table of contents' 22 | -------------------------------------------------------------------------------- /resources/locales/titles/zh_CN.yml: -------------------------------------------------------------------------------- 1 | # Simplified Chinese translations of default titles 2 | 3 | acknowledgement: '致谢' 4 | afterword: '后记' 5 | author: '作者简介' 6 | blank: '此页留白' 7 | conclusion: '总论' 8 | cover: '封面' 9 | dedication: 'Dedication' 10 | edition: 'About this edition' 11 | epilogue: 'Epilogue' 12 | foreword: '续' 13 | glossary: '词汇表' 14 | introduction: 'Introduction' 15 | license: 'License' 16 | lof: 'List of Figures' 17 | lot: 'List of Tables' 18 | preface: '前言' 19 | prologue: 'Prologue' 20 | title: 'Title' 21 | toc: '目录' 22 | -------------------------------------------------------------------------------- /resources/templates/epub/_layout.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {% block title %}{% endblock %} 9 | 10 | 11 | 12 | 13 |
14 | {% block body %}{% endblock %} 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/templates/epub/acknowledgement.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/afterword.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/appendix.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/author.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/chapter.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{item.label }} {{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.label }} {{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/conclusion.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/cover.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 | cover 8 |
9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /resources/templates/epub/dedication.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/edition.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/epilogue.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/foreword.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/glossary.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/introduction.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/license.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/lof.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{ item.title }}

4 | 5 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /resources/templates/epub/lot.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{ item.title }}

4 | 5 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /resources/templates/epub/part.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.label }} {{ item.title }}

3 | {{ item.content }} 4 |
5 | -------------------------------------------------------------------------------- /resources/templates/epub/preface.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ item.content }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/epub/prologue.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/epub/table.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% if item.caption != '' %} 3 |

{{ item.label }} {{ item.caption }}

4 | {% endif %} 5 | 6 | {{ item.content }} 7 |
8 | -------------------------------------------------------------------------------- /resources/templates/epub/title.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ item.content }} 3 |
4 | -------------------------------------------------------------------------------- /resources/templates/epub/toc.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.title }}

8 | {{ toc }} 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /resources/templates/pdf/_item.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.title }}

3 | {% block content %} 4 | {{ item.content }} 5 | {% endblock %} 6 |
7 | -------------------------------------------------------------------------------- /resources/templates/pdf/acknowledgement.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/afterword.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/appendix.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.label }} {{ item.title }}

3 | {{ item.content }} 4 |
5 | -------------------------------------------------------------------------------- /resources/templates/pdf/author.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/blank.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.title }}

3 |
4 | -------------------------------------------------------------------------------- /resources/templates/pdf/book.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ config.title }} 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | {% if frontmatter.size > 0 %} 18 |
19 | {% for item in frontmatter %} 20 | {{ item.content }} 21 | {% endfor %} 22 |
23 | {% endif %} 24 | 25 | {% if bodymatter.size > 0 %} 26 |
27 | {% for item in bodymatter %} 28 | {{ item.content }} 29 | {% endfor %} 30 |
31 | {% endif %} 32 | 33 | {% if backmatter.size > 0 %} 34 |
35 | {% for item in backmatter %} 36 | {{ item.content }} 37 | {% endfor %} 38 |
39 | {% endif %} 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resources/templates/pdf/chapter.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.label }} {{ item.title }}

3 | {{ item.content }} 4 |
5 | -------------------------------------------------------------------------------- /resources/templates/pdf/code.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ item.content }} 3 |
4 | -------------------------------------------------------------------------------- /resources/templates/pdf/conclusion.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/cover.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% if config.edition %}

{{ config.edition }}

{% endif %} 3 |

{{ config.title }}

4 | {% if config.subtitle %}

{{ config.subtitle }}

{% endif %} 5 |

{{ config.author }}{% if config.translator %}  {{ config.translator }}{% endif %}

6 |
7 | -------------------------------------------------------------------------------- /resources/templates/pdf/dedication.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ item.content }} 3 |
4 | -------------------------------------------------------------------------------- /resources/templates/pdf/edition.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/epilogue.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/foreword.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/glossary.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/introduction.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/license.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/lof.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{ item.title }}

4 | 5 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /resources/templates/pdf/lot.liquid: -------------------------------------------------------------------------------- 1 |
2 | 3 |

{{ item.title }}

4 | 5 | 23 | 24 |
25 | -------------------------------------------------------------------------------- /resources/templates/pdf/part.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.label }} {{ item.title }}

3 | {{ item.content }} 4 |
5 | -------------------------------------------------------------------------------- /resources/templates/pdf/preface.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/prologue.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'item' %} 2 | -------------------------------------------------------------------------------- /resources/templates/pdf/table.liquid: -------------------------------------------------------------------------------- 1 |
2 | {% if item.caption != '' %} 3 |

{{ item.label }} {{ item.caption }}

4 | {% endif %} 5 | 6 | {{ item.content }} 7 |
8 | -------------------------------------------------------------------------------- /resources/templates/pdf/title.liquid: -------------------------------------------------------------------------------- 1 |
2 | {{ item.content }} 3 |
4 | -------------------------------------------------------------------------------- /resources/templates/pdf/toc.liquid: -------------------------------------------------------------------------------- 1 |
2 |

{{ item.title }}

3 | {{ toc }} 4 |
5 | -------------------------------------------------------------------------------- /resources/templates/site/_layout.liquid: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}{{ config.title }}{% endblock %} 6 | 7 | 8 | 9 | 10 | 11 |
12 | {% block body %}{% endblock %} 13 | {% block navigation %} 14 | {% if item.pre or item.nxt %} 15 | 23 | {% endif %} 24 | {% endblock %} 25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/templates/site/author.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.label }} {{ item.title }}

8 | {{ item.toc }} 9 |
10 | {{ item.content }} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /resources/templates/site/chapter.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{item.label }} {{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.label }} {{ item.title }}

8 | {{ item.toc }} 9 |
10 | {{ item.content }} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /resources/templates/site/foreword.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.label }} {{ item.title }}

8 | {{ item.toc }} 9 |
10 | {{ item.content }} 11 |
12 |
13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /resources/templates/site/preface.liquid: -------------------------------------------------------------------------------- 1 | {% extends 'layout' %} 2 | 3 | {% block title %}{{ item.title }}{% endblock %} 4 | 5 | {% block body %} 6 |
7 |

{{ item.label }} {{ item.title }}

8 | {{ item.toc }} 9 |
10 | {{ item.content }} 11 |
12 |
13 | {% endblock %} 14 | --------------------------------------------------------------------------------