rake4latex 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+