From 91b56eca6122cb1198848b15cd9fc58e1abd3a55 Mon Sep 17 00:00:00 2001 From: Paul van Tilburg Date: Tue, 24 Mar 2009 19:53:54 +0100 Subject: [PATCH] Initial import of rubberin. --- rubberin | 177 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100755 rubberin diff --git a/rubberin b/rubberin new file mode 100755 index 0000000..6b0c77f --- /dev/null +++ b/rubberin @@ -0,0 +1,177 @@ +#!/usr/bin/env ruby +# +# rubberin - a rubber wrapper using inotify +# +# Copyright (C) 2007 Paul van Tilburg +# +# 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 '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') + + system "rubber --inplace #{mode_opt} #{params} #{infile} 2> #{err_file}" + File.open(err_file) { |file| puts file.read } +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') + when :ps + exec "evince", infile.with_extname('ps') + when :pdf, :pspdf + exec "evince", infile.with_extname('pdf') + end +end + +# Reload the right viewer based on the mode. +def reload(infile, mode) + case mode + when :ps + system "evince", infile.with_extname('ps') + when :pdf, :pspdf + system "evince", infile.with_extname('pdf') + end +end + +## Initialisation +Program = File.basename $0 +Version = '0.4a' + +# 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 ]) +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 + 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) + +# 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) { Process.kill(sig, viewer_pid); exit } +end +# If xdvi exits, this program should exit too. +Signal.trap("CLD") { puts "#{Program}: viewer exited, so will I!"; exit } + +## Main event loop + +# Add input file with dependancies to the watch list and start event loop. +i = Inotify.new +files.each do |file| + i.add_watch(file, Inotify::MODIFY) +end +# Process the events, recompile and reload when necessary. +i.each_event do |ev| + puts "I: file #{ev.name} modified, compiling #{infile}..." + compile(infile, mode) + reload(infile, mode) +end