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)


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

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



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

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



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

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



282
283
284
# File 'lib/softcover/utils.rb', line 282

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.



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

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.



330
331
332
333
# File 'lib/softcover/utils.rb', line 330

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.



319
320
321
322
323
324
325
326
327
# File 'lib/softcover/utils.rb', line 319

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.



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

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