class TeXzip::Project

Constants

IMAGE_EXTENSIONS
PACKAGE_EXTENSIONS
TEXIMAGE_EXTENSIONS

Attributes

overwrite_all[RW]

Public Class Methods

new(master_file) click to toggle source
Calls superclass method
# File lib/texzip/Project.rb, line 65
def initialize(master_file)
  super()
  @tex_master_file = Pathname.new(master_file).expand_path

  # All possible include-paths for TeX
  @tex_dirs = [@tex_master_file.dirname]
  @tex_dirs.concat((ENV['TEXINPUTS'] || '').split(':').map {|d| Pathname.new(d)})
  @tex_dirs.map!(&:expand_path)
  @tex_dirs.uniq!

  @overwrite_all = false

  parse_files
end

Public Instance Methods

add_bib(bib_file_name) click to toggle source

Adds a BibTeX-file to the list of included BibTeX-files. @param [String] image_file_name The path of the BibTeX-file.

# File lib/texzip/Project.rb, line 286
def add_bib(bib_file_name)
  bib_file = find_file(bib_file_name, ['.bib'])

  if bib_file.nil?
    puts "WARNING: Can't find included BibTeX file #{bib_file_name}"
  else
    @bib_files.add bib_file
  end
end
add_image(image_file_name) click to toggle source

Adds an image to the list of included images. @param [String] image_file_name The path of the image-file

# File lib/texzip/Project.rb, line 269
def add_image(image_file_name)
  ext = File.extname(image_file_name)
  image_files = if ext == ''
                  find_files(image_file_name, IMAGE_EXTENSIONS)
                else
                  [find_file(image_file_name)].compact
                end

  if image_files.empty?
    puts "WARNING: Can't find included image #{image_file_name}"
  else
    @image_files.merge image_files
  end
end
ask_overwrite(file) click to toggle source
# File lib/texzip/Project.rb, line 505
def ask_overwrite(file)
  if !@overwrite_all && File.exist?(file)
    ask("File #{file.relative_path_from(Pathname.getwd)} exists. Overwrite? [Ynaq]") do |q|
      q.character = true
      q.validate = /[ynaq\r ]/
      q.case = :down
      q.overwrite = false
      q.answer_type = lambda do |c|
        case c
        when 'q' then raise Quit
        when 'y' then true
        when 'n' then false
        when 'a' then
          @overwrite_all = true
          true
        end
      end
    end
  else
    true
  end
end
bib_files() click to toggle source

Returns a list of included BibTeX-files. @return [Array<Pathname>] Included BibTeX files.

# File lib/texzip/Project.rb, line 137
def bib_files
  @bib_files.to_a
end
cites() click to toggle source

Returns a list of citations. @return [Array<String>] Citations.

# File lib/texzip/Project.rb, line 143
def cites
  @cites.to_a
end
filter_bibtex() click to toggle source
# File lib/texzip/Project.rb, line 393
def filter_bibtex
  if @bib
    cites = @cites.to_a.map(&:to_s)
    seen_cites = cites.to_set
    until cites.empty?
      cite = cites.pop
      entries = @bib[cite]
      if entries.nil?
        puts "WARNING: Can't find BibTeX-entry #{cite}"
      else
        entries = [entries] unless entries.is_a?(Array)
        entries.each do |entry|
          crossref = entry['crossref']
          if crossref
            crossref.split(',').map(&:strip).each do |ref|
              ref = ref.to_s
              cites << ref if seen_cites.add? ref
            end
          end
        end
      end
    end

    @bib = BibTeX::Bibliography.new.add(@bib.data.select {|entry| seen_cites.include? entry.key.to_s})
  end
end
find_file(file, extensions = []) click to toggle source

Returns the full path for a certain file.

The file is searched in the current directory as well as all directories given by the environment variable TEXINPUTS

@param [String] file The (relative) path of the file. @param [Array<String>] extensions The (possible) file extensions. @return [Pathname,nil] The path to the file if exist.

# File lib/texzip/Project.rb, line 155
def find_file(file, extensions = [])
  extensions = [''] + extensions # the empty extension
  extensions.uniq!

  @tex_dirs.each do |d|
    extensions.each do |ext|
      file_path = d.join(file + ext).expand_path
      return FilePath.new(d, file + ext) if File.file?(file_path)
    end
  end
  nil
end
find_files(file, extensions) click to toggle source

Returns the full paths for all variants of a certain file.

The files are searched in the current directory as well as all directories given by the environment variable TEXINPUTS

@param [String] file The base file-name. @param [Array<String>] extensions The possible file-extensions. @return [Array<Pathname>] All found files.

# File lib/texzip/Project.rb, line 176
def find_files(file, extensions)
  extensions = extensions.uniq

  files = []

  extensions.each do |ext|
    @tex_dirs.each do |d|
      file_path = d.join(file + ext).expand_path
      if file_path.file?
        files << FilePath.new(d, file + ext)
        break
      end
    end
  end

  files
