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, #get_filename, #in_book_directory?, #language_labels, #linux?, #logged_in?, #master_content, #master_filename, #master_latex_header, #mkdir, #non_comment_lines, #os_x?, #path, #raw_lines, #reset_current_book!, #rm, #rm_r, #silence, #source, #template_dir, #tmpify, #unpublish_slug, #write_master_latex_file, #write_pygments_file

Constructor Details

#initialize(options = {}) ⇒ BookManifest

Returns a new instance of BookManifest.



100
101
102
103
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
# File 'lib/softcover/book_manifest.rb', line 100

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?
          # *** test
          # 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.



347
348
349
350
351
352
353
# File 'lib/softcover/book_manifest.rb', line 347

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:



355
356
357
# File 'lib/softcover/book_manifest.rb', line 355

def self.not_found!
  raise NotFound
end

.valid_directory?Boolean

Returns:

  • (Boolean)


337
338
339
340
341
342
343
344
# File 'lib/softcover/book_manifest.rb', line 337

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



367
368
369
# File 'lib/softcover/book_manifest.rb', line 367

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

#chapter_file_pathsObject

Returns an iterator for the chapter file paths.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/softcover/book_manifest.rb', line 265

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.



220
221
222
223
# File 'lib/softcover/book_manifest.rb', line 220

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

#chapter_objectsObject



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

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.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/softcover/book_manifest.rb', line 181

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('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



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

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

#find_chapter_by_number(number) ⇒ Object



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

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

#find_chapter_by_slug(slug) ⇒ Object



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

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.



249
250
251
# File 'lib/softcover/book_manifest.rb', line 249

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

#frontmatter?Boolean

Returns true if the book has frontmatter.

Returns:

  • (Boolean)


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

def frontmatter?
  @frontmatter
end

#full_html_fileObject

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



283
284
285
# File 'lib/softcover/book_manifest.rb', line 283

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

#markdown?Boolean Also known as: md?

Returns true if converting Markdown source.

Returns:

  • (Boolean)


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

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

#pdf_chapter_filenamesObject

Returns the full chapter filenames for the PDF.



295
296
297
# File 'lib/softcover/book_manifest.rb', line 295

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

#pdf_chapter_namesObject

Returns chapters for the PDF.



288
289
290
291
292
# File 'lib/softcover/book_manifest.rb', line 288

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)


260
261
262
# File 'lib/softcover/book_manifest.rb', line 260

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.



213
214
215
216
217
# File 'lib/softcover/book_manifest.rb', line 213

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



320
321
322
323
324
325
326
327
328
329
330
# File 'lib/softcover/book_manifest.rb', line 320

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.



333
334
335
# File 'lib/softcover/book_manifest.rb', line 333

def preview_chapters
  chapters[preview_chapter_range]
end

#read_from_mdObject



381
382
383
# File 'lib/softcover/book_manifest.rb', line 381

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.



237
238
239
240
# File 'lib/softcover/book_manifest.rb', line 237

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.



361
362
363
364
365
# File 'lib/softcover/book_manifest.rb', line 361

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)


203
204
205
206
207
# File 'lib/softcover/book_manifest.rb', line 203

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.



308
309
310
311
312
313
314
# File 'lib/softcover/book_manifest.rb', line 308

def url(chapter_number)
  if (chapter = find_chapter_by_number(chapter_number))
    chapter.slug
  else
    '#'
  end
end