class Softcover::BookManifest
Constants
- TXT_PATH
- YAML_PATH
Attributes
Public Class Methods
Changes the directory until in the book's root directory.
# File lib/softcover/book_manifest.rb, line 351 def self.find_book_root! loop do return true if valid_directory? return not_found! if Dir.pwd == '/' Dir.chdir '..' end end
# File lib/softcover/book_manifest.rb, line 104 def initialize(options = {}) @source = options[:source] || :polytex @origin = options[:origin] @book_file = TXT_PATH ensure_template_files if ungenerated_markdown? puts "Error: No book to publish" puts "Run `softcover build:<format>` for at least one format" exit 1 end yaml_attrs = read_from_yml attrs = case when polytex? then yaml_attrs when markdown? then yaml_attrs.merge(read_from_md) else self.class.not_found! end.symbolize_keys! marshal_load attrs write_master_latex_file(self) if polytex? tex_filename = filename + '.tex' self.chapters = [] self.frontmatter = [] base_contents = File.read(tex_filename) if base_contents.match(/frontmatter/) @frontmatter = true chapters.push Chapter.new(slug: 'frontmatter', title: language_labels["frontmatter"], sections: nil, chapter_number: 0) end raw_frontmatter = remove_frontmatter(base_contents, frontmatter) if frontmatter? self.frontmatter = chapter_includes(raw_frontmatter) else self.frontmatter = [] end chapter_includes(base_contents).each_with_index do |name, i| slug = File.basename(name, '.*') chapter_title_regex = /^\s*\\chapter{(.*)}/ filename = File.join(polytex_dir, slug + '.tex') content = File.read(filename) chapter_title = content[chapter_title_regex, 1] if article? && @origin == :markdown if chapter_title.nil? # Articles are "chapters" with the title of the full document. chapter_title = title else # Override the title based on the value of the top-level heading. self.title = chapter_title # Overwrite book.yml with the new title. book_yml = File.read(YAML_PATH) File.write(YAML_PATH, book_yml.sub(/title: .*/, "title: #{title}")) # Strip out the chapter line, which is invalid in articles. File.write(filename, content.sub(chapter_title_regex, '')) end end j = 0 sections = content.scan(/^\s*\\section{(.*)}/).flatten.map do |name| Section.new(name: name, section_number: j += 1) end chapter_title = title if article? chapters.push Chapter.new(slug: slug, title: chapter_title, sections: sections, chapter_number: i + 1) end end write_master_latex_file(self) verify_paths! if options[:verify_paths] end
# File lib/softcover/book_manifest.rb, line 359 def self.not_found! raise NotFound end
# File lib/softcover/book_manifest.rb, line 341 def self.valid_directory? # Needed for backwards compatibility if File.exist?('book.yml') && !Dir.pwd.include?('config') Softcover::Utils.mkdir('config') FileUtils.mv('book.yml', 'config') end [YAML_PATH, TXT_PATH].any? { |f| File.exist?(f) } end
Public Instance Methods
# File lib/softcover/book_manifest.rb, line 371 def basenames source_files.map { |file| File.basename(file, '.*') } end
Returns an iterator for the chapter file paths.
# File lib/softcover/book_manifest.rb, line 269 def chapter_file_paths pdf_chapter_names.map do |name| file_path = case when markdown? || @origin == :markdown chapter = chapters.find { |chapter| chapter.slug == name } extension = chapter.nil? ? '.md' : chapter.extension File.join("chapters", "#{name}#{extension}") when polytex? File.join("chapters", "#{name}.tex") end yield file_path if block_given? file_path end end
Returns an array of the chapters to include.
# File lib/softcover/book_manifest.rb, line 224 def chapter_includes(string) chapter_regex = /^\s*\\include\{#{polytex_dir}\/(.*?)\}/ string.scan(chapter_regex).flatten end
# File lib/softcover/book_manifest.rb, line 379 def chapter_objects basenames.zip(extensions).map do |name, extension| Chapter.new(slug: name, extension: extension) end end
Ensures the existence of needed template files like 'marketing.yml'. We copy from the template if necessary. Needed for backwards compatibility.
# File lib/softcover/book_manifest.rb, line 184 def ensure_template_files self.class.find_book_root! template_dir = Softcover::Utils.template_dir(article: Softcover::Utils.article?) files = [File.join(Softcover::Directories::CONFIG, 'marketing.yml'), path('images/cover-web.png'), path('latex_styles/custom_pdf.sty'), path('latex_styles/applekeys.sty'), path('config/preamble.tex'), path('config/lang.yml'), path('epub/OEBPS/styles/custom_epub.css'), path('epub/OEBPS/styles/page-template.xpgt'), ] files.each do |file| unless File.exist?(file) puts "Copying missing file '#{file}' from template" FileUtils.mkdir_p(File.dirname(file)) FileUtils.cp(File.join(template_dir, file), file) end end end
# File lib/softcover/book_manifest.rb, line 20 def escaped_title CGI.escape_html(title) end
# File lib/softcover/book_manifest.rb, line 375 def extensions source_files.map { |file| File.extname(file) } end
# File lib/softcover/book_manifest.rb, line 307 def find_chapter_by_number(number) chapters.find { |chapter| chapter.chapter_number == number } end
# File lib/softcover/book_manifest.rb, line 303 def find_chapter_by_slug(slug) chapters.find { |chapter| chapter.slug == slug } end
Returns the first full chapter. This arranges to skip the frontmatter, if any.
# File lib/softcover/book_manifest.rb, line 253 def first_chapter frontmatter? ? chapters[1] : chapters[0] end
Returns true if the book has frontmatter.
# File lib/softcover/book_manifest.rb, line 247 def frontmatter? @frontmatter end
Returns the name of the HTML file containing the full book.
# File lib/softcover/book_manifest.rb, line 287 def full_html_file path("html/#{slug}.#{html_extension}") end
Run the title through the Polytexnic pipeline to make an HTML title.
# File lib/softcover/book_manifest.rb, line 25 def html_title polytexnic_html(title) end
Returns true if converting Markdown source.
# File lib/softcover/book_manifest.rb, line 258 def markdown? @source == :markdown || @source == :md end
Returns the full chapter filenames for the PDF.
# File lib/softcover/book_manifest.rb, line 299 def pdf_chapter_filenames pdf_chapter_names.map { |name| File.join(polytex_dir, "#{name}.tex") } end
Returns chapters for the PDF.
# File lib/softcover/book_manifest.rb, line 292 def pdf_chapter_names chaps = chapters.reject { |chapter| chapter.slug == 'frontmatter' }. collect(&:slug) frontmatter? ? frontmatter + chaps : chaps end
Returns true if converting PolyTeX source.
# File lib/softcover/book_manifest.rb, line 264 def polytex? @source == :polytex end
Returns the directory where the LaTeX files are located. We put them in the a separate directory when using them as an intermediate format when working with Markdown books. Otherwise, we use the chapters directory, which is the default location when writing LaTeX/PolyTeX books.
# File lib/softcover/book_manifest.rb, line 217 def polytex_dir dir = (markdown? || @origin == :markdown) ? 'generated_polytex' : 'chapters' mkdir dir dir end
Returns the chapter range for book previews. We could `eval` the range, but that would allow users to execute arbitrary code (maybe not a big problem on their system, but it would be a Bad Thing on a server).
# File lib/softcover/book_manifest.rb, line 324 def preview_chapter_range unless respond_to?(:epub_mobi_preview_chapter_range) $stderr.puts("Error: Preview not built") $stderr.puts("Define epub_mobi_preview_chapter_range in config/book.yml") $stderr.puts("See http://manual.softcover.io/book/getting_started#sec-build_preview") exit(1) end first, last = epub_mobi_preview_chapter_range.split('..').map(&:to_i) first..last end
Returns the chapters to use in the preview as a range.
# File lib/softcover/book_manifest.rb, line 337 def preview_chapters chapters[preview_chapter_range] end
# File lib/softcover/book_manifest.rb, line 385 def read_from_md { chapters: chapter_objects, filename: book_file } end
Removes frontmatter. The frontmatter shouldn't be included in the chapter slugs, so we remove it. For example, in
\frontmatter \maketitle \tableofcontents % List frontmatter sections here (preface, foreword, etc.). \include{chapters/preface} \mainmatter % List chapters here in the order they should appear in the book. \include{chapters/a_chapter}
we don't want to include the preface.
# File lib/softcover/book_manifest.rb, line 241 def remove_frontmatter(base_contents, frontmatter) base_contents.gsub!(/\\frontmatter(.*)\\mainmatter/m, '') $1 end
Returns the source files specified by Book.txt. Allows a mixture of Markdown and PolyTeX files.
# File lib/softcover/book_manifest.rb, line 365 def source_files self.class.find_book_root! md_tex = /.*(?:\.md|\.tex)/ book_file_lines(self).select { |path| path =~ md_tex }.map(&:strip) end
Handles case of Markdown books without running `softcover build`.
# File lib/softcover/book_manifest.rb, line 207 def ungenerated_markdown? dir = 'generated_polytex' @origin == :markdown && (!File.directory?(dir) || Dir.glob(path("#{dir}/*")).empty?) end
Returns a URL for the chapter with the given number.
# File lib/softcover/book_manifest.rb, line 312 def url(chapter_number) if (chapter = find_chapter_by_number(chapter_number)) chapter.slug else '#' end end
Private Instance Methods
Ensures that the book.yml file is in the right directory. This is for backwards compatibility.
# File lib/softcover/book_manifest.rb, line 402 def ensure_book_yml path = self.class::YAML_PATH unless File.exist?(path) base = File.basename(path) Softcover::Utils.mkdir Softcover::Directories::CONFIG FileUtils.mv base, Softcover::Directories::CONFIG end end
# File lib/softcover/book_manifest.rb, line 392 def read_from_yml require 'softcover/config' require 'yaml/store' self.class.find_book_root! ensure_book_yml YAML.load_file(self.class::YAML_PATH) end
# File lib/softcover/book_manifest.rb, line 412 def verify_paths! chapter_file_paths do |chapter_path| next if chapter_path =~ /frontmatter/ unless File.exist?(chapter_path) $stderr.puts "ERROR -- document not built" $stderr.puts "Chapter file '#{chapter_path}'' not found" exit 1 end end end