rake4latex 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,103 @@
1
+ #
2
+ #Class to determine dependecies of a LaTeX project.
3
+ #
4
+ #* \input/\include: searched recursive
5
+ #
6
+ #Not done (yet):
7
+ #* check kpsewhich if files are not found
8
+ #* scan for \bibliography{testdocument} and search via kpsewhich
9
+ # rule '.bbl' => 'xxx.bib'
10
+ #
11
+ class LaTeXDependencies
12
+ def self.get_dependecies(filename, *arguments)
13
+ new(filename).get_dependecies(*arguments)
14
+ end
15
+ #
16
+ #Define the main file for the dependecy-analyse.
17
+ def initialize( filename )
18
+ @filename = filename
19
+ raise ArgumentError, "File #{@filename} not found" unless File.exist?(@filename)
20
+ @basedir = File.dirname(filename)
21
+ @basename = File.basename(filename)
22
+ end
23
+ =begin rdoc
24
+ Get all dependecies.
25
+
26
+ Which dependecies you get is defined by the arguments:
27
+ * :inputs: All sub files, loaded with \input and \include
28
+ There are also predefined actions:
29
+ * :flat: flat the nested dependecies and delete double entries.
30
+ Missing dependecies:
31
+ *index (ind)
32
+ *bibliography (bbl? + bib)
33
+ *splitindex
34
+ =end
35
+ def get_dependecies(*arguments)
36
+ deps = [ @filename ]
37
+ arguments.each{|arg|
38
+ case arg
39
+ when :inputs; deps << find_included_files
40
+ when :flat #Action done at end
41
+ else
42
+ raise ArgumentError, "Unknown option #{arg}"
43
+ end
44
+ }
45
+
46
+ if arguments.include?(:flat)
47
+ return deps.flatten.uniq
48
+ else
49
+ return deps
50
+ end
51
+ end
52
+
53
+ =begin rdoc
54
+ Finds the files given as arguments to the <tt>\includeonly</tt>, <tt>\include</tt>
55
+ and <tt>\input</tt> latex command in file _file_ and returns a nested array of their
56
+ names (relative to the directory of file).
57
+ =end
58
+ def find_included_files()
59
+ deps = []
60
+ #Change to the basedir of the main_file and start from there the recursive analyse.
61
+ Dir.chdir(@basedir){
62
+ begin
63
+ deps = find_included_files_rec( @basename )
64
+ rescue Errno::EMFILE => err
65
+ raise "Recursive include structure in #{@filename}"
66
+ end
67
+ }
68
+ deps
69
+ end #self.find_included_files
70
+ #
71
+ =begin rdoc
72
+ There is a bit trouble with the basedir of TeX-files,
73
+ All includes must be located relative to the main_file.
74
+
75
+ There is no check on the file found by kpsewhich ##fixme##
76
+ =end
77
+ def find_included_files_rec( file )
78
+
79
+ raise ArgumentError, "File #{file} not found" unless File.exist?(file)
80
+ deps = []
81
+
82
+ File.foreach(file){ |line|
83
+ #Skip comments, then look for input macros
84
+ #Remarks:
85
+ #* A \% is not detected.
86
+ #* \endinput is not detected
87
+ line.sub(/%.*/, '').scan(/\\(?:include|input|includeonly)\{([^}]+)\}/){|m|
88
+ m.each{|ifile|
89
+ ifile = ifile.ext('tex') #add the extension tex if not already done.
90
+ deps << ifile
91
+
92
+ if File.exist?(ifile)
93
+ subdeps = find_included_files_rec(ifile) #Get includes in include file
94
+ deps << subdeps unless subdeps.empty? #add sub-includes if available
95
+ else
96
+ puts "Include-File #{ifile} not found" #fixme puts/log...
97
+ end
98
+ }#hit
99
+ }#line.scan
100
+ }#File.foreach
101
+ deps
102
+ end #self.find_included_files
103
+ end #LaTeXDependencies
@@ -0,0 +1,268 @@
1
+ =begin rdoc
2
+ Class which encapsulates all the information needed to call latex
3
+ as often as needed.
4
+ It calls (pdf|xe|lua)LaTeX and checks if additional tools are needed.
5
+ =end
6
+ class LaTeXRunner
7
+ #Define Infinity (needed for option maxruns.
8
+ Infinity = 1.0/0
9
+ #
10
+ #Define the main settings for the runner.
11
+ #
12
+ #This values can be overwritten with
13
+ # Rake.application.set_latexrunner_default(:loglevel, Log4r::DEBUG)
14
+ #
15
+ DEFAULT_SETTINGS = {
16
+ :main_file => nil, #only needed for key-check. Must be defined later.
17
+ :maxruns => 5,
18
+ :program => :pdflatex, #Valid entries see PROGRAMS
19
+ :loglevel => Log4r::INFO,
20
+ :options => ['-interaction=batchmode'],
21
+ :texerrors_allowed => false,
22
+ }
23
+ =begin rdoc
24
+ All tasks, which should be executed after a TeX-call.
25
+
26
+ The constant is filled via LaTeXRunner#tex_postrule,
27
+ LaTeXRunner#run_latex_once will loop on all this tasks
28
+ and execute them if necessary.
29
+ =end
30
+ Post_Prerequisites = []
31
+ =begin rdoc
32
+ Defines an optional check routine to check if a Post_Prerequisites
33
+ is necessary.
34
+ Should return true or false.
35
+ =end
36
+ Post_Prerequisites_check = {}
37
+
38
+ =begin rdoc
39
+ The default programs to use to process latex files, depending on the extension
40
+ of the file to generate. To add a default program for another extension, simply
41
+ add the corresponding entry here. To change the default program for a given
42
+ extension for all the latex tasks, change it here. To change the program used
43
+ in a specific task, change it in the corresponding runner
44
+ =end
45
+ PROGRAMS = {
46
+ :latex => 'latex',
47
+ :pdflatex => 'pdflatex',
48
+ :lualatex => 'lualatex',
49
+ :xelatex => 'xelatex',
50
+ }
51
+
52
+
53
+ #Error class
54
+ class MainfileError < ArgumentError; end
55
+
56
+ =begin rdoc
57
+ Define a new runner.
58
+
59
+ The settings contain the following values:
60
+ * main_file, see LaTeXRunner#main_file
61
+ * maxruns, see LaTeXRunner#maxruns
62
+ =end
63
+ def initialize( settings )
64
+
65
+ #~ raise ArgumentError, "Task is no RakeTask but #{t.class}" unless task.is_a?(Rake::Task)
66
+
67
+ @logger = Log4r::Logger.new("LaTeXRunner #{settings[:main_file]}")
68
+ @logger.outputters = Log4r::StdoutOutputter.new('log_stdout')
69
+
70
+ DEFAULT_SETTINGS.merge(settings).each{|key, value|
71
+ #~ puts "#{key}: #{value}"
72
+ case key
73
+ when :main_file; @main_file = value
74
+ when :maxruns; @maxruns = value
75
+ when :program; self.program = value
76
+ when :options; @options = value
77
+ when :loglevel; @logger.level = value
78
+ when :texerrors_allowed; @texerrors_allowed = value
79
+ when :dummy #dummy
80
+ else
81
+ raise ArgumentError, "Undefined key #{key} for LaTeXRunner"
82
+ end
83
+ }
84
+
85
+ raise MainfileError, "main_file missing" unless @main_file
86
+ raise MainfileError, "main_file #{@main_file} not found" unless File.exist?(@main_file)
87
+ raise ArgumentError, "Maxrun is no number" unless @maxruns.is_a?( Fixnum ) or @maxruns == Infinity
88
+
89
+ Rake.application.latexrunner = self
90
+ @basename = File.basename(@main_file)
91
+ @input_dir = File.dirname(@main_file)
92
+ @full_input_dir = File.expand_path(@input_dir)
93
+
94
+ @logger.name.replace("LaTeXRunner #{@basename}")
95
+ @logger.info( "StartLaTeXRunner for #{@main_file}")
96
+
97
+ @rerun_necessary = true #initialize
98
+ end #initialize
99
+
100
+ def inspect()
101
+ return "#<LaTeXRunner: #{@main_file}>"
102
+ end
103
+ =begin rdoc
104
+ The name of the file on which latex should be invoked. The input directory is
105
+ determined from this.
106
+ =end
107
+ attr_accessor :main_file
108
+
109
+ =begin rdoc
110
+ The maximum number of times latex can be run.
111
+ If you want to remove the limit, you can set it to infinity
112
+ (LaTeXRunner::Infinity)
113
+ =end rdoc
114
+ attr_accessor :maxruns
115
+
116
+ # The directory where the input file is located. It is determined automatically during initialization
117
+ attr_reader :input_dir
118
+
119
+ # The full path of the input directory. It is determined automatically during initialization
120
+ attr_reader :full_input_dir
121
+
122
+ # The looger for the LaTeX-Run.
123
+ attr_reader :logger
124
+
125
+ # Flag if TeX should be started again.
126
+ def rerun_necessary?()
127
+ @rerun_necessary and ( maxruns > @texruns )
128
+ end
129
+
130
+ =begin rdoc
131
+ The program to use to process the .tex files for this runner.
132
+ Default is :pdflatex, but can be redefined by the task definition.
133
+ =end
134
+ def program=(key)
135
+ raise ArgumentError, "Programm key #{key.inspect} unknown" unless PROGRAMS[key]
136
+ @program = PROGRAMS[key]
137
+ end
138
+
139
+ # An array of the options to pass to latex. Defaults to <tt>['-interaction=batchmode']</tt>
140
+ attr_reader :options
141
+
142
+
143
+ =begin rdoc
144
+ Starts the build process for the output file.
145
+
146
+ It determines the latex command line and calls <tt>run_latex_as_needed</tt>.
147
+ =end
148
+ def execute()
149
+
150
+ cmd = "#{@program} #{@options.join ' '} " +
151
+ #~ "-output-directory=#@full_output_dir
152
+ " #{@basename}"
153
+ Dir.chdir(@input_dir) do
154
+ @texruns = 0
155
+ while rerun_necessary?
156
+ @texruns += 1
157
+ @rerun_necessary = false
158
+ run_latex_once(cmd)
159
+ end
160
+ end
161
+ #Guarentee that help files are deleted fin clean.
162
+ set4clean( @basename )
163
+
164
+ plural_s = "#{@texruns} run#{@texruns>1 ? 's': ''}"
165
+ if @rerun_necessary
166
+ @logger.warn("TeX-run stopped after #{plural_s}, but more runs are necessary" )
167
+ else
168
+ @logger.info("TeX-run stopped after #{plural_s}" )
169
+ end
170
+ end
171
+
172
+ =begin rdoc
173
+ Manages a single invocation of latex. This means:
174
+ * Get the MD5 digest of help-files.
175
+ * executes latex
176
+ * Invokes the following tasks like Index...
177
+ This method is expected to be started in @input_dir
178
+ =end
179
+ def run_latex_once(cmd)
180
+
181
+ checksums = {}
182
+ FileList["#{@basename.ext('*')}"].each do |file|
183
+ # Computes a MD5 digest for the given file, reading the file.
184
+ File.open(file){|f| checksums[file] = MD5.new(f.read).hexdigest }
185
+ end
186
+
187
+ @logger.info("Excute #{cmd}")
188
+ latex_output = `#{cmd}`
189
+ if $? != 0
190
+ @logger.fatal("There where #@program errors. See #{@main_file.ext('.log')} for details")
191
+ raise RuntimeError, "There where #@program errors. "\
192
+ "See #{@main_file.ext('.log')} for details" unless @texerrors_allowed
193
+ end
194
+
195
+ #Build the checksums.
196
+ FileList["#{@basename.ext('*')}"].each do |file|
197
+ # Compare old and new MD5 digest for the given file.
198
+ File.open(file){|f|
199
+ case checksums[file]
200
+ when nil
201
+ @logger.debug("new file #{file}")
202
+ checksums[file] = :changed
203
+ when File.open(file){|f| MD5.new(f.read).hexdigest}
204
+ @logger.debug("Unchanged file #{file}")
205
+ else
206
+ @logger.debug(" Changed file #{file}" )
207
+ checksums[file] = :changed
208
+ end
209
+ }
210
+ end
211
+
212
+ #check changes in some files.
213
+ %w{aux toc lof lot}.each{|ext|
214
+ next unless checksums[@basename.ext(ext)] == :changed
215
+ @rerun_necessary = true
216
+ @logger.info("Rerun necessary (#{ext}-file changed)" ) if @rerun_necessary
217
+ break #stop after the first reason for an additional run.
218
+ }
219
+
220
+ #Loop on all Post_Prerequisites (e.g. makeindex, BibTeX...)
221
+ Post_Prerequisites.each{|pre|
222
+ @logger.debug("Check Post-prerequisite #{pre}" )
223
+ #Get the Post-prerequisites.
224
+ #Stops if it does not exist.
225
+ begin
226
+ if ! post_prereq = Rake.application[@basename.ext(pre)]
227
+ @logger.fatal("No rule for Post-prerequisites #{pre}" )
228
+ next
229
+ end
230
+ rescue RuntimeError => err
231
+ @logger.debug("No Post-prerequisite #{pre}" )
232
+ next
233
+ end
234
+
235
+ #Starts only, if prerequisites of post_prereq exist and was regenerated by TeX.
236
+ #Here we make an additional check if we need the post_prereq.
237
+ post_prereq.prerequisites.each{|post_prereq_pre|
238
+ reason4call = false
239
+ #Check special rules (e.g. for BibTeX)
240
+ if Post_Prerequisites_check[pre]
241
+ @logger.debug("Make precheck for #{pre}" )
242
+ Post_Prerequisites_check[pre].each{|precheck|
243
+ #Call the pre-check with the task...
244
+ reason4call = precheck.call(:task => post_prereq, :checksums => checksums, :logger => @logger)
245
+ break if reason4call #we need only one reason to start the task.
246
+ }
247
+ else #make the standard check (
248
+ reason4call = "#{post_prereq_pre} changed" if ( checksums[post_prereq_pre] == :changed )
249
+ end
250
+ if reason4call #We have to run the task for the post_prereq
251
+ #One of the sources of post_prereq changes
252
+ @logger.info("Call target #{post_prereq.name} (#{reason4call})" )
253
+ post_prereq.invoke()
254
+ File.open(post_prereq.name){|f|
255
+ @rerun_necessary = (MD5.new(f.read).hexdigest != checksums[f.path])
256
+ @logger.info("Rerun necessary (#{f.path} changed)" ) if @rerun_necessary
257
+ } unless @rerun_necessary #check not necessary, if we already need an additional run
258
+ break #Only one call necessary
259
+ end #source changed
260
+ } ##prerequisites of post_prereq
261
+ } #Post_Prerequisites
262
+
263
+ #fixme: get info for next run (global?, adabp invoke?
264
+ #set again task??
265
+ #call rake again? creaite new app?
266
+ end
267
+
268
+ end #LaTeXRunner
@@ -0,0 +1,134 @@
1
+ #Some Tools have screen output.
2
+ #runtex should run silent in background, so we have to redirect the output.
3
+ require 'catch_output'
4
+ include Catch_output
5
+
6
+
7
+ desc "Show compiled source in Adobe Acrobat Reader."
8
+ task :acroread do |t|
9
+ t.prerequisites.each{|pre|
10
+ #~ sh "acroread #{pre.ext('pdf')}"
11
+ #Problem: rake process waits until reader is closed
12
+ sh "call #{pre.ext('pdf')}"
13
+ }
14
+ end
15
+
16
+ desc "Set the basefile - needed for clean and statistic"
17
+ task :basefile do |task|
18
+ task.prerequisites.each{|pre|
19
+ #~ puts "Set basefile #{pre.inspect}"
20
+ set4clean( pre )
21
+ file :statistic => pre
22
+ }
23
+ task.reenable() #If not the second call for clean will not work.
24
+ end
25
+ task :default => :basefile
26
+
27
+
28
+ #
29
+ #Reset the timestamp of a file (can be used to force the first compilation)
30
+ #
31
+ desc "Touch - reset timestamp"
32
+ task :touch do |t|
33
+ t.prerequisites.each{|pre|
34
+ FileUtils.touch(pre)
35
+ }
36
+ end
37
+
38
+ desc "Build a dvi-file with LaTeX"
39
+ rule '.dvi' => '.tex' do |t|
40
+ runner = LaTeXRunner.new(
41
+ :main_file => t.source,
42
+ :program => :latex,
43
+ :dummy => nil
44
+ )
45
+ runner.execute #Does all the work and calls the "post-prerequisites"
46
+ end
47
+
48
+ desc "Build a pdf-file with pdfLaTeX"
49
+ rule '.pdf' => '.tex' do |t|
50
+ runner = LaTeXRunner.new(
51
+ :main_file => t.source,
52
+ :program => :pdflatex,
53
+ :dummy => nil
54
+ )
55
+ runner.execute #Does all the work and calls the "post-prerequisites"
56
+ end
57
+
58
+ desc "Call Makeindex"
59
+ tex_postrule '.ind' => '.idx' do |t, args |
60
+ #check for splitidx
61
+ splitidx = Splitindex.new(t.source, Rake.application.latexrunner.logger)
62
+ if splitidx.makeindex? #standard index, no splitidx
63
+ #makeindex writes to stderr -> catch it
64
+ stdout, stderr = catch_screen_output{
65
+ sh "makeindex #{t.source}"
66
+ }
67
+ else #splitidx used
68
+ #call splitindex
69
+ splitidx.execute()
70
+ end
71
+ end
72
+
73
+ desc "Call BibTeX"
74
+ #Define the precheck
75
+ tex_postrule_check '.bbl' do |args|
76
+ necessary = false
77
+ #Check for change in aux-file
78
+ #This must be done outside the prerequisites-loop.
79
+ #With definition
80
+ # file 'testdocument.pdf' => 'testdocument.bib'
81
+ #the auxfile is missing in prerequisites and we loose the 3rd run.
82
+ auxfile = args[:task].name.ext('aux')
83
+ if File.exist?(auxfile)
84
+ #BibTeX should only start when BibTeX is really used.
85
+ if ( args[:checksums][auxfile] == :changed and File.read(auxfile) =~ /bibdata/ )
86
+ necessary = "#{auxfile} changed"
87
+ end
88
+ end
89
+ #Check all prerequisites.
90
+ #This should be the aux-file and (optional) bib-files.
91
+ args[:task].prerequisites.each{|post_prereq_pre|
92
+ args[:logger].debug("\tCheck dependency <#{post_prereq_pre}>")
93
+ case post_prereq_pre
94
+ when /\.aux\Z/ #Already done
95
+ when String #Name of the file (normally xxx.bib)
96
+ #compare timestamps of last modification
97
+ if File.exist?(post_prereq_pre)
98
+ if args[:task].timestamp < File.mtime(post_prereq_pre)
99
+ args[:logger].debug("Bibbliography <#{post_prereq_pre}> changed")
100
+ necessary = "#{post_prereq_pre} changed"
101
+ end
102
+ else #File not found
103
+ args[:logger].warn("Bibbliography: File <#{post_prereq_pre}> not found for pre-check")
104
+ necessary = "#{post_prereq_pre}"
105
+ end #File.exist?
106
+ else
107
+ args[:logger].warn("Bibbliography: Don't know how to pre-check <#{post_prereq_pre.inspect}>")
108
+ necessary = "#{post_prereq_pre}"
109
+ end #case post_prereq_pre
110
+ break if necessary
111
+ } unless necessary
112
+ necessary #return parameter
113
+ end
114
+ #Define the rule
115
+ tex_postrule '.bbl' => '.aux' do |t|
116
+ stdout, stderr = catch_screen_output{
117
+ puts `bibtex #{t.source}`
118
+ }
119
+ if $? != 0
120
+ Rake.application.latexrunner.logger.fatal("There where BibTeX errors. \n#{stdout}")
121
+ end
122
+ end
123
+
124
+ desc "Build a statistic for TeX project"
125
+
126
+ task :statistic => :basefile do |t|
127
+ puts "Statistics:"
128
+ t.prerequisites.each{|pre|
129
+ next if pre == 'basefile' #exclude task :basefile
130
+ stat = TeX_Statistic.new(pre)
131
+ puts stat
132
+ }
133
+ end
134
+