Class: Softcover::BookManifest

Inherits:
OpenStruct
  • Object
show all
Includes:
Utils
Defined in:
lib/softcover/book_manifest.rb

Direct Known Subclasses

MarketingManifest

Defined Under Namespace

Classes: Chapter, NotFound, Section

Constant Summary collapse

TXT_PATH =
'Book.txt'
YAML_PATH =
File.join(Softcover::Directories::CONFIG, 'book.yml')

Constants included from Utils

Utils::UNITS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#add_highlight_class!, #article?, #as_size, #book_file_lines, #chapter_label, #commands, #current_book, #dependency_filename, #digest, #executable, #execute, #filename_or_default, #first_path, #get_filename, #html_extension, #in_book_directory?, #language_labels, #linux?, #logged_in?, #master_content, #master_filename, #master_latex_header, #mkdir, #non_comment_lines, #os_x?, #path, #polytexnic_html, #raw_lines, #reset_current_book!, #rm, #rm_r, #silence, #silence_stream, #source, #template_dir, #tmpify, #unpublish_slug, #write_master_latex_file, #write_pygments_file

Constructor Details

#initialize(options = {}) ⇒ BookManifest

Returns a new instance of BookManifest.



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# 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

Instance Attribute Details

#book_fileObject

Returns the value of attribute book_file.



10
11
12
# File 'lib/softcover/book_manifest.rb', line 10

def book_file
  @book_file
end

Class Method Details

.find_book_root!Object

Changes the directory until in the book’s root directory.



351
352
353
354
355
356
357
# 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

.not_found!Object

Raises:



359
360
361
# File 'lib/softcover/book_manifest.rb', line 359

def self.not_found!
  raise NotFound
end

.valid_directory?Boolean

Returns:

  • (Boolean)


341
342
343
344
345
346
347
348
# 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

Instance Method Details

#basenamesObject



371
372
373
# File 'lib/softcover/book_manifest.rb', line 371

def basenames
  source_files.map { |file| File.basename(file, '.*') }
end

#chapter_file_pathsObject

Returns an iterator for the chapter file paths.



269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# 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

#chapter_includes(string) ⇒ Object

Returns an array of the chapters to include.



224
225
226
227
# File 'lib/softcover/book_manifest.rb', line 224

def chapter_includes(string)
  chapter_regex = /^\s*\\include\{#{polytex_dir}\/(.*?)\}/
  string.scan(chapter_regex).flatten
end

#chapter_objectsObject



379
380
381
382
383
# 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

#ensure_template_filesObject

Ensures the existence of needed template files like ‘marketing.yml’. We copy from the template if necessary. Needed for backwards compatibility.



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# 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

#escaped_titleObject



20
21
22
# File 'lib/softcover/book_manifest.rb', line 20

def escaped_title
  CGI.escape_html(title)
end

#extensionsObject



375
376
377
# File 'lib/softcover/book_manifest.rb', line 375

def extensions
  source_files.map { |file| File.extname(file) }
end

#find_chapter_by_number(number) ⇒ Object



307
308
309
# File 'lib/softcover/book_manifest.rb', line 307

def find_chapter_by_number(number)
  chapters.find { |chapter| chapter.chapter_number == number }
end

#find_chapter_by_slug(slug) ⇒ Object



303
304
305
# File 'lib/softcover/book_manifest.rb', line 303

def find_chapter_by_slug(slug)
  chapters.find { |chapter| chapter.slug == slug }
end

#first_chapterObject

Returns the first full chapter. This arranges to skip the frontmatter, if any.



253
254
255
# File 'lib/softcover/book_manifest.rb', line 253

def first_chapter
  frontmatter? ? chapters[1] : chapters[0]
end

#frontmatter?Boolean

Returns true if the book has frontmatter.

Returns:

  • (Boolean)


247
248
249
# File 'lib/softcover/book_manifest.rb', line 247

def frontmatter?
  @frontmatter
end

#full_html_fileObject

Returns the name of the HTML file containing the full book.



287
288
289
# File 'lib/softcover/book_manifest.rb', line 287

def full_html_file
  path("html/#{slug}.#{html_extension}")
end

#html_titleObject

Run the title through the Polytexnic pipeline to make an HTML title.



25
26
27
# File 'lib/softcover/book_manifest.rb', line 25

def html_title
  polytexnic_html(title)
end

#markdown?Boolean Also known as: md?

Returns true if converting Markdown source.

Returns:

  • (Boolean)


258
259
260
# File 'lib/softcover/book_manifest.rb', line 258

def markdown?
  @source == :markdown || @source == :md
end

#pdf_chapter_filenamesObject

Returns the full chapter filenames for the PDF.



299
300
301
# File 'lib/softcover/book_manifest.rb', line 299

def pdf_chapter_filenames
  pdf_chapter_names.map { |name| File.join(polytex_dir, "#{name}.tex") }
end

#pdf_chapter_namesObject

Returns chapters for the PDF.



292
293
294
295
296
# File 'lib/softcover/book_manifest.rb', line 292

def pdf_chapter_names
  chaps = chapters.reject { |chapter| chapter.slug.match(/frontmatter/) }.
                   collect(&:slug)
  frontmatter? ? frontmatter + chaps : chaps
end

#polytex?Boolean

Returns true if converting PolyTeX source.

Returns:

  • (Boolean)


264
265
266
# File 'lib/softcover/book_manifest.rb', line 264

def polytex?
  @source == :polytex
end

#polytex_dirObject

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.



217
218
219
220
221
# File 'lib/softcover/book_manifest.rb', line 217

def polytex_dir
  dir = (markdown? || @origin == :markdown) ? 'generated_polytex' : 'chapters'
  mkdir dir
  dir
end

#preview_chapter_rangeObject

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).



324
325
326
327
328
329
330
331
332
333
334
# 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

#preview_chaptersObject

Returns the chapters to use in the preview as a range.



337
338
339
# File 'lib/softcover/book_manifest.rb', line 337

def preview_chapters
  chapters[preview_chapter_range]
end

#read_from_mdObject



385
386
387
# File 'lib/softcover/book_manifest.rb', line 385

def read_from_md
  { chapters: chapter_objects, filename: book_file }
end

#remove_frontmatter(base_contents, frontmatter) ⇒ Object

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.



241
242
243
244
# File 'lib/softcover/book_manifest.rb', line 241

def remove_frontmatter(base_contents, frontmatter)
  base_contents.gsub!(/\\frontmatter(.*)\\mainmatter/m, '')
  $1
end

#source_filesObject

Returns the source files specified by Book.txt. Allows a mixture of Markdown and PolyTeX files.



365
366
367
368
369
# 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

#ungenerated_markdown?Boolean

Handles case of Markdown books without running ‘softcover build`.

Returns:

  • (Boolean)


207
208
209
210
211
# 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

#url(chapter_number) ⇒ Object

Returns a URL for the chapter with the given number.



312
313
314
315
316
317
318
# 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