rubberin/rubberin
Admar Schoonen 9768a3ab89 Added printing empty lines after compilation
Adding printing an empty line after a compile iteration has finished
makes it easier to parse the output and look for errors.
2018-06-22 20:56:04 +02:00

204 lines
5.7 KiB
Ruby
Executable file

#!/usr/bin/env ruby
#
# rubberin - a rubber wrapper using inotify
#
# Copyright (C) 2007 Paul van Tilburg <paul@luon.net>
#
# This script can assist the authoring of LaTeX documents. It acts as an
# drop-in replacement for rubber. While rubber would just perform a
# compilation of the LaTeX file, rubberin will perform a compilation (using
# rubber) each time it detects a modification in the LaTeX file or any of
# its dependencies (include files, BibTeX files, image, etc.) until
# rubberin is interrupted. Additionally, rubberin spawns a viewer to view
# the result of the LaTeX compilation and forces it to reload every time
# rubberin has recompiled the LaTeX file.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WAR RANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this p rogram; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
require 'getoptlong'
require 'rb-inotify'
require 'pathname'
# Small necessary Pathname class extension.
class Pathname
def extname=(ext)
raise "Invalid extension" unless ext.is_a? String and ext.match(/^\./)
@path.sub!(/#{extname}$/, ext)
end
end
## Helper methods
# Print the usage.
def usage
puts "Usage: #{Program} [options]... [tex-file] [rubber-options]..."
end
# Print the command line help.
def help
usage
puts "Wrapper for rubber that facilitates automatic compiling and viewer updating"
puts "when the input file or dependancy files are modified."
puts
puts "Available options:"
puts " -d, --pdf produce a PDF file (via ps2pdf if used with -p)"
puts " -h, --help display this help"
puts " -p, --ps produce a PostScript file"
puts "The options --pdf and --ps are passed to rubber like all options not listed above."
end
# Compile the input file using the right options based on the mode.
def compile(infile, mode)
mode_opt = {:pdf => "--pdf",
:ps => "--ps",
:pspdf => "--ps --pdf"}[mode]
params = ARGV.join(' ')
err_file = infile.with_extname('err')
ret = system "rubber --inplace #{mode_opt} #{params} #{infile} 2> #{err_file}"
File.open(err_file) { |file| puts file.read }
# Remove the output save file if compile was succesful.
#clean(infile) if ret
end
# Start the right viewer based on the mode.
def view(infile, mode)
case mode
when :dvi
exec "xdvi.bin", "-watchfile", "1",
"-name", "xdvi", infile.with_extname('dvi').to_s
when :ps
exec "evince", infile.with_extname('ps').to_s
when :pdf, :pspdf
exec "evince", infile.with_extname('pdf').to_s
end
end
# Clean up cruft related to compilation of the input file.
def clean(infile)
err_file = infile.with_extname('err')
File.unlink(err_file) if err_file.exist?
system "rubber --clean --inplace #{infile}"
end
# Reload the right viewer based on the mode.
def reload(infile, mode)
case mode
when :ps
system "evince", infile.with_extname('ps').to_s
when :pdf, :pspdf
system "evince", infile.with_extname('pdf').to_s
end
end
## Initialisation
Program = File.basename $0
Version = '0.6'
# Parse the command line options.
# Determine the compile mode from the options.
mode = :dvi
opts = GetoptLong.new(
[ "--help", "-h", GetoptLong::NO_ARGUMENT ],
[ "--ps", "-p", GetoptLong::NO_ARGUMENT ],
[ "--pdf", "-d", GetoptLong::NO_ARGUMENT ],
[ "--version", "-v", GetoptLong::NO_ARGUMENT ])
opts.ordering = GetoptLong::REQUIRE_ORDER
opts.quiet = true
opts.each do |opt, arg|
case opt
when "--help"
help
exit 0
when "--ps"
mode = :ps
when "--pdf"
mode = if mode == :ps then :pspdf
else :pdf
end
when "--version"
puts "#{Program} #{Version}"
exit 0
end
end
# Get the input file from the arguments.
if ARGV.length < 1
usage
exit 1
else
infile = Pathname.new(ARGV.shift)
def infile.base
self.basename(self.extname)
end
def infile.with_extname(ext)
file = self.base
file.extname = ".#{ext}"
file
end
end
# Check the input file.
begin
File.open(infile) {}
rescue => err
puts "#{Program}: #{err}"
exit 2
end
# Find the dependancies of the input file using rubber-info and
# do an initial run.
files = `rubber-info --deps #{infile}`.chomp.split
compile(infile, mode)
puts ""
# Spawn a viewer based on the mode.
viewer_pid = fork
view(infile, mode) if not viewer_pid
# Handle signals.
["INT", "TERM", "QUIT"].each do |sig|
Signal.trap(sig) do
Process.kill(sig, viewer_pid)
clean(infile)
exit
end
end
# If xdvi exits, this program should exit too.
Signal.trap("CLD") do
puts "#{Program}: viewer exited, so will I!"
clean(infile)
exit
end
## Main event loop
# Add input file with dependancies to the watch list and start event loop.
notifier = INotify::Notifier.new
dirs = files.map { |file| File.dirname(file) }
dirs.each do |dir|
# Set up a watch per directory
notifier.watch(dir, :close_write) do |ev|
# Only compile if a dependency of the input file has been modified
next unless files.include?(dir + "/" + ev.name)
puts "I: file #{ev.name} modified, compiling #{infile}..."
compile(infile, mode)
reload(infile, mode)
puts ""
end
end
notifier.run