Module: Softcover::Utils

Constant Summary collapse

UNITS =
%W(B KB MB GB TB).freeze

Instance Method Summary collapse

Instance Method Details

#add_highlight_class!(pygments_css) ⇒ Object

Adds a ‘highlight’ class for MathJax compatibility.



168
169
170
# File 'lib/softcover/utils.rb', line 168

def add_highlight_class!(pygments_css)
  pygments_css.gsub!(/^/, '.highlight ')
end

#article?Boolean

Returns true if document is an article.

Returns:

  • (Boolean)


317
318
319
# File 'lib/softcover/utils.rb', line 317

def article?
  !!File.readlines(path('config/preamble.tex')).first.match(/extarticle/)
end

#as_size(number) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/softcover/utils.rb', line 50

def as_size(number)
  if number.to_i < 1024
    exponent = 0

  else
    max_exp  = UNITS.size - 1

    exponent = ( Math.log( number ) / Math.log( 1024 ) ).to_i
    exponent = max_exp if exponent > max_exp

    number  /= 1024 ** exponent
  end

  "#{number.round} #{UNITS[ exponent ]}"
end

#book_file_lines(manifest) ⇒ Object

Returns the lines of book file as an array, removing commented-out lines.



82
83
84
# File 'lib/softcover/utils.rb', line 82

def book_file_lines(manifest)
  non_comment_lines(raw_lines(manifest))
end

#chapter_label(chapter_number) ⇒ Object



302
303
304
305
306
307
308
# File 'lib/softcover/utils.rb', line 302

def chapter_label(chapter_number)
  if language_labels["chapter"]["order"] == "reverse"
    "#{chapter_number} #{language_labels['chapter']['word']}"
  else
    "#{language_labels['chapter']['word']} #{chapter_number}"
  end
end

#commands(lines) ⇒ Object

Returns the commands from the given lines. We skip comments and blank lines.



240
241
242
243
# File 'lib/softcover/utils.rb', line 240