end
handle_command(command, argument) click to toggle source

Handles parsed commands.

# File lib/texzip/Project.rb, line 230
def handle_command(command, argument)
  case command
  when 'includegraphics'
    add_image argument
  when 'bibliography'
    argument.split(',').uniq.each {|f| add_bib f.strip}
  when 'usepackage'
    return [argument + '.sty']
  when 'documentclass'
    return [argument + '.cls']
  when 'cite'
    @cites.merge argument.split(',').map(&:strip)
  else
    ext = File.extname(argument)
    if TEXIMAGE_EXTENSIONS.include?(ext)
      file = find_file(argument)
      unless file
        puts "WARNING: Can't find tex-image file #{argument}"
        return nil
      end
      dir = File.dirname(argument)
      parse_file file do |command, arg|
        if command == 'includegraphics'
          add_image File.join(dir, arg)
        else
          raise TeXzip::Error, "Unexpected command '\\#{command}' in tex-image file: \\#{argument}"
        end
        nil
      end
    elsif ext != '.tex'
      argument += '.tex'
    end
    return [argument]
  end
  nil
end
image_files() click to toggle source

Returns a list of included image-files. @return [Array<Pathname>] Included image files.

# File lib/texzip/Project.rb, line 131
def image_files
  @image_files.to_a
end
master_file() click to toggle source

Returns the master-file's path.

# File lib/texzip/Project.rb, line 119
def master_file
  @tex_master_file
end
modify_files(outdir, image_dir, plain_img, bibtex_file) click to toggle source
# File lib/texzip/Project.rb, line 296
def modify_files(outdir, image_dir, plain_img, bibtex_file)
  @output_directory = Pathname.new(outdir).expand_path
  @output_image_directory = @output_directory.join(Pathname.new(image_dir)).expand_path
  @output_bibtex = @output_directory.join(Pathname.new(bibtex_file)).expand_path
  @modified_files = {}

  plain_img_files = Hash.new {|h,k| h[k] = []}

  @tex_files.each_key do |file|
    if TEXIMAGE_EXTENSIONS.include? file.extname
      if plain_img
        file.set_plain_output_directory @output_image_directory
        plain_img_files[file.file.to_s] << file
      else
        file.set_output_directory @output_image_directory
      end
    else
      file.set_output_directory @output_directory
    end
  end
  @tex_files.each_pair do |file, text|
    @modified_files[file] = update_file file, text, plain_img
  end

  @image_files.each do |file|
    if plain_img
      file.set_plain_output_directory @output_image_directory
      plain_img_files[file.file.to_s] << file
    else
      file.set_output_directory @output_image_directory
    end
  end

  # maybe rename some files
  plain_img_files.each_pair do |fname, files|
    ext = File.extname(fname)
    next if files.size <= 1
    # TODO: not supported when updating references
    raise 'Multiple images with the same name but different directories'
    cnt = 2
    files.each do |file|
      loop do
        new_fname = fname.basename.sub_ext("#{cnt}#{ext}")
        if plain_img_files.include?(new_fname.to_s)
          cnt += 1
        else
          file.out_file = new_fname
          break
        end
      end
    end
  end

  filter_bibtex
end
parse_file(file_name, &block) click to toggle source

Load and parse a single tex-file.

The file is parsed for commands including images, BibTeX-files and citations. The command along with the command's argument is passed to the block. The block is assumed to return a list of further tex-files to be parsed.

@param [Pathname,String] file_name The name of the TeX-file to parse @return [Array<String>] A list of included TeX-files.

# File lib/texzip/Project.rb, line 203
def parse_file(file_name, &block)
  text = nil
  File.open(file_name.path, 'rb') do |f|
    text = f.read
  end
  @tex_files[file_name] = text

  block = method(:handle_command) unless block

  included_files = []
  text_without_comments = ''
  text.each_line do |line|
    comment_match = line.match(/(?:\\)*%/)
    if comment_match && (comment_match.end(0) - comment_match.begin(0)).odd?
      line = line[0...comment_match.end(0)]
    end
    text_without_comments.concat line
  end

  text_without_comments.scan(/\\(documentclass|usepackage|include|input|includegraphics|bibliography|cite)(?:\[[^\]]+\])?\{([^}]+)\}/) do |cmd, arg|
    new_files = block.call(cmd, arg)
    included_files.concat(new_files) if new_files
  end
  included_files
