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

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utils

#add_highlight_class!, #as_size, #book_txt_lines, #chapter_label, #commands, #current_book, #dependency_filename, #digest, #executable, #execute, #in_book_directory?, #language_labels, #linux?, #logged_in?, #master_content, #master_filename, #master_latex_header, #mkdir, #os_x?, #path, #raw_lines, #reset_current_book!, #rm, #silence, #source, #tmpify, #unpublish_slug, #write_master_latex_file, #write_pygments_file

Constructor Details

#initialize(options = {}) ⇒ BookManifest

Returns a new instance of BookManifest.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
# File 'lib/softcover/book_manifest.rb', line 80

def initialize(options = {})
  @source = options[:source] || :polytex
  @origin = options[:origin]

  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{(.*)}/
      content = File.read(File.join(polytex_dir, slug + '.tex'))
      chapter_title = content[chapter_title_regex, 1]
      j = 0
      sections = content.scan(/^\s*\\section{(.*)}/).flatten.map do |name|
        Section.new(name: name, section_number: j += 1)
      end
      chapters.push Chapter.new(slug: slug,
                                title: chapter_title,
                                sections: sections,
                                chapter_number: i + 1)
    end
  end
  verify_paths! if options[:verify_paths]
end

Class Method Details

.find_book_root!Object

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



295
296
297
298
299
300
301
# File 'lib/softcover/book_manifest.rb', line 295

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:



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

def self.not_found!
  raise NotFound
end

.valid_directory?Boolean

Returns:

  • (Boolean)


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

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



315
316
317
# File 'lib/softcover/book_manifest.rb', line 315

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

#chapter_file_pathsObject

Returns an iterator for the chapter file paths.



225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/softcover/book_manifest.rb', line 225

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.



180
181
182
183
# File 'lib/softcover/book_manifest.rb', line 180

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

#chapter_objectsObject



323
324
325
326
327
# File 'lib/softcover/book_manifest.rb', line 323

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.



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/softcover/book_manifest.rb', line 143

def ensure_template_files
  self.class.find_book_root!
  template_dir = File.join(File.dirname(__FILE__), 'template')
  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')
         ]
  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

#extensionsObject



319
320
321
# File 'lib/softcover/book_manifest.rb', line 319

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

#find_chapter_by_number(number) ⇒ Object



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

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

#find_chapter_by_slug(slug) ⇒ Object



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

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.



209
210
211
# File 'lib/softcover/book_manifest.rb', line 209

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

#frontmatter?Boolean

Returns true if the book has frontmatter.

Returns:

  • (Boolean)


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

def frontmatter?
  @frontmatter
end

#markdown?Boolean Also known as: md?

Returns true if converting Markdown source.

Returns:

  • (Boolean)


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

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

#pdf_chapter_filenamesObject

Returns the full chapter filenames for the PDF.



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

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

#pdf_chapter_namesObject

Returns chapters for the PDF.



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

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)


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

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.



173
174
175
176
177
# File 'lib/softcover/book_manifest.rb', line 173

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



275
276
277
278
# File 'lib/softcover/book_manifest.rb', line 275

def preview_chapter_range
  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.



281
282
283
# File 'lib/softcover/book_manifest.rb', line 281

def preview_chapters
  chapters[preview_chapter_range]
end

#read_from_mdObject



329
330
331
# File 'lib/softcover/book_manifest.rb', line 329

def read_from_md
  { chapters: chapter_objects, filename: TXT_PATH }
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.



197
198
199
200
# File 'lib/softcover/book_manifest.rb', line 197

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.



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

def source_files
  self.class.find_book_root!
  md_tex = /.*(?:\.md|\.tex)/
  book_txt_lines.select { |path| path =~ md_tex }.map(&:strip)
end

#ungenerated_markdown?Boolean

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

Returns:

  • (Boolean)


163
164
165
166
167
# File 'lib/softcover/book_manifest.rb', line 163

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.



263
264
265
266
267
268
269
# File 'lib/softcover/book_manifest.rb', line 263

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