2009-03-24 19:53:54 +01:00
|
|
|
#!/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.
|
|
|
|
#
|
2019-03-30 20:23:07 +01:00
|
|
|
# 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
|
2009-03-24 19:53:54 +01:00
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
|
|
|
2019-03-30 20:23:07 +01:00
|
|
|
# Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License along
|
2020-12-04 22:23:59 +01:00
|
|
|
# with this program; if not, write to the Free Software Foundation, Inc.,
|
2009-03-24 19:53:54 +01:00
|
|
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
2020-12-04 22:37:54 +01:00
|
|
|
require "English"
|
2020-12-04 22:23:59 +01:00
|
|
|
require "getoptlong"
|
|
|
|
require "rb-inotify"
|
|
|
|
require "pathname"
|
2009-03-24 19:53:54 +01:00
|
|
|
|
|
|
|
# Small necessary Pathname class extension.
|
|
|
|
class Pathname
|
|
|
|
def extname=(ext)
|
2020-12-04 22:23:59 +01:00
|
|
|
raise "Invalid extension" unless ext.is_a?(String) && ext.match(/^\./)
|
|
|
|
|
2009-03-24 19:53:54 +01:00
|
|
|
@path.sub!(/#{extname}$/, ext)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
## Helper methods
|
|
|
|
|
|
|
|
# Print the usage.
|
|
|
|
def usage
|
2020-12-04 22:23:59 +01:00
|
|
|
puts "Usage: #{PROGRAM} [options]... [tex-file] [rubber-options]..."
|
2009-03-24 19:53:54 +01:00
|
|
|
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)
|
2020-12-04 22:23:59 +01:00
|
|
|
mode_opt = { pdf: "--pdf",
|
|
|
|
ps: "--ps",
|
|
|
|
pspdf: "--ps --pdf" }[mode]
|
|
|
|
params = ARGV.join(" ")
|
|
|
|
err_file = infile.with_extname("err")
|
2009-03-24 19:53:54 +01:00
|
|
|
|
2023-08-18 22:57:45 +02:00
|
|
|
system "rubber --inplace #{mode_opt} #{params} #{infile} 2> #{err_file}"
|
2009-03-24 19:53:54 +01:00
|
|
|
File.open(err_file) { |file| puts file.read }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Start the right viewer based on the mode.
|
|
|
|
def view(infile, mode)
|
|
|
|
case mode
|
2020-12-04 22:23:59 +01:00
|
|
|
when :dvi
|
2020-12-04 22:37:54 +01:00
|
|
|
system "xdvi.bin",
|
|
|
|
"-watchfile", "1",
|
|
|
|
"-name", "xdvi", infile.with_extname("dvi").to_s
|
2020-12-04 22:23:59 +01:00
|
|
|
when :ps
|
2020-12-04 22:37:54 +01:00
|
|
|
system "evince", infile.with_extname("ps").to_s
|
2020-12-04 22:23:59 +01:00
|
|
|
when :pdf, :pspdf
|
2020-12-04 22:37:54 +01:00
|
|
|
system "evince", infile.with_extname("pdf").to_s
|
2009-03-24 19:53:54 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-07-16 12:02:40 +02:00
|
|
|
# Clean up cruft related to compilation of the input file.
|
|
|
|
def clean(infile)
|
2020-12-04 22:23:59 +01:00
|
|
|
err_file = infile.with_extname("err")
|
2009-07-16 12:02:40 +02:00
|
|
|
File.unlink(err_file) if err_file.exist?
|
|
|
|
system "rubber --clean --inplace #{infile}"
|
|
|
|
end
|
|
|
|
|
2009-03-24 19:53:54 +01:00
|
|
|
# Reload the right viewer based on the mode.
|
|
|
|
def reload(infile, mode)
|
|
|
|
case mode
|
2020-12-04 22:23:59 +01:00
|
|
|
when :ps
|
|
|
|
system "evince", infile.with_extname("ps").to_s
|
|
|
|
when :pdf, :pspdf
|
|
|
|
system "evince", infile.with_extname("pdf").to_s
|
2009-03-24 19:53:54 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
## Initialisation
|
2020-12-04 22:23:59 +01:00
|
|
|
PROGRAM = File.basename($PROGRAM_NAME).freeze
|
2021-12-09 17:45:34 +01:00
|
|
|
VERSION = "0.8".freeze
|
2009-03-24 19:53:54 +01:00
|
|
|
|
|
|
|
# Parse the command line options.
|
|
|
|
# Determine the compile mode from the options.
|
|
|
|
mode = :dvi
|
2020-12-04 22:23:59 +01:00
|
|
|
opts = GetoptLong.new(["--help", "-h", GetoptLong::NO_ARGUMENT],
|
|
|
|
["--ps", "-p", GetoptLong::NO_ARGUMENT],
|
|
|
|
["--pdf", "-d", GetoptLong::NO_ARGUMENT],
|
|
|
|
["--version", "-v", GetoptLong::NO_ARGUMENT])
|
2009-03-24 19:53:54 +01:00
|
|
|
opts.ordering = GetoptLong::REQUIRE_ORDER
|
|
|
|
opts.quiet = true
|
2020-12-04 22:23:59 +01:00
|
|
|
opts.each do |opt, _arg|
|
2009-03-24 19:53:54 +01:00
|
|
|
case opt
|
2020-12-04 22:23:59 +01:00
|
|
|
when "--help"
|
|
|
|
help
|
|
|
|
exit 0
|
|
|
|
when "--ps"
|
|
|
|
mode = :ps
|
|
|
|
when "--pdf"
|
2022-11-05 15:08:15 +01:00
|
|
|
mode = if mode == :ps
|
|
|
|
:pspdf
|
|
|
|
else
|
|
|
|
:pdf
|
2020-12-04 22:23:59 +01:00
|
|
|
end
|
|
|
|
when "--version"
|
|
|
|
puts "#{PROGRAM} #{VERSION}"
|
|
|
|
exit 0
|
2009-03-24 19:53:54 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Get the input file from the arguments.
|
2020-12-04 22:23:59 +01:00
|
|
|
if ARGV.empty?
|
2009-03-24 19:53:54 +01:00
|
|
|
usage
|
|
|
|
exit 1
|
|
|
|
else
|
|
|
|
infile = Pathname.new(ARGV.shift)
|
2020-12-04 22:23:59 +01:00
|
|
|
|
2009-03-24 19:53:54 +01:00
|
|
|
def infile.with_extname(ext)
|
2021-12-09 17:42:17 +01:00
|
|
|
file = dirname + basename(extname)
|
2009-03-24 19:53:54 +01:00
|
|
|
file.extname = ".#{ext}"
|
|
|
|
file
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Check the input file.
|
|
|
|
begin
|
|
|
|
File.open(infile) {}
|
2020-12-04 22:23:59 +01:00
|
|
|
rescue SystemCallError => e
|
|
|
|
puts "#{PROGRAM}: #{e}"
|
2009-03-24 19:53:54 +01:00
|
|
|
exit 2
|
|
|
|
end
|
|
|
|
|
2021-12-09 17:45:09 +01:00
|
|
|
# Find the dependencies of the input file using rubber-info and
|
2009-03-24 19:53:54 +01:00
|
|
|
# do an initial run.
|
|
|
|
files = `rubber-info --deps #{infile}`.chomp.split
|
|
|
|
compile(infile, mode)
|
2018-06-22 23:13:21 +02:00
|
|
|
puts
|
2009-03-24 19:53:54 +01:00
|
|
|
|
|
|
|
# Spawn a viewer based on the mode.
|
2020-12-04 22:37:54 +01:00
|
|
|
pid = $PROCESS_ID
|
|
|
|
viewer_pid =
|
|
|
|
fork do
|
|
|
|
view(infile, mode)
|
|
|
|
# If xdvi/evince exits, this program should exit too.
|
|
|
|
puts "#{PROGRAM}: viewer exited, so will I!"
|
2023-08-18 22:59:27 +02:00
|
|
|
Process.kill("HUP", pid)
|
2020-12-04 22:37:54 +01:00
|
|
|
end
|
2009-03-24 19:53:54 +01:00
|
|
|
|
|
|
|
## Main event loop
|
|
|
|
|
|
|
|
# Add input file with dependancies to the watch list and start event loop.
|
2018-06-22 20:07:26 +02:00
|
|
|
notifier = INotify::Notifier.new
|
2021-12-09 17:45:09 +01:00
|
|
|
dirs = files.map { |file| Pathname.new(file).dirname }.uniq
|
2009-03-25 12:51:33 +01:00
|
|
|
dirs.each do |dir|
|
2018-06-22 20:07:26 +02:00
|
|
|
# Set up a watch per directory
|
2018-06-22 23:13:37 +02:00
|
|
|
notifier.watch(dir.to_s, :close_write) do |ev|
|
2018-06-22 20:20:30 +02:00
|
|
|
# Only compile if a dependency of the input file has been modified
|
2018-06-22 23:09:14 +02:00
|
|
|
file_path = (dir + ev.name).to_s
|
|
|
|
next unless files.include? file_path
|
2020-12-04 22:23:59 +01:00
|
|
|
|
2023-08-18 22:58:54 +02:00
|
|
|
puts "#{PROGRAM}: file #{ev.name} modified, compiling #{infile}..."
|
2009-03-25 12:51:33 +01:00
|
|
|
compile(infile, mode)
|
|
|
|
reload(infile, mode)
|
2018-06-22 23:13:21 +02:00
|
|
|
puts
|
2009-03-25 12:51:33 +01:00
|
|
|
end
|
2009-03-24 19:53:54 +01:00
|
|
|
end
|
2018-06-22 20:07:26 +02:00
|
|
|
|
2023-08-18 22:59:27 +02:00
|
|
|
# Handle signals during the main event loop.
|
|
|
|
["INT", "TERM", "QUIT"].each do |sig|
|
|
|
|
Signal.trap(sig) do
|
|
|
|
puts "#{PROGRAM}: caught signal #{sig}, stopping the viewer..."
|
|
|
|
Process.kill(sig, viewer_pid)
|
|
|
|
notifier.stop
|
|
|
|
end
|
|
|
|
end
|
|
|
|
Signal.trap("HUP") { notifier.stop }
|
|
|
|
|
|
|
|
# Run the main event loop.
|
2018-06-22 20:07:26 +02:00
|
|
|
notifier.run
|
2023-08-18 22:59:27 +02:00
|
|
|
|
|
|
|
# Clean before finishing!
|
|
|
|
clean(infile)
|