class TeXzip::Project
Constants
- IMAGE_EXTENSIONS
- PACKAGE_EXTENSIONS
- TEXIMAGE_EXTENSIONS
Attributes
Public Class Methods
# 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
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
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
# 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
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
Returns a list of citations. @return [Array<String>] Citations.
# File lib/texzip/Project.rb, line 143 def cites @cites.to_a end
# 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
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
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
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
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
Returns the master-file's path.
# File lib/texzip/Project.rb, line 119 def master_file @tex_master_file end
# 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
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
# 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
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
# 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
# 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
# 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
# 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