end
parse_files() click to toggle source
# File lib/texzip/Project.rb, line 80
def parse_files
  # The hash of all files, including the whole text.
  @tex_files = {}
  @image_files = Set.new
  @bib_files = Set.new
  @cites = Set.new

  # Read all files recursively
  unparsed_files = [@tex_master_file]
  until unparsed_files.empty?
    fname = unparsed_files.pop
    file = find_file(fname)
    if file.nil? then
      if PACKAGE_EXTENSIONS.include?(File.extname(fname))
        next
      else
        raise TeXzip::Error, "Can't find file: #{fname}"
      end
    end

    unless @tex_files.has_key?(file)
      included_files = parse_file file
      unparsed_files.concat included_files
    end
  end

  if !@bib_files.empty?
    @bib = nil
  else
    @bib = BibTeX::Bibliography.new
    @bib_files.each do |bib_file|
      bib = BibTeX.open(bib_file.path)
      bib.replace_strings
      @bib.add(bib.data)
    end
  end
end
tex_files() click to toggle source

Returns a list of included tex and sty files. @return [Array<Pathname>] Included tex files.

# File lib/texzip/Project.rb, line 125
def tex_files
  @tex_files.keys
end
update_file(tex_file, text, plain) click to toggle source
# File lib/texzip/Project.rb, line 352
def update_file(tex_file, text, plain)
  ext = tex_file.path.extname

  new_text = ''
  text.each_line do |line|
    comment_match = line.match(/(?:\\)*%/)
    if comment_match && (comment_match.end(0) - comment_match.begin(0)).odd?
      comment = line[comment_match.end(0)..-1]
      line = line[0...comment_match.end(0)]
    else
      comment = ''
    end
    new_line = line.gsub(/(\\(include|input|includegraphics|bibliography)(?:\[[^\]]+\])?)\{([^}]+)\}/) do |m|
      start = $1
      cmd = $2
      file = $3
      if cmd == 'includegraphics'
        if TEXIMAGE_EXTENSIONS.include? ext
          file = File.join(tex_file.file.dirname, file)
        end
        new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
        # TODO: this does not support multiple files with same name
        new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
      elsif cmd == 'bibliography'
        new_file = @output_bibtex.basename.to_s.gsub(/\.bib$/, '')
      elsif TEXIMAGE_EXTENSIONS.include?(File.extname(file))
        new_file = @output_image_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
        # TODO: this does not support multiple files with same name
        new_file = @output_image_directory.join(new_file.basename).relative_path_from(@output_directory) if plain
      else
        new_file = @output_directory.join(Pathname.new(file)).relative_path_from(@output_directory)
      end
      "#{start}{#{new_file}}"
    end
    new_text.concat new_line
    new_text.concat comment
  end

  new_text
end
write_archive(archive_file, force = false) click to toggle source
# File lib/texzip/Project.rb, line 435
def write_archive(archive_file, force = false)
  require 'ffi-libarchive'

  archive_file = Pathname.new(archive_file).expand_path
  return unless ask_overwrite(archive_file)

  compression = case File.basename(archive_file.to_s)
                when /\.tgz$/, /\.tar\.gz$/
                  :gzip
                when /\.tbz2$/, /\.tar\.bz2$/
                  :bzip2
                when /\.txz$/, /\.tar\.xz$/
                  :xz
                when /\.tlzma$/, /\.tar\.lzma$/
                  :lzma
                when /\.tZ$/, /\.tar\.Z$/
                  :Z
                when /\.tar$/
                  :none
                else
                  raise TeXzip::Error, "Can't derive archive-type from file name #{archive_file}"
                end

  puts "Write archive #{archive_file.relative_path_from(Pathname.getwd)}"
  Archive.write_open_filename archive_file.to_s, compression, :tar do |ar|
    write_data true do |path, data|
      ar.add_entry do |e|
        e.pathname = path.relative_path_from(@output_directory).to_s
        if data.is_a? Pathname
          e.copy_stat(data.to_s)
          File.open(data, 'rb', &:read)
        else
          e.mode = 0644
          e.atime = Time.now
          e.mtime = Time.now
          e.filetype = :file
          data
        end
      end
    end
  end
end
write_data(force = false) { |path, data| ... } click to toggle source
# File lib/texzip/Project.rb, line 478
def write_data(force = false)
  raise ArgumentError, 'Block required' unless block_given?

  overwrite_all = force
  commands = []

  @modified_files.each_pair do |file, text|
    if force || ask_overwrite(file.output_path)
      commands << [file.output_path, text]
    end
  end

  @image_files.each do |file|
    if force || ask_overwrite(file.output_path)
      commands << [file.output_path, file.path]
    end
  end

  if @bib && (force || ask_overwrite(@output_bibtex))
    commands << [@output_bibtex, @bib.to_s]
  end

  commands.each do |path, data|
    yield(path, data)
  end
end
write_files(force = false) click to toggle source
# File lib/texzip/Project.rb, line 420
def write_files(force = false)
  cwd = Pathname.getwd.expand_path
  write_data do |path, data|
    puts "Write file #{path.relative_path_from(cwd)}"
    FileUtils.mkdir_p path.dirname unless path.dirname.exist?
    if data.is_a? Pathname
      FileUtils.copy data, path
    else
      File.open(path, 'wb') do |f|
        f.write data
      end
    end
  end
end