def commands(lines)
  skip = /(^\s*#|^\s*$)/
  lines.reject { |line| line =~ skip }.join("\n")
end

#current_bookObject



4
5
6
7
8
9
# File 'lib/softcover/utils.rb', line 4

def current_book
  # using module level variable because it should be context independent
  @@current_book ||= begin
    in_book_directory? ? Softcover::Book.new(origin: source) : false
  end
end

#dependency_filename(label) ⇒ Object

Returns the filename of a dependency given a label.



253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/softcover/utils.rb', line 253

def dependency_filename(label)
  case label
  when :latex
    get_filename(:xelatex)
  when :ghostscript
    get_filename(:gs)
  when :calibre
    get_filename(:'ebook-convert')
  when :epubcheck
    # Finds EpubCheck anywhere on the path.
    version_4 = path('epubcheck-4.0.2/epubcheck.jar')
    first_path(version_4) || get_filename(:'epubcheck') || ""
  when :inkscape
    default = '/Applications/Inkscape.app/Contents/Resources/bin/inkscape'
    filename_or_default(:inkscape, default)
  when :phantomjs
    phantomjs = get_filename(label)
    # Test for version 2, which is now necessary.
    version = `#{phantomjs} -v`.scan(/^(\d)\./).flatten.first.to_i rescue nil
    if version == 2
      phantomjs
    else
      ""
    end
  when :python2
    python = get_filename(:python)
    # Python 2 stupidly outputs the version number to STDERR instead of STDOUT.
    require 'open3'
    stdout, stderr, status = Open3.capture3("#{python} --version")
    stderr.match(/Python 2/) ? python : ""
  else
    get_filename(label)
  end
end

#digest(string) ⇒ Object

Returns a digest of the string.



173
174
175
# File 'lib/softcover/utils.rb', line 173

def digest(string)
  Digest::SHA1.hexdigest(string)
end

#executable(filename) ⇒ Object

Returns the executable if it exists, raising an error otherwise.



178
179
180
181
182
183
184
185
186
# File 'lib/softcover/utils.rb', line 178

def executable(filename)
  filename.tap do |f|
    unless File.exist?(f)
      $stderr.puts "Document not built due to missing dependency"
      $stderr.puts "Run `softcover check` to check dependencies"
      exit 1
    end
  end
end

#execute(command) ⇒ Object

Execute a command. The issue here is that ‘exec` is awful in tests, since it exits the process. This command arranges to use `system` in tests instead.



216
217
218
# File 'lib/softcover/utils.rb', line 216

def execute(command)
  Softcover.test? ? system(command) : exec(command)
end

#filename_or_default(name, default) ⇒ Object

Returns the filename if it exists on the path and a default otherwise.



293
294
295
# File 'lib/softcover/utils.rb', line 293

def filename_or_default(name, default)
  (f = get_filename(name)).empty? ? default : f
end

#first_path(file) ⇒ Object

Returns first location on the path for a given file.



246
247
248
249
250
# File 'lib/softcover/utils.rb', line 246

def first_path(file)
  possible_paths = ENV['PATH'].split(File::PATH_SEPARATOR).
                                     collect { |x| File.join(x, file) }
  possible_paths.find { |f| File.file?(f) }
end

#get_filename(name) ⇒ Object



288
289
290
# File 'lib/softcover/utils.rb', line 288

def get_filename(name)
  `which #{name}`.chomp
end

#html_extensionObject



44
45
46
# File 'lib/softcover/utils.rb', line 44

def html_extension
  'html'
end

#in_book_directory?Boolean

Returns:

  • (Boolean)


25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/softcover/utils.rb', line 25

def in_book_directory?
  Softcover::BookManifest::find_book_root!

  files = Dir['**/*']

  Softcover::FORMATS.each do |format|
    unless files.any?{ |file| File.extname(file) == ".#{format}" }
      puts "No #{format} found, skipping."
    end
  end

  return Softcover::BookManifest::valid_directory?
end

#language_labelsObject

Returns the language labels from the config file.



298
299
300
# File 'lib/softcover/utils.rb', line 298

def language_labels
  YAML.load_file(File.join(Softcover::Directories::CONFIG, 'lang.yml'))
end

#linux?Boolean

Returns true if platform is Linux.

Returns:

  • (Boolean)


234
235
236
# File 'lib/softcover/utils.rb', line 234

def linux?
  RUBY_PLATFORM.match(/linux/)
end

#logged_in?Boolean

Returns:

  • (Boolean)


39
40
41
42
# File 'lib/softcover/utils.rb', line 39

def logged_in?
  require 'softcover/config'
  Softcover::Config['api_key'].present?
end

#master_content(manifest) ⇒ Object

Returns the content for the master LaTeX file.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/softcover/utils.rb', line 98

def master_content(manifest)
  front_or_mainmatter = /(.*):\s*$/
  source_file = /(.*)(?:\.md|\.tex)\s*$/

  tex_file = [master_latex_header(manifest)]
  book_file_lines(manifest).each do |line|
    if line.match(source_file)
      tex_file << "\\include{#{manifest.polytex_dir}/#{$1}}"
    elsif line.match(front_or_mainmatter)  # frontmatter or mainmatter
      tex_file << "\\#{$1}"
    elsif line.strip == 'cover'
      tex_file << '\\includepdf{images/cover.pdf}'
    else # raw command, like 'maketitle' or 'tableofcontents'
      tex_file << "\\#{line.strip}"
    end
  end
  tex_file << '\end{document}'
  tex_file.join("\n") + "\n"
end

#master_filename(manifest) ⇒ Object

Returns the name of the master LaTeX file.



77
78
79
# File 'lib/softcover/utils.rb', line 77

def master_filename(manifest)
  "#{manifest.filename}.tex"
end

#master_latex_header(manifest) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/softcover/utils.rb', line 118

def master_latex_header(manifest)
  preamble = File.read(path('config/preamble.tex'))
  subtitle = manifest.subtitle.nil? ? "" : "\\subtitle{#{manifest.subtitle}}"
  <<-EOS
#{preamble}
\\usepackage{#{Softcover::Directories::STYLES}/softcover}
\\VerbatimFootnotes % Allows verbatim text in footnotes
\\title{#{manifest.title}}
#{subtitle}
\\author{#{manifest.author}}
\\date{#{manifest.date}}

\\begin{document}
  EOS
end

#mkdir(dir) ⇒ Object



188
189
190
# File 'lib/softcover/utils.rb', line 188

def mkdir(dir)
  Dir.mkdir(dir) unless File.directory?(dir)
end

#non_comment_lines(lines) ⇒ Object

Returns only non-comment lines.



87
88
89
90
# File 'lib/softcover/utils.rb', line 87

def non_comment_lines(lines)
  comment = /^\s*#.*$/
  lines.reject { |line| line.match(comment) }
end

#os_x?Boolean

Returns true if platform is OS X.

Returns:

  • (Boolean)


229
230
231
# File 'lib/softcover/utils.rb', line 229

def os_x?
  RUBY_PLATFORM.match(/darwin/)
end

#path(path_string = '') ⇒ Object

Returns the system-independent file path. It’s nicer to write ‘path(’foo/bar/baz’)‘ than `File.join(’foo’, ‘bar’, ‘baz’)‘.



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

def path(path_string='')
  File.join(*path_string.split('/'))
end

#polytexnic_html(text) ⇒ Object

Run text through the Polytexnic pipeline to make an HTML snippet.



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

def polytexnic_html(text)
  Nokogiri::HTML(Polytexnic::Pipeline.new(text).to_html).at_css('p')
                                                       .inner_html.strip
end

#raw_lines(manifest) ⇒ Object

Returns all the lines in Book.txt.



93
94
95
# File 'lib/softcover/utils.rb', line 93

def raw_lines(manifest)
  File.readlines(manifest.book_file)
end

#reset_current_book!Object



21
22
23
# File 'lib/softcover/utils.rb', line 21

def reset_current_book!
  @@current_book = nil
end

#rm(file) ⇒ Object

Removes a file (or list of files).



193
194
195
196
197
198
199
# File 'lib/softcover/utils.rb', line 193

def rm(file)
  if file.is_a?(Array)
    file.each { |f| rm(f) }
  else
    FileUtils.rm(file) if File.exist?(file)
  end
end

#rm_r(directory) ⇒ Object

Removes a directory recursively.



202
203
204
# File 'lib/softcover/utils.rb', line 202

def rm_r(directory)
  FileUtils.rm_r(directory) if File.directory?(directory)
end

#silenceObject



220
221
222
223
224
225
226
# File 'lib/softcover/utils.rb', line 220

def silence
  return yield if ENV['silence'] == 'false'

  silence_stream(STDOUT) do
    yield
  end
end

#silence_stream(stream) ⇒ Object

Silences a stream. This is taken directly from Rails Active Support ‘silence_stream`. The `silence_stream` method is deprecated because it’s not thread-safe, but we don’t care about that and the deprecation warnings are annoying.



325
326
327
328
329
330
331
332
333
# File 'lib/softcover/utils.rb', line 325

def silence_stream(stream)
  old_stream = stream.dup
  stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
  stream.sync = true
  yield
ensure
  stream.reopen(old_stream)
  old_stream.close
end

#sourceObject

Returns the source type (PolyTeX or Markdown) of the current book.



12
13
14
# File 'lib/softcover/utils.rb', line 12

def source
  Dir.glob(path('chapters/*.md')).empty? ? :polytex : :markdown
end

#template_dir(options) ⇒ Object

Returns the directory of the document template.



311
312
313
314
# File 'lib/softcover/utils.rb', line 311

def template_dir(options)
  doc = options[:article] ? 'article' : 'book'
  File.expand_path File.join(File.dirname(__FILE__), "#{doc}_template")
end

#tmpify(manifest, filename) ⇒ Object

Returns the tmp version of a filename. E.g., tmpify(‘foo.tex’) => ‘foo.tmp.tex’



136
137
138
139
140
141
142
# File 'lib/softcover/utils.rb', line 136

def tmpify(manifest, filename)
  tmp = Softcover::Directories::TMP
  mkdir tmp
  sep = File::SEPARATOR
  filename.sub(manifest.polytex_dir + sep, tmp + sep).
           sub('.tex', '.tmp.tex')
end

#unpublish_slugObject

Returns the slug to be unpublished.



17
18
19
# File 'lib/softcover/utils.rb', line 17

def unpublish_slug
  Softcover::BookManifest.new(origin: source).slug
end

#write_master_latex_file(manifest) ⇒ Object

Writes the master LaTeX file <name>.tex to use chapters from Book.txt. We skip this step if Book.txt doesn’t exist, as that means the user is writing raw LaTeX.



70
71
72
73
74
# File 'lib/softcover/utils.rb', line 70

def write_master_latex_file(manifest)
  if File.exist?(manifest.book_file)
    File.write(master_filename(manifest), master_content(manifest))
  end
end

#write_pygments_file(format, path) ⇒ Object

Writes a Pygments style file. We support both :html (outputting CSS) and :latex (outputting a LaTeX style file).



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/softcover/utils.rb', line 147

def write_pygments_file(format, path)
  require 'pygments'
  extension = case format
              when :html
                'css'
              when :latex
                'sty'
              end
  # Here we burrow into the private 'Pygments#mentos' method.
  # Pygments exposes a 'css' method to return the CSS,
  # but we want to be able to output a LaTeX style file as well.
  # The inclusion of the ':css' symbol is necessary but doesn't actually
  # result in CSS being output unless the format is 'html'.
  pygments = Pygments::Popen.new.send(:mentos, :css, [format.to_s, ''])
  add_highlight_class!(pygments) if format == :html
  File.open(File.join(path, "pygments.#{extension}"), 'w') do |f|
    f.write(pygments)
  end
end