wortsammler 1.0.3 → 2.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.
Files changed (69) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +46 -4
  3. data/.gitpod.Dockerfile +32 -0
  4. data/.gitpod.yml +1 -0
  5. data/.idea/.name +1 -0
  6. data/.idea/.rakeTasks +7 -0
  7. data/.idea/202_wortsammler-gem.iml +119 -0
  8. data/.idea/compiler.xml +22 -0
  9. data/.idea/encodings.xml +6 -0
  10. data/.idea/inspectionProfiles/profiles_settings.xml +7 -0
  11. data/.idea/misc.xml +4 -0
  12. data/.idea/modules.xml +9 -0
  13. data/.idea/vcs.xml +7 -0
  14. data/Gemfile +1 -0
  15. data/README.md +25 -28
  16. data/Rakefile +3 -3
  17. data/changelog.md +17 -1
  18. data/lib/wortsammler.rb +61 -54
  19. data/lib/wortsammler/class.Traceable.md.rb +9 -7
  20. data/lib/wortsammler/class.Traceable.rb +74 -60
  21. data/lib/wortsammler/class.proolib.rb +1102 -982
  22. data/lib/wortsammler/mdTraceParser.treetop +2 -2
  23. data/lib/wortsammler/version.rb +1 -1
  24. data/resources/default.wortsammler.latex +4 -3
  25. data/resources/main.md +1 -1
  26. data/resources/pandocdefault.docx +0 -0
  27. data/resources/pandocdefault.epub +70 -0
  28. data/resources/pandocdefault.html +73 -0
  29. data/resources/pandocdefault.latex +403 -0
  30. data/resources/sample_the-sample-document.yaml +27 -1
  31. data/spec/TC_EXP_001.md +2 -1
  32. data/spec/Zupfnoter.jpg +0 -0
  33. data/spec/tc_exp_003_reference.txt +14 -29
  34. data/spec/test_beautify.md +13 -1
  35. data/spec/test_beautify_reference.md +17 -3
  36. data/spec/test_mkindex_reference.txt +9 -38
  37. data/spec/test_slides.md +38 -0
  38. data/spec/wortsammler_spec.rb +186 -150
  39. data/testproject.xx/30_Sources/001_Main/main.md +273 -0
  40. data/testproject.xx/30_Sources/900_snippets/snippets.xlsx +0 -0
  41. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_compact.docx +0 -0
  42. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_compact.html +145 -0
  43. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_compact.latex +416 -0
  44. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_compact.pdf +0 -0
  45. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_folien.beamer.pdf +0 -0
  46. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_folien.docx +0 -0
  47. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_folien.html +145 -0
  48. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_folien.latex +416 -0
  49. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_folien.pdf +0 -0
  50. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_mieter.docx +0 -0
  51. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_mieter.html +145 -0
  52. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_mieter.latex +416 -0
  53. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_mieter.pdf +0 -0
  54. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_review.latex +582 -0
  55. data/testproject.xx/30_Sources/ZGEN_Documents/RS_Main_review.pdf +0 -0
  56. data/testproject.xx/30_Sources/ZGEN_RequirementsTracing/RS_Main.traces.md +56 -0
  57. data/testproject.xx/30_Sources/ZGEN_RequirementsTracing/ZGEN_Reqtrace.graphml +119 -0
  58. data/testproject.xx/30_Sources/ZGEN_RequirementsTracing/ZGEN_Reqtrace.md +50 -0
  59. data/testproject.xx/30_Sources/ZGEN_RequirementsTracing/ZGEN_ReqtraceCompare.txt +52 -0
  60. data/testproject.xx/30_Sources/ZSUPP_Manifests/sample_the-sample-document.yaml +79 -0
  61. data/testproject.xx/30_Sources/ZSUPP_Styles/default.wortsammler.latex +321 -0
  62. data/testproject.xx/30_Sources/ZSUPP_Styles/logo.jpg +0 -0
  63. data/testproject.xx/30_Sources/ZSUPP_Tools/rakefile.rb +5 -0
  64. data/testresults/wortsammler_testresults.html +49 -466
  65. data/uninstall-pandoc.pl +79 -0
  66. data/wortsammler.gemspec +7 -4
  67. metadata +84 -14
  68. data/testresults/wortsammler_testresults.log +0 -325
  69. data/wortsammler-gem.sublime-project +0 -8
@@ -1,982 +1,1102 @@
1
- #
2
- # This script converts the trace-References in a markdown file
3
- # to hot references.
4
- #
5
- # usage prepareTracingInPandoc <infile> <format> <outfile>
6
- #
7
- # Traces are formatted according to [RS_DM_008].
8
- #
9
- # Trace itself becomes the target, uptraces are converted to references.
10
- #
11
- # Traces can also be referenced by
12
- #
13
- #
14
- require 'rubygems'
15
- require 'yaml'
16
- require 'tmpdir'
17
- require 'nokogiri'
18
- require "rubyXL"
19
- require 'logger'
20
- require 'wortsammler/latex_helper'
21
-
22
-
23
- Encoding.default_external = Encoding::UTF_8
24
- Encoding.default_internal = Encoding::UTF_8
25
-
26
- # TODO: make these patterns part of the configuration
27
-
28
- ANY_ANCHOR_PATTERN = /<a\s+id=\"([^\"]+)\"\/>/
29
- ANY_REF_PATTERN = /<a\s+href=\"#([^\"]+)\"\>([^<]*)<\/a>/
30
-
31
- TRACE_ANCHOR_PATTERN = /\[(\w+_\w+_\w+)\](\s*\*\*)/
32
- UPTRACE_REF_PATTERN = /\}\( ((\w+_\w+_\w+) (,\s*\w+_\w+_\w+)*)\)/x
33
- TRACE_REF_PATTERN = /->\[(\w+_\w+_\w+)\]/
34
-
35
- # filename
36
- # heading
37
- # level
38
- # pages to include
39
- # pageclearance
40
- INCLUDE_PDF_PATTERN = /^\s+~~PDF\s+"(.+)" \s+ "(.+)" \s* (\d*) \s* (\d+-\d+)? \s* (clearpage|cleardoublepage)?~~/x
41
-
42
- INCLUDE_MD_PATTERN = /(\s*)~~MD\s+"(.+)"~~/x
43
-
44
- SNIPPET_PATTERN = /(\s*)~~SN \s+ (\w+)~~/x
45
-
46
- EMBEDDED_IMAGE_PATTERN = /~~EMBED\s+ "(.+)" \s+ (r|l|i|o) \s+ (.+) \s+ (.+)~~/x
47
-
48
- EXPECTED_RESULT_PATTERN = /(^\s*)~~~~\s*\{.expectedResult\s+label=\"([A-Za-z]+_[A-Za-z]+_[0-9]+)\"}\s([^~]*)~~~~/x
49
-
50
- PLANTUML_PATTERN = /[~`]{3,}\s+{\.plantuml}\s+@startuml\s+([^\n]+)(\s+title\s+([^\n]+))?[^~`]+[~`]{3,}/x
51
-
52
- #
53
- # This mixin convertes a file path to the os Path representation
54
- # todo maybe replace this by a builtin ruby stuff such as "pathname"
55
- #
56
- class String
57
- # convert the string to a path notation of the current operating system
58
- def to_osPath
59
- gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
60
- end
61
-
62
- # convert the string to a path notation of ruby.
63
- def to_rubyPath
64
- gsub(File::ALT_SEPARATOR || File::SEPARATOR, File::SEPARATOR)
65
- end
66
-
67
- # adding quotes around the string. Main purpose is to escape blanks
68
- # in file paths.
69
- def esc
70
- "\"#{self}\""
71
- end
72
- end
73
-
74
-
75
-
76
- #
77
- # This class provides methods to tweak the reference according to the
78
- # target document format
79
- #
80
- #
81
- class ReferenceTweaker
82
-
83
- #This attribute keeps the target format
84
- attr_accessor :target, :log
85
-
86
-
87
- private
88
-
89
- # this prepares the reference in the target format
90
- #
91
- # :string: the Id of the referenced Traceable
92
- def prepareTraceReferences(string)
93
- result=string.gsub(/\s*/,"").split(",").map{|trace|
94
- itrace = mkInternalTraceId(trace)
95
- texTrace = mkTexTraceDisplay(trace)
96
- if @target == "pdf" then
97
- "\\hyperlink{#{itrace}}{#{texTrace}}"
98
- else
99
- "[#{trace}](\##{itrace})"
100
- end
101
- }
102
- result.join(", ")
103
- end
104
-
105
-
106
- #
107
- # [prepareExpectedResults description]
108
- # @param label [type] [description]
109
- # @param body [type] [description]
110
- #
111
- # @return [type] [description]
112
- def prepareExpectedResults(indent="", original_label, body)
113
- result_items=body.split("- ")[1..-1].map{|i|i.strip}
114
- result= ["\\begin{Form}"]
115
-
116
- label=original_label.gsub(/_/, "-")
117
- j="00"
118
- result << result_items.map{|i|
119
- j = j.next
120
- "\\CheckBox[name=#{label}-#{j}]{} #{i}"
121
- }
122
- result << "\\vspace{1em}"
123
- result << "\\ChoiceMenu[combo, name=#{label}-verdict, default=none]{Test verdict:}{none, ok-30, ok-60, ok, fail, pending}"
124
- result << "\\vspace{1em}"
125
- result << ["\\TextField[ name=#{label}-comment , width=40em, height=2cm, multiline=true, backgroundcolor={0.9 0.9 0.9}] {}"]
126
- result << ["\\end{Form}"]
127
-
128
- unless $1.nil? then
129
- leading_whitespace=$1.split("\n",100)
130
- leading_lines=leading_whitespace[0..-1].join("\n")
131
- leading_spaces=leading_whitespace.last || ""
132
- replacetext=leading_lines+replacetext_raw.gsub("\n", "\n#{leading_spaces}")
133
- end
134
-
135
- result=result.compact.flatten.map{|i|"#{indent}#{i}"}
136
- result.join("\n#{indent}\n")
137
- end
138
-
139
- # this tweaks the reference-Id to be comaptible as TeX label
140
- # private methd
141
- def mkInternalTraceId(string)
142
- string.gsub("_","-")
143
- end
144
-
145
- # this tweaks the reference-id to be displayed in TeX
146
- # private method
147
- def mkTexTraceDisplay(trace)
148
- trace.gsub("_", "\\_")
149
- end
150
-
151
-
152
-
153
- #
154
- # This replaces markdown inlays
155
- # it is a subroutine which is called
156
- # recursively
157
- # todo: handle indentation
158
- #
159
- # @param text [String] text in which the markdown inlays shall be processed
160
- # @return [String] The resulting text
161
- def replace_md_inlay(text)
162
- text.gsub!(INCLUDE_MD_PATTERN){|m|
163
- if File.exist?($2) then
164
- replacetext_raw=File.open($2,:encoding => 'bom|utf-8').read
165
- unless $1.nil? then
166
- leading_whitespace=$1.split("\n",100)
167
- leading_lines=leading_whitespace[0..-1].join("\n")
168
- leading_spaces=leading_whitespace.last || ""
169
- replacetext=leading_lines+replacetext_raw.gsub("\n", "\n#{leading_spaces}")
170
- end
171
- else
172
- replacetext=""
173
- @log.warn("File not found: #{$2}")
174
- end
175
- result=replace_md_inlay(replacetext)
176
- result
177
- }
178
- text
179
- end
180
-
181
- public
182
-
183
- # constructor
184
- # :target: the target format
185
- # in which the referneces shall be represented
186
- #todo: improve logger approach
187
- def initialize(target, logger=nil)
188
- @target=target
189
-
190
- @log=logger || $logger || nil
191
-
192
- if @log == nil
193
- @log = Logger.new(STDOUT)
194
- @log.level = Logger::INFO
195
- @log.datetime_format = "%Y-%m-%d %H:%M:%S"
196
- @log.formatter = proc do |severity, datetime, progname, msg|
197
- "#{datetime}: #{msg}\n"
198
- end
199
- end
200
- end
201
-
202
- # this does the postprocessing
203
- # of the file
204
- # in particluar handle wortsammler's specific syntax.
205
- def prepareFile(infile, outfile)
206
-
207
- infileIo=File.new(infile)
208
- text = infileIo.readlines.join
209
- infileIo.close
210
-
211
- #include pdf files
212
-
213
- if @target == "pdf"
214
- text.gsub!(INCLUDE_PDF_PATTERN){|m|
215
-
216
- if $4
217
- pages="[pages=#{$4}]"
218
- else
219
- pages=""
220
- end
221
-
222
- if $5
223
- clearpage=$5
224
- else
225
- clearpage="cleardoublepage"
226
- end
227
-
228
- if $3.length > 0
229
- level=$3
230
- else
231
- level=9
232
- end
233
-
234
- "\n\n\\#{clearpage}\n\\bookmark[level=#{level},page=\\thepage]{#{$2}}\n\\includepdf#{pages}{#{$1}}"
235
- }
236
- else #if not pdf then it gets a regular external link
237
- text.gsub!(INCLUDE_PDF_PATTERN){|m|
238
- "[#{$2}](#{$1})"
239
- }
240
- end
241
-
242
- # include Markdown files
243
- #
244
- #
245
- text = replace_md_inlay(text)
246
-
247
-
248
- # embed images
249
- #
250
- if @target == "pdf"
251
- text.gsub!(EMBEDDED_IMAGE_PATTERN){|m|
252
- "\\wsembedimage{#{$1}}{#{$2}}{#{$3}}{#{$4}}"
253
- }
254
- else #if not pdf then it gets a regular image
255
- text.gsub!(EMBEDDED_IMAGE_PATTERN){|m|
256
- "![#{$1}](#{$1})"
257
- }
258
- end
259
-
260
- #inject the anchors for references to traces ->[traceid]
261
- if @target == "pdf" then
262
- text.gsub!(TRACE_ANCHOR_PATTERN){|m| "[#{$1}]#{$2}\\hypertarget{#{mkInternalTraceId($1)}}{}"}
263
- else
264
- text.gsub!(TRACE_ANCHOR_PATTERN){|m| "<a id=\"#{mkInternalTraceId($1)}\">[#{$1}]</a>#{$2}"}
265
- end
266
-
267
- #substitute arbitrary anchors for arbitrary targets <a id="">
268
- if @target == "pdf" then
269
- text.gsub!(ANY_ANCHOR_PATTERN){|m| "\\hypertarget{#{mkInternalTraceId($1)}}{}"}
270
- else
271
- # it is already html
272
- end
273
-
274
- #substitute arbitrary document internal references <a href=""></a>
275
- if @target == "pdf" then
276
- text.gsub!(ANY_REF_PATTERN){|m| "\\hyperlink{#{$1}}{#{mkTexTraceDisplay($2)}}"}
277
- else
278
- # it is already html
279
- end
280
-
281
- # substitute the uptrace references
282
- text.gsub!(UPTRACE_REF_PATTERN){|m| "}(#{prepareTraceReferences($1)})"}
283
-
284
- # substitute the informal trace references
285
- text.gsub!(TRACE_REF_PATTERN){|m| "[#{prepareTraceReferences($1)}]"}
286
-
287
-
288
- # substitute expected Results
289
- #
290
- #
291
- if @target == "pdf" then
292
- text.gsub!(EXPECTED_RESULT_PATTERN){|m| "#{prepareExpectedResults($1, $2, $3)}"}
293
- else
294
- # it is already leave it as it is
295
- end
296
-
297
- # substitute plantuml
298
- #
299
- # note this is substituted in any case
300
- #
301
- #if @target == "pdf" then
302
- text.gsub!(PLANTUML_PATTERN){|m| ""}
303
-
304
- #else
305
- # it is already leave it as it is
306
- #end
307
-
308
- File.open(outfile, "w"){|f| f.puts(text)}
309
- end
310
- end
311
-
312
-
313
-
314
- #
315
- # This class handles the configuration of WortSammler framework
316
- #
317
-
318
- class ProoConfig
319
- attr_reader :input, # An array with the input filenames
320
- :outdir, # directory where to place the output files
321
- :outname, # basis to determine the output files
322
- :format, # array of output formats
323
- :traceSortOrder, # Array of strings to determine the sort ord
324
- :vars, # hash of variables for pandoc
325
- :editions, # hash of editions for pandoc
326
- :snippets, # Array of strings to determine snippet filenames
327
- :upstream_tracefiles, # Array of strings to determine upstream tracefile names
328
- :downstream_tracefile, # String to save downstram filename
329
- :reqtracefile_base, # string to determine the requirements tracing results
330
- :frontmatter, # Array of string to determine input filenames of frontmatter
331
- :rootdir, # String directory of the configuration file
332
- :stylefiles # Hash of stylefiles path to pandoc latex style file
333
-
334
-
335
- # constructor
336
- # @param [String] configFileName name of the configfile (without .yaml)
337
- # @param [Symbol] configSelect Default configuration. If not specified
338
- # the very first entry in the config file
339
- # will apply.
340
- # TODO: not yet implemented.
341
- # @return [ProoConfig] instance
342
- def initialize(configFileName, configSelect=nil)
343
- begin
344
- config = YAML.load(File.new(configFileName))
345
- rescue Exception => e
346
- unless File.exist?(configFileName) then
347
- $log.error "config file not found '#{configFileName}'"
348
- else
349
- $log.error "config file could not be loaded '#{configFileName}'"
350
- if File.directory?(configFileName)then
351
- # note that windows does not disinguish this.
352
- $log.error "#{configFileName} is a directory"
353
- end
354
- $log.error "reason '#{e.message}'"
355
- end
356
- exit(false)
357
- end
358
-
359
- basePath = File.dirname(configFileName)
360
-
361
- # this makes an absolute path based on the absolute path
362
- # of the configuration file
363
- expand_path=lambda do |lf|
364
- File.expand_path("#{basePath}/#{lf}")
365
- end
366
-
367
-
368
- #activeConfigs=config.select{|x| [x[:name]] & ConfigSelet}
369
-
370
- selectedConfig=config.first
371
- #TODO: check the config file
372
- @input = selectedConfig[:input].map{|file| File.expand_path("#{basePath}/#{file}")}
373
- @outdir = File.expand_path("#{basePath}/#{selectedConfig[:outdir]}")
374
- @outname = selectedConfig[:outname]
375
- @format = selectedConfig[:format]
376
- @traceSortOrder = selectedConfig[:traceSortOrder]
377
- @vars = selectedConfig[:vars] || {}
378
- @editions = selectedConfig[:editions] || nil
379
-
380
- @downstream_tracefile = selectedConfig[:downstream_tracefile] || nil
381
-
382
- @reqtracefile_base = selectedConfig[:reqtracefile_base] #todo expand path
383
-
384
- @upstream_tracefiles = selectedConfig[:upstream_tracefiles] || nil
385
- @upstream_tracefiles = @upstream_tracefiles.map{|file| File.expand_path("#{basePath}/#{file}")} unless @upstream_tracefiles.nil?
386
- @frontmatter = selectedConfig[:frontmatter] || nil
387
- @frontmatter = selectedConfig[:frontmatter].map{|file| File.expand_path("#{basePath}/#{file}")} unless @frontmatter.nil?
388
- @rootdir = basePath
389
-
390
- stylefiles = selectedConfig[:stylefiles] || nil
391
- if stylefiles.nil?
392
- @stylefiles = {
393
- :latex => expand_path.call("../ZSUPP_Styles/default.latex"),
394
- :docx => expand_path.call("../ZSUPP_Styles/default.docx"),
395
- :html => expand_path.call("../ZSUPP_Styles/default.css")
396
- }
397
- else
398
- @stylefiles = stylefiles.map{ |key,value| {key => expand_path.call(value)} }.reduce(:merge)
399
- end
400
-
401
- snippets = selectedConfig[:snippets]
402
- if snippets.nil?
403
- @snippets = nil
404
- else
405
- @snippets = snippets.map{|file| File.expand_path("#{basePath}/#{file}")}
406
- end
407
- end
408
- end
409
-
410
-
411
- #
412
- # This class provides the major functionalites
413
- #
414
- # Note that it is called PandocBeautifier for historical reasons
415
- #
416
- # provides methods to Process a pandoc file
417
- #
418
-
419
- class PandocBeautifier
420
-
421
- attr_accessor :log
422
-
423
- # the constructor
424
- # @param [Logger] logger logger object to be applied.
425
- # if none is specified, a default logger
426
- # will be implemented
427
-
428
- def initialize(logger=nil)
429
-
430
- @view_pattern = /~~ED((\s*(\w+))*)~~/
431
- # @view_pattern = /<\?ED((\s*(\w+))*)\?>/
432
- @tempdir = Dir.mktmpdir
433
-
434
- @log=logger || $logger || nil
435
-
436
- if @log == nil
437
- @log = Logger.new(STDOUT)
438
- @log.level = Logger::INFO
439
- @log.datetime_format = "%Y-%m-%d %H:%M:%S"
440
- @log.formatter = proc do |severity, datetime, progname, msg|
441
- "#{datetime}: #{msg}\n"
442
- end
443
-
444
- end
445
- end
446
-
447
-
448
-
449
- #
450
-
451
- # This checks if an appropriate pandoc version can be
452
- # started on the machine
453
-
454
- #
455
-
456
- # @return [boolean] true if an appropriate version is available
457
- def check_pandoc_version
458
- required_version_string="1.11"
459
- begin
460
- pandoc_version=`pandoc -v`.split("\n").first.split(" ")[1]
461
- if pandoc_version < required_version_string then
462
- @log.error "found pandoc #{pandoc_version} need #{required_version_string}"
463
- result = false
464
- else
465
- result = true
466
- end
467
- rescue Exception => e
468
- @log.error("could not run pandoc: #{e.message}")
469
- result=false
470
- end
471
- result
472
- end
473
-
474
- # perform the beautify
475
- # * process the file with pandoc
476
- # * revoke some quotes introduced by pandoc
477
- # @param [String] file the name of the file to be beautified
478
- def beautify(file)
479
-
480
- @log.debug(" Cleaning: \"#{file}\"")
481
-
482
- docfile = File.new(file)
483
- olddoc = docfile.readlines.join
484
- docfile.close
485
-
486
- # process the file in pandoc
487
- cmd="pandoc -s #{file.esc} -f markdown -t markdown --atx-headers --id-prefix=#{File.basename(file).esc}_ "
488
- newdoc = `#{cmd}`
489
- @log.debug "beautify #{file.esc}: #{$?}"
490
- @log.debug(" finished: \"#{file}\"")
491
-
492
- # tweak the quoting
493
- if $?.success? then
494
- # do this twice since the replacement
495
- # does not work on e.g. 2\_3\_4\_5.
496
- #
497
- newdoc.gsub!(/(\w)\\_(\w)/, '\1_\2')
498
- newdoc.gsub!(/(\w)\\_(\w)/, '\1_\2')
499
-
500
- # fix more quoting
501
- newdoc.gsub!('-\\>[', '->[')
502
-
503
- # (RS_Mdc)
504
- # TODO: fix Table width toggles sometimes
505
- if (not olddoc == newdoc) then ##only touch the file if it is really changed
506
- File.open(file, "w"){|f| f.puts(newdoc)}
507
- File.open(file+".bak", "w"){|f| f.puts(olddoc)} # (RS_Mdc_) # remove this if needed
508
- @log.debug(" cleaned: \"#{file}\"")
509
- else
510
- @log.debug("was clean: \"#{file}\"")
511
- end
512
- #TODO: error handling here
513
- else
514
- @log.error("error calling pandoc - please watch the screen output")
515
- end
516
- end
517
-
518
-
519
- # this replaces the text snippets in files
520
- def replace_snippets_in_file(infile, snippets)
521
- input_data = File.open(infile){|f| f.readlines.join}
522
- output_data=input_data.clone
523
-
524
- @log.debug("replacing snippets in #{infile}")
525
-
526
- replace_snippets_in_text(output_data, snippets)
527
-
528
- if (not input_data == output_data)
529
- File.open(infile, "w"){|f| f.puts output_data}
530
- end
531
- end
532
-
533
- # this replaces the snippets in a text
534
- def replace_snippets_in_text(text, snippets)
535
- changed=false
536
- text.gsub!(SNIPPET_PATTERN){|m|
537
- replacetext_raw=snippets[$2.to_sym]
538
-
539
- if replacetext_raw
540
- changed=true
541
- unless $1.nil? then
542
- leading_whitespace=$1.split("\n",100)
543
- leading_lines=leading_whitespace[0..-1].join("\n")
544
- leading_spaces=leading_whitespace.last || ""
545
- replacetext=leading_lines+replacetext_raw.gsub("\n", "\n#{leading_spaces}")
546
- end
547
- @log.debug("replaced snippet #{$2} with #{replacetext}")
548
- else
549
- replacetext=m
550
- @log.warn("Snippet not found: #{$2}")
551
- end
552
- replacetext
553
- }
554
- #recursively process nested snippets
555
- #todo: this approach might rais undefined snippets twice if there are defined and undefined ones
556
- replace_snippets_in_text(text, snippets) if changed==true
557
- end
558
-
559
-
560
- #
561
- # Ths determines the view filter
562
- #
563
- # @param [String] line - the current input line
564
- # @param [String] view - the currently selected view
565
- #
566
- # @return true/false if a view-command is found, else nil
567
- def get_filter_command(line, view)
568
- r = line.match(@view_pattern)
569
-
570
- if not r.nil?
571
- found = r[1].split(" ")
572
- result = (found & [view, "all"].flatten).any?
573
- else
574
- result = nil
575
- end
576
-
577
- result
578
- end
579
-
580
- #
581
- # This filters the document according to the target audience
582
- #
583
- # @param [String] inputfile name of inputfile
584
- # @param [String] outputfile name of outputfile
585
- # @param [String] view - name of intended view
586
-
587
- def filter_document_variant(inputfile, outputfile, view)
588
-
589
- input_data = File.open(inputfile){|f| f.readlines}
590
-
591
- output_data = Array.new
592
- is_active = true
593
- input_data.each{|l|
594
- switch=self.get_filter_command(l, view)
595
- l.gsub!(@view_pattern, "")
596
- is_active = switch unless switch.nil?
597
- @log.debug "select edtiion #{view}: #{is_active}: #{l.strip}"
598
-
599
- output_data << l if is_active
600
- }
601
-
602
- File.open(outputfile, "w"){|f| f.puts output_data.join }
603
- end
604
-
605
- #
606
- # This filters the document according to the target audience
607
- #
608
- # @param [String] inputfile name of inputfile
609
- # @param [String] outputfile name of outputfile
610
- # @param [String] view - name of intended view
611
-
612
- def process_debug_info(inputfile, outputfile, view)
613
-
614
- input_data = File.open(inputfile){|f| f.readlines }
615
-
616
- output_data = Array.new
617
-
618
- input_data.each{|l|
619
- l.gsub!(@view_pattern){|p|
620
- if $1.strip == "all" then
621
- color="black"
622
- else
623
- color="red"
624
- end
625
-
626
- "\\color{#{color}}\\rule{2cm}{0.5mm}\\newline\\marginpar{#{$1.strip}}"
627
-
628
- }
629
-
630
- l.gsub!(/todo:|TODO:/){|p| "#{p}\\marginpar{TODO}"}
631
-
632
- output_data << l
633
- }
634
-
635
- File.open(outputfile, "w"){|f| f.puts output_data.join }
636
- end
637
-
638
-
639
- # This compiles the input documents to one single file
640
- # it also beautifies the input files
641
- #
642
- # @param [Array of String] input - the input files to be processed in the given sequence
643
- # @param [String] output - the the name of the output file
644
- def collect_document(input, output)
645
- inputs=input.map{|xx| xx.esc.to_osPath }.join(" ") # qoute cond combine the inputs
646
- inputname=File.basename(input.first)
647
-
648
- #now combine the input files
649
- @log.debug("combining the input files #{inputname} et al")
650
- cmd="pandoc -s -o #{output} --ascii #{inputs}" # note that inputs is already quoted
651
- system(cmd)
652
- if $?.success? then
653
- PandocBeautifier.new().beautify(output)
654
- end
655
- end
656
-
657
- #
658
- # This loads snipptes from xlsx file
659
- # @param [String] file name of the xlsx file
660
- # @return [Hash] a hash with the snippetes
661
- #
662
- def load_snippets_from_xlsx(file)
663
- temp_filename = "#{@tempdir}/snippett.xlsx"
664
- FileUtils::copy(file, temp_filename)
665
- wb=RubyXL::Parser.parse(temp_filename)
666
- result={}
667
- wb.first.each{|row|
668
- key, the_value = row
669
- unless key.nil?
670
- unless the_value.nil?
671
- result[key.value.to_sym] = resolve_xml_entities(the_value.value) rescue ""
672
- end
673
- end
674
- }
675
- result
676
- end
677
-
678
- #
679
- # this resolves xml entities in Text (lt, gt, amp)
680
- # @param [String] text with entities
681
- # @return [String] text with replaced entities
682
- def resolve_xml_entities(text)
683
- result=text
684
- result.gsub!("&lt;", "<")
685
- result.gsub!("&gt;", ">")
686
- result.gsub!("&amp;", "&")
687
- result
688
- end
689
-
690
- #
691
- # This generates the final document
692
- #
693
- # It actually does this in two steps:
694
- #
695
- # 1. process front matter to laTeX
696
- # 2. process documents
697
- #
698
- # @param [Array of String] input the input files to be processed in the given sequence
699
- # @param [String] outdir the output directory
700
- # @param [String] outname the base name of the output file. It is a basename in case the
701
- # output format requires multiple files
702
- # @param [Array of String] format list of formats which shall be generated.
703
- # supported formats: "pdf", "latex", "html", "docx", "rtf", txt
704
- # @param [Hash] vars - the variables passed to pandoc
705
- # @param [Hash] editions - the editions to process; default nil - no edition processing
706
- # @param [Array of String] snippetfiles the list of files containing snippets
707
- # @param [String] frontmatter file path to frontmatter the file to processed as frontmatter
708
- # @param [ProoConfig] config - the configuration file to be used
709
- def generateDocument(input, outdir, outname, format, vars, editions=nil, snippetfiles=nil, frontmatter=nil, config=nil)
710
-
711
- # combine the input files
712
-
713
- temp_filename = "#{@tempdir}/x.md".to_osPath
714
- temp_frontmatter = "#{@tempdir}/xfrontmatter.md".to_osPath unless frontmatter.nil?
715
- collect_document(input, temp_filename)
716
- collect_document(frontmatter, temp_frontmatter) unless frontmatter.nil?
717
-
718
- # process the snippets
719
-
720
- if not snippetfiles.nil?
721
- snippets={}
722
- snippetfiles.each{|f|
723
- if File.exists?(f)
724
- type=File.extname(f)
725
- case type
726
- when ".yaml"
727
- x=YAML.load(File.new(f))
728
- when ".xlsx"
729
- x=load_snippets_from_xlsx(f)
730
- else
731
- @log.error("Unsupported File format for snipptets: #{type}")
732
- x={}
733
- end
734
- snippets.merge!(x)
735
- else
736
- @log.error("Snippet file not found: #{f}")
737
- end
738
- }
739
-
740
- replace_snippets_in_file(temp_filename, snippets)
741
- end
742
-
743
- vars_frontmatter=vars.clone
744
- vars_frontmatter[:usetoc] = "nousetoc"
745
-
746
-
747
- if editions.nil?
748
- # there are no editions
749
- unless frontmatter.nil? then
750
- render_document(temp_frontmatter, tempdir, temp_frontmatter, ["frontmatter"], vars_frontmatter)
751
- vars[:frontmatter] = "#{tempdir}/#{temp_frontmatter}.latex"
752
- end
753
- render_document(temp_filename, outdir, outname, format, vars, config)
754
- else
755
- # process the editions
756
- editions.each{|edition_name, properties|
757
- edition_out_filename = "#{outname}_#{properties[:filepart]}"
758
- edition_temp_frontmatter = "#{@tempdir}/#{edition_out_filename}_frontmatter.md" unless frontmatter.nil?
759
- edition_temp_filename = "#{@tempdir}/#{edition_out_filename}.md"
760
- vars[:title] = properties[:title]
761
-
762
- if properties[:debug]
763
- process_debug_info(temp_frontmatter, edition_temp_frontmatter, edition_name.to_s) unless frontmatter.nil?
764
- process_debug_info(temp_filename, edition_temp_filename, edition_name.to_s)
765
- lvars=vars.clone
766
- lvars[:linenumbers] = "true"
767
- unless frontmatter.nil? # frontmatter
768
- lvars[:usetoc] = "nousetoc"
769
- render_document(edition_temp_frontmatter, @tempdir, "xfrontmatter", ["frontmatter"], lvars)
770
- lvars[:usetoc] = vars[:usetoc] || "usetoc"
771
- lvars[:frontmatter] = "#{@tempdir}/xfrontmatter.latex"
772
- end
773
- render_document(edition_temp_filename, outdir, edition_out_filename, ["pdf", "latex"], lvars, config)
774
- else
775
- unless frontmatter.nil? # frontmatter
776
- filter_document_variant(temp_frontmatter, edition_temp_frontmatter, edition_name.to_s)
777
- render_document(edition_temp_frontmatter, @tempdir, "xfrontmatter", ["frontmatter"], vars_frontmatter)
778
- vars[:frontmatter]="#{@tempdir}/xfrontmatter.latex"
779
- end
780
- filter_document_variant(temp_filename, edition_temp_filename, edition_name.to_s)
781
- render_document(edition_temp_filename, outdir, edition_out_filename, format, vars, config)
782
- end
783
- }
784
- end
785
- end
786
-
787
- #
788
-
789
- # render a single file
790
- # @param input [String] path to the inputfile
791
- # @param outdir [String] path to the output directory
792
- # @param format [Array of String] formats
793
- # @return [nil] no useful return value
794
- def render_single_document(input, outdir, format)
795
- outname=File.basename(input, ".*")
796
- render_document(input, outdir, outname, format, {:geometry=>"a4paper"})
797
- end
798
- #
799
- # This renders the final document
800
- # @param [String] input the input file
801
- # @param [String] outdir the output directory
802
- # @param [String] outname the base name of the output file. It is a basename in case the
803
- # output format requires multiple files
804
- # @param [Array of String] format list of formats which shall be generated.
805
- # supported formats: "pdf", "latex", "html", "docx", "rtf", txt
806
- # @param [Hash] vars - the variables passed to pandoc
807
-
808
- # @param config [ProoConfig] the entire config object (for future extensions)
809
- # @return nil
810
-
811
- def render_document(input, outdir, outname, format, vars, config=nil)
812
-
813
- #TODO: Clarify the following
814
- # on Windows, Tempdir contains a drive letter. But drive letter
815
- # seems not to work in pandoc -> pdf if the path separator ist forward
816
- # slash. There are two options to overcome this
817
- #
818
- # 1. set tempdir such that it does not contain a drive letter
819
- # 2. use Dir.mktempdir but ensure that all provided file names
820
- # use the platform specific SEPARATOR
821
- #
822
- # for whatever Reason, I decided for 2.
823
-
824
- tempfile = input
825
- tempfilePdf = "#{@tempdir}/x.TeX.md".to_osPath
826
- tempfileHtml = "#{@tempdir}/x.html.md".to_osPath
827
- outfile = "#{outdir}/#{outname}".to_osPath
828
- outfilePdf = "#{outfile}.pdf"
829
- outfileDocx = "#{outfile}.docx"
830
- outfileHtml = "#{outfile}.html"
831
- outfileRtf = "#{outfile}.rtf"
832
- outfileLatex = "#{outfile}.latex"
833
- outfileText = "#{outfile}.txt"
834
- outfileSlide = "#{outfile}.slide.html"
835
-
836
- if vars.has_key? :frontmatter
837
- latexTitleInclude = "--include-before-body=#{vars[:frontmatter].esc}"
838
- else
839
- latexTitleInclude
840
- end
841
-
842
- #todo: make config required, so it can be reduced to the else part
843
- if config.nil? then
844
- latexStyleFile = File.dirname(File.expand_path(__FILE__))+"/../../resources/default.wortsammler.latex"
845
- latexStyleFile = File.expand_path(latexStyleFile).to_osPath
846
- css_style_file = File.dirname(File.expand_path(__FILE__))+"/../../resources/default.wortsammler.css"
847
- css_style_file = File.expand_path(css_style_file).to_osPath
848
- else
849
- latexStyleFile = config.stylefiles[:latex]
850
- css_style_file = config.stylefiles[:css]
851
- end
852
-
853
-
854
- toc = "--toc"
855
- toc = "" if vars[:usetoc]=="nousetoc"
856
-
857
- if vars[:documentclass]=="book"
858
- option_chapters = "--chapters"
859
- else
860
- option_chapter = ""
861
- end
862
-
863
- begin
864
- vars_string=vars.map.map{|key, value| "-V #{key}=#{value.esc}"}.join(" ")
865
- rescue
866
- require 'pry';binding.pry
867
- end
868
-
869
- @log.info("rendering #{outname} as [#{format.join(', ')}]")
870
-
871
- supported_formats=["pdf", "latex", "frontmatter", "docx", "html", "txt", "rtf", "slide"]
872
- wrong_format=format - supported_formats
873
- wrong_format.each{|f|@log.error("format not supported: #{f}")}
874
-
875
- begin
876
-
877
- if format.include?("frontmatter") then
878
-
879
- ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
880
-
881
- cmd="pandoc -S #{tempfilePdf.esc} --latex-engine xelatex #{vars_string} --ascii -o #{outfileLatex.esc}"
882
- `#{cmd}`
883
- end
884
-
885
-
886
- if (format.include?("pdf") | format.include?("latex")) then
887
- @log.debug("creating #{outfileLatex}")
888
- ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
889
-
890
- cmd="pandoc -S #{tempfilePdf.esc} #{toc} --standalone #{option_chapters} --latex-engine xelatex --number-sections #{vars_string}" +
891
- " --template #{latexStyleFile.esc} --ascii -o #{outfileLatex.esc} #{latexTitleInclude}"
892
- `#{cmd}`
893
- end
894
-
895
-
896
- if format.include?("pdf") then
897
- @log.debug("creating #{outfilePdf}")
898
- ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
899
- #cmd="pandoc -S #{tempfilePdf.esc} #{toc} --standalone #{option_chapters} --latex-engine xelatex --number-sections #{vars_string}" +
900
- # " --template #{latexStyleFile.esc} --ascii -o #{outfilePdf.esc} #{latexTitleInclude}"
901
- cmd="xelatex -halt-on-error -interaction nonstopmode -output-directory=#{outdir.esc} #{outfileLatex.esc}"
902
- #cmdmkindex = "makeindex \"#{outfile.esc}.idx\""
903
-
904
- latex=LatexHelper.new.set_latex_command(cmd).setlogger(@log)
905
- latex.run(outfileLatex)
906
-
907
- messages=latex.log_analyze("#{outdir}/#{outname}.log")
908
-
909
- removeables = ["toc", "aux", "bak", "idx", "ilg", "ind" ]
910
- removeables << "log" unless messages > 0
911
-
912
-
913
- removeables << "latex" unless format.include?("latex")
914
- removeables = removeables.map{|e| "#{outdir}/#{outname}.#{e}"}.select{|f| File.exists?(f)}
915
- removeables.each{|e|
916
- @log.debug "removing file: #{e}"
917
- FileUtils.rm e
918
- }
919
- end
920
-
921
- if format.include?("html") then
922
- #todo: handle css
923
- @log.debug("creating #{outfileHtml}")
924
-
925
- ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
926
-
927
- cmd="pandoc -S #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
928
- " -o #{outfileHtml.esc}"
929
-
930
- `#{cmd}`
931
- end
932
-
933
- if format.include?("docx") then
934
- #todo: handle style file
935
- @log.debug("creating #{outfileDocx}")
936
-
937
- ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
938
-
939
- cmd="pandoc -S #{tempfileHtml.esc} #{toc} --standalone --self-contained --ascii --number-sections #{vars_string}" +
940
- " -o #{outfileDocx.esc}"
941
- cmd="pandoc -S #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
942
- " -o #{outfileDocx.esc}"
943
- `#{cmd}`
944
- end
945
-
946
- if format.include?("rtf") then
947
- @log.debug("creating #{outfileRtf}")
948
- ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
949
-
950
- cmd="pandoc -S #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
951
- " -o #{outfileRtf.esc}"
952
- `#{cmd}`
953
- end
954
-
955
- if format.include?("txt") then
956
- @log.debug("creating #{outfileText}")
957
-
958
- ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfileHtml)
959
-
960
- cmd="pandoc -S #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
961
- " -t plain -o #{outfileText.esc}"
962
- `#{cmd}`
963
- end
964
-
965
- if format.include?("slide") then
966
- @log.debug("creating #{outfileSlide}")
967
-
968
- ReferenceTweaker.new("slide").prepareFile(tempfile, tempfilePdf)
969
- #todo: hanlde stylefile
970
- cmd="pandoc -S #{tempfileHtml.esc} --toc --standalone --number-sections #{vars_string}" +
971
- " --ascii -t dzslides --slide-level 2 -o #{outfileSlide.esc}"
972
- `#{cmd}`
973
- end
974
- rescue Exception => e
975
- @log.error "failed to perform #{cmd}, \n#{e.message}"
976
- @log.error e.backtrace.join("\n")
977
- #TODO make a try catch block kere
978
- end
979
- nil
980
- end
981
-
982
- end
1
+ #
2
+ # This script converts the trace-References in a markdown file
3
+ # to hot references.
4
+ #
5
+ # usage prepareTracingInPandoc <infile> <format> <outfile>
6
+ #
7
+ # Traces are formatted according to [RS_DM_008].
8
+ #
9
+ # Trace itself becomes the target, uptraces are converted to references.
10
+ #
11
+ # Traces can also be referenced by
12
+ #
13
+ #
14
+ require 'rubygems'
15
+ require 'yaml'
16
+ require 'tmpdir'
17
+ require 'nokogiri'
18
+ require "rubyXL"
19
+ require 'logger'
20
+ require 'wortsammler/latex_helper'
21
+
22
+
23
+ Encoding.default_external = Encoding::UTF_8
24
+ Encoding.default_internal = Encoding::UTF_8
25
+
26
+ # TODO: make these patterns part of the configuration
27
+
28
+ PANDOC_EXE ="pandoc_2.5 " # wat
29
+ LATEX_EXE = "xelatex "
30
+
31
+ ANY_ANCHOR_PATTERN = /<a\s+id=\"([^\"]+)\"\/>/
32
+ ANY_REF_PATTERN = /<a\s+href=\"#([^\"]+)\"\>([^<]*)<\/a>/
33
+
34
+ TRACE_ANCHOR_PATTERN = /\[(\w+_\w+_\w+)\](\s*\*\*)/
35
+ UPTRACE_REF_PATTERN = /\}\( ((\w+_\w+_\w+) (,\s*\w+_\w+_\w+)*)\)/x
36
+ TRACE_REF_PATTERN = /->\[(\w+_\w+_\w+)\]/
37
+
38
+ # filename
39
+ # heading
40
+ # level
41
+ # pages to include
42
+ # pageclearance
43
+ INCLUDE_PDF_PATTERN = /^\s+~~PDF\s+"(.+)" \s+ "(.+)" \s* (\d*) \s* (\d+-\d+)? \s* (clearpage|cleardoublepage)?~~/x
44
+
45
+ INCLUDE_MD_PATTERN = /(\s*)~~MD\s+\\?"(.+)\\?"~~/x
46
+
47
+ SNIPPET_PATTERN = /(\s*)~~SN \s+ (\w+)~~/x
48
+
49
+ EMBEDDED_IMAGE_PATTERN = /~~EMBED\s+ "(.+)" \s+ (r|l|i|o) \s+ (.+) \s+ (.+)~~/x
50
+
51
+ EXPECTED_RESULT_PATTERN = /(^\s*)[~`]{3,}\s*\{.expectedResult\s+label=\"([A-Za-z]+\\?_[A-Za-z]+\\?_[0-9]+)\"}\s([^~`]+)[~`]{3,}/x
52
+
53
+ PLANTUML_PATTERN = /[~`]{3,}\s+{\.plantuml}\s+@startuml\s+([^\n]+)(\s+title\s+([^\n]+))?[^~`]+ v/x
54
+
55
+ #
56
+ # This mixin convertes a file path to the os Path representation
57
+ # todo maybe replace this by a builtin ruby stuff such as "pathname"
58
+ #
59
+ class String
60
+ # convert the string to a path notation of the current operating system
61
+ def to_osPath
62
+ gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
63
+ end
64
+
65
+ # convert the string to a path notation of ruby.
66
+ def to_rubyPath
67
+ gsub(File::ALT_SEPARATOR || File::SEPARATOR, File::SEPARATOR)
68
+ end
69
+
70
+ # adding quotes around the string. Main purpose is to escape blanks
71
+ # in file paths.
72
+ def esc
73
+ "\"#{self}\""
74
+ end
75
+ end
76
+
77
+
78
+ #
79
+ # This class provides methods to tweak the reference according to the
80
+ # target document format
81
+ #
82
+ #
83
+ class ReferenceTweaker
84
+
85
+ #This attribute keeps the target format
86
+ attr_accessor :target, :log
87
+
88
+
89
+ private
90
+
91
+ # this prepares the reference in the target format
92
+ #
93
+ # :string: the Id of the referenced Traceable
94
+ def prepareTraceReferences(string)
95
+ result=string.gsub(/\s*/, "").split(",").map { |trace|
96
+ itrace = mkInternalTraceId(trace)
97
+ texTrace = mkTexTraceDisplay(trace)
98
+ if @target == "pdf" then
99
+ "\\hyperlink{#{itrace}}{#{texTrace}}"
100
+ else
101
+ "[#{trace}](\##{itrace})"
102
+ end
103
+ }
104
+ result.join(", ")
105
+ end
106
+
107
+
108
+ #
109
+ # [prepareExpectedResults description]
110
+ # @param label [type] [description]
111
+ # @param body [type] [description]
112
+ #
113
+ # @return [type] [description]
114
+ def prepareExpectedResults(indent="", original_label, body)
115
+ result_items=body.split("- ")[1..-1].map { |i| i.strip }
116
+ result = ["\\begin{Form}"]
117
+
118
+ label=original_label.gsub(/_/, "-")
119
+ j ="00"
120
+ result << result_items.map { |i|
121
+ j = j.next
122
+ "\\CheckBox[name=#{label}-#{j}]{} #{i}"
123
+ }
124
+ result << "\\vspace{1em}"
125
+ result << "\\ChoiceMenu[combo, name=#{label}-verdict, default=none]{Test verdict:}{none, ok-30, ok-60, ok, fail, pending}"
126
+ result << "\\vspace{1em}"
127
+ result << ["\\TextField[ name=#{label}-comment , width=40em, height=2cm, multiline=true, backgroundcolor={0.9 0.9 0.9}] {}"]
128
+ result << ["\\end{Form}"]
129
+
130
+ unless $1.nil? then
131
+ leading_whitespace=$1.split("\n", 100)
132
+ leading_lines =leading_whitespace[0..-1].join("\n")
133
+ leading_spaces =leading_whitespace.last || ""
134
+ replacetext =leading_lines+replacetext_raw.gsub("\n", "\n#{leading_spaces}")
135
+ end
136
+
137
+ result=result.compact.flatten.map { |i| "#{indent}#{i}" }
138
+ result.join("\n#{indent}\n")
139
+ end
140
+
141
+ # this tweaks the reference-Id to be comaptible as TeX label
142
+ # private methd
143
+ def mkInternalTraceId(string)
144
+ string.gsub("_", "-")
145
+ end
146
+
147
+ # this tweaks the reference-id to be displayed in TeX
148
+ # private method
149
+ def mkTexTraceDisplay(trace)
150
+ trace.gsub("_", "\\_")
151
+ end
152
+
153
+
154
+ #
155
+ # This replaces markdown inlays
156
+ # it is a subroutine which is called
157
+ # recursively
158
+ # todo: handle indentation
159
+ #
160
+ # @param text [String] text in which the markdown inlays shall be processed
161
+ # @return [String] The resulting text
162
+ def replace_md_inlay(text)
163
+ text.gsub!(INCLUDE_MD_PATTERN) { |m|
164
+ infile = $2.gsub("\\_", "_")
165
+ if File.exist?(infile) then
166
+ replacetext_raw = File.open(infile, :encoding => 'bom|utf-8').read
167
+ unless $1.nil? then
168
+ leading_whitespace = $1.split("\n", 100)
169
+ leading_lines = leading_whitespace[0 .. -1].join("\n")
170
+ leading_spaces = leading_whitespace.last || ""
171
+ replacetext = leading_lines + replacetext_raw.gsub("\n", "\n#{leading_spaces}")
172
+ else
173
+ replacetext = replacetext_raw
174
+ end
175
+ else
176
+ replacetext = ""
177
+ @log.warn("File not found: #{$2}")
178
+ end
179
+ result = replace_md_inlay(replacetext)
180
+ result
181
+ }
182
+ text
183
+ end
184
+
185
+ public
186
+
187
+ # constructor
188
+ # :target: the target format
189
+ # in which the referneces shall be represented
190
+ #todo: improve logger approach
191
+ def initialize(target, logger=nil)
192
+ @target=target
193
+
194
+ @log=logger || $logger || nil
195
+
196
+ if @log == nil
197
+ @log = Logger.new(STDOUT)
198
+ @log.level = Logger::INFO
199
+ @log.datetime_format = "%Y-%m-%d %H:%M:%S"
200
+ @log.formatter = proc do |severity, datetime, progname, msg|
201
+ "#{datetime}: #{msg}\n"
202
+ end
203
+ end
204
+ end
205
+
206
+ # this does the postprocessing
207
+ # of the file
208
+ # in particluar handle wortsammler's specific syntax.
209
+ def prepareFile(infile, outfile)
210
+
211
+ infileIo=File.new(infile)
212
+ text = infileIo.readlines.join
213
+ infileIo.close
214
+
215
+ #include pdf files
216
+
217
+ if @target == "pdf"
218
+ text.gsub!(INCLUDE_PDF_PATTERN) { |m|
219
+
220
+ if $4
221
+ pages="[pages=#{$4}]"
222
+ else
223
+ pages=""
224
+ end
225
+
226
+ if $5
227
+ clearpage=$5
228
+ else
229
+ clearpage="cleardoublepage"
230
+ end
231
+
232
+ if $3.length > 0
233
+ level=$3
234
+ else
235
+ level=9
236
+ end
237
+
238
+ "\n\n\\#{clearpage}\n\\bookmark[level=#{level},page=\\thepage]{#{$2}}\n\\includepdf#{pages}{#{$1}}"
239
+ }
240
+ else #if not pdf then it gets a regular external link
241
+ text.gsub!(INCLUDE_PDF_PATTERN) { |m|
242
+ "[#{$2}](#{$1})"
243
+ }
244
+ end
245
+
246
+ # include Markdown files
247
+ #
248
+ #
249
+ text = replace_md_inlay(text)
250
+
251
+
252
+ # embed images
253
+ #
254
+ if @target == "pdf"
255
+ text.gsub!(EMBEDDED_IMAGE_PATTERN) { |m|
256
+ "\\wsembedimage{#{$1}}{#{$2}}{#{$3}}{#{$4}}"
257
+ }
258
+ else #if not pdf then it gets a regular image
259
+ text.gsub!(EMBEDDED_IMAGE_PATTERN) { |m|
260
+ "![#{$1}](#{$1})"
261
+ }
262
+ end
263
+
264
+ #inject the anchors for references to traces ->[traceid]
265
+ if @target == "pdf" then
266
+ text.gsub!(TRACE_ANCHOR_PATTERN) { |m| "[#{$1}]#{$2}\\hypertarget{#{mkInternalTraceId($1)}}{}" }
267
+ else
268
+ text.gsub!(TRACE_ANCHOR_PATTERN) { |m| "<a id=\"#{mkInternalTraceId($1)}\">[#{$1}]</a>#{$2}" }
269
+ end
270
+
271
+ #substitute arbitrary anchors for arbitrary targets <a id="">
272
+ if @target == "pdf" then
273
+ text.gsub!(ANY_ANCHOR_PATTERN) { |m| "\\hypertarget{#{mkInternalTraceId($1)}}{}" }
274
+ else
275
+ # it is already html
276
+ end
277
+
278
+ #substitute arbitrary document internal references <a href=""></a>
279
+ if @target == "pdf" then
280
+ text.gsub!(ANY_REF_PATTERN) { |m| "\\hyperlink{#{$1}}{#{mkTexTraceDisplay($2)}}" }
281
+ else
282
+ # it is already html
283
+ end
284
+
285
+ # substitute the uptrace references
286
+ text.gsub!(UPTRACE_REF_PATTERN) { |m| "}(#{prepareTraceReferences($1)})" }
287
+
288
+ # substitute the informal trace references
289
+ text.gsub!(TRACE_REF_PATTERN) { |m| "[#{prepareTraceReferences($1)}]" }
290
+
291
+
292
+ # substitute expected Results
293
+ #
294
+ #
295
+ if @target == "pdf" then
296
+ text.gsub!(EXPECTED_RESULT_PATTERN) { |m| "#{prepareExpectedResults($1, $2, $3)}" }
297
+ else
298
+ # it is already leave it as it is
299
+ end
300
+
301
+ # substitute plantuml
302
+ #
303
+ # note this is substituted in any case
304
+ #
305
+ #if @target == "pdf" then
306
+ text.gsub!(PLANTUML_PATTERN) { |m| "" }
307
+
308
+ #else
309
+ # it is already leave it as it is
310
+ #end
311
+
312
+ File.open(outfile, "w") { |f| f.puts(text) }
313
+ end
314
+ end
315
+
316
+
317
+ #
318
+ # This class handles the configuration of WortSammler framework
319
+ #
320
+
321
+ class ProoConfig
322
+ attr_reader :input, # An array with the input filenames
323
+ :outdir, # directory where to place the output files
324
+ :outname, # basis to determine the output files
325
+ :format, # array of output formats
326
+ :traceSortOrder, # Array of strings to determine the sort ord
327
+ :vars, # hash of variables for pandoc
328
+ :editions, # hash of editions for pandoc
329
+ :snippets, # Array of strings to determine snippet filenames
330
+ :upstream_tracefiles, # Array of strings to determine upstream tracefile names
331
+ :downstream_tracefile, # String to save downstram filename
332
+ :reqtracefile_base, # string to determine the requirements tracing results
333
+ :frontmatter, # Array of string to determine input filenames of frontmatter
334
+ :rootdir, # String directory of the configuration file
335
+ :stylefiles, # Hash of stylefiles path to pandoc latex style file
336
+ :mdreaderoptions, # string to be appended to -f markdown specifier in pandoc
337
+ :mdwriteroptions # string to be appendod to -t markdown in pandoc
338
+
339
+
340
+ # constructor
341
+ def initialize
342
+ @mdreaderoptions = %w{
343
+ +fenced_code_blocks
344
+ +compact_definition_lists
345
+ -space_in_atx_header
346
+ }.join()
347
+
348
+ @mdwriteroptions = %w{
349
+ -backtick_code_blocks
350
+ +fenced_code_blocks
351
+ +compact_definition_lists
352
+ +space_in_atx_header
353
+ +yaml_metadata_block
354
+ }.join()
355
+ end
356
+
357
+ # @param [String] configFileName name of the configfile (without .yaml)
358
+ # @param [Symbol] configSelect Default configuration. If not specified
359
+ # the very first entry in the config file
360
+ # will apply.
361
+ # TODO: not yet implemented.
362
+ # @return [ProoConfig] instance
363
+ def load_from_file(configFileName, configSelect=nil)
364
+ begin
365
+ config = YAML.load(File.new(configFileName))
366
+ rescue Exception => e
367
+ unless File.exist?(configFileName) then
368
+ $log.error "config file not found '#{configFileName}'"
369
+ else
370
+ $log.error "config file could not be loaded '#{configFileName}'"
371
+ if File.directory?(configFileName) then
372
+ # note that windows does not disinguish this.
373
+ $log.error "#{configFileName} is a directory"
374
+ end
375
+ $log.error "reason '#{e.message}'"
376
+ end
377
+ exit(false)
378
+ end
379
+
380
+ basePath = File.dirname(configFileName)
381
+
382
+ # this makes an absolute path based on the absolute path
383
+ # of the configuration file
384
+ expand_path =lambda do |lf|
385
+ File.expand_path("#{basePath}/#{lf}")
386
+ end
387
+
388
+
389
+ #activeConfigs=config.select{|x| [x[:name]] & ConfigSelet}
390
+
391
+ selectedConfig = config.first
392
+ #TODO: check the config file
393
+ #TODO: refactor the configuration processing
394
+ @input = selectedConfig[:input].map { |file| File.expand_path("#{basePath}/#{file}") }
395
+ @outdir = File.expand_path("#{basePath}/#{selectedConfig[:outdir]}")
396
+ @outname = selectedConfig[:outname]
397
+ @format = selectedConfig[:format]
398
+ @traceSortOrder = selectedConfig[:traceSortOrder]
399
+ @vars = selectedConfig[:vars] || {}
400
+ @editions = selectedConfig[:editions] || nil
401
+
402
+ @downstream_tracefile = selectedConfig[:downstream_tracefile] || nil
403
+
404
+ @reqtracefile_base = selectedConfig[:reqtracefile_base] #todo expand path
405
+
406
+ @upstream_tracefiles = selectedConfig[:upstream_tracefiles] || nil
407
+ @upstream_tracefiles = @upstream_tracefiles.map { |file| File.expand_path("#{basePath}/#{file}") } unless @upstream_tracefiles.nil?
408
+ @frontmatter = selectedConfig[:frontmatter] || nil
409
+ @frontmatter = selectedConfig[:frontmatter].map { |file| File.expand_path("#{basePath}/#{file}") } unless @frontmatter.nil?
410
+ @rootdir = basePath
411
+
412
+ @mdreaderoptions = selectedConfig[:mdreaderoptions].join() if selectedConfig[:mdreaderoptions]
413
+ @mdwriteroptions = selectedConfig[:mdwriteroptions].join() if selectedConfig[:mdwriteroptions]
414
+
415
+
416
+ stylefiles = selectedConfig[:stylefiles] || nil
417
+ if stylefiles.nil?
418
+ @stylefiles = {
419
+ :latex => expand_path.call("../ZSUPP_Styles/default.latex"),
420
+ :docx => expand_path.call("../ZSUPP_Styles/default.docx"),
421
+ :html => expand_path.call("../ZSUPP_Styles/default.css")
422
+ }
423
+ else
424
+ @stylefiles = stylefiles.map { |key, value| { key => expand_path.call(value) } }.reduce(:merge)
425
+ end
426
+
427
+ snippets = selectedConfig[:snippets]
428
+ if snippets.nil?
429
+ @snippets = nil
430
+ else
431
+ @snippets = snippets.map { |file| File.expand_path("#{basePath}/#{file}") }
432
+ end
433
+ end
434
+ end
435
+
436
+
437
+ #
438
+ # This class provides the major functionalites
439
+ #
440
+ # Note that it is called PandocBeautifier for historical reasons
441
+ #
442
+ # provides methods to Process a pandoc file
443
+ #
444
+
445
+ class PandocBeautifier
446
+
447
+ attr_accessor :log, :config
448
+
449
+ # the constructor
450
+ # @param [Logger] logger logger object to be applied.
451
+ # if none is specified, a default logger
452
+ # will be implemented
453
+ def initialize(logger = nil)
454
+
455
+ @markdown_output_switches = %w{
456
+ +backtick_code_blocks
457
+ -fenced_code_blocks
458
+ +compact_definition_lists
459
+ +space_in_atx_header
460
+ +yaml_metadata_block
461
+ }.join()
462
+
463
+ @markdown_input_switches = %w{
464
+ +smart
465
+ +backtick_code_blocks
466
+ +fenced_code_blocks
467
+ +compact_definition_lists
468
+ -space_in_atx_header
469
+ }.join()
470
+
471
+
472
+ @view_pattern = /~~ED((\s*(\w+))*)~~/
473
+ # @view_pattern = /<\?ED((\s*(\w+))*)\?>/
474
+ @tempdir = Dir.mktmpdir
475
+
476
+ @config = ProoConfig.new()
477
+
478
+ @log=logger || $logger || nil
479
+
480
+ if @log == nil
481
+ @log = Logger.new(STDOUT)
482
+ @log.level = Logger::INFO
483
+ @log.datetime_format = "%Y-%m-%d %H:%M:%S"
484
+ @log.formatter = proc do |severity, datetime, progname, msg|
485
+ "#{datetime}: #{msg}\n"
486
+ end
487
+
488
+ end
489
+ end
490
+
491
+
492
+ #
493
+
494
+ # This checks if an appropriate pandoc version can be
495
+ # started on the machine
496
+
497
+ #
498
+
499
+ # @return [boolean] true if an appropriate version is available
500
+ def check_pandoc_version
501
+ required_version_string="2.0.5"
502
+ begin
503
+ pandoc_version=`#{PANDOC_EXE} -v`.split("\n").first.split(" ")[1]
504
+ if pandoc_version < required_version_string then
505
+ @log.error "found pandoc #{pandoc_version} need #{required_version_string}"
506
+ result = false
507
+ else
508
+ result = true
509
+ end
510
+ rescue Exception => e
511
+ @log.error("could not run pandoc: #{e.message}")
512
+ result=false
513
+ end
514
+ result
515
+ end
516
+
517
+ # perform the beautify
518
+ # * process the file with pandoc
519
+ # * revoke some quotes introduced by pandoc
520
+ # @param [String] file the name of the file to be beautified
521
+ def beautify(file)
522
+
523
+ @log.debug(" Cleaning: \"#{file}\"")
524
+
525
+ docfile = File.new(file)
526
+ olddoc = docfile.readlines.join
527
+ docfile.close
528
+
529
+ # process the file in pandoc
530
+ cmd = "#{PANDOC_EXE} --standalone #{file.esc} -f markdown#{@markdown_input_switches} -t markdown#{@markdown_output_switches} --atx-headers --id-prefix=#{File.basename(file).esc}_ "
531
+
532
+ newdoc = `#{cmd}`
533
+ @log.debug "beautify #{file.esc}: #{$?}"
534
+ @log.debug(" finished: \"#{file}\"")
535
+
536
+ # tweak the quoting
537
+ if $?.success? then
538
+ # (RS_Mdc)
539
+ # TODO: fix Table width toggles sometimes
540
+ if (not olddoc == newdoc) then ##only touch the file if it is really changed
541
+ File.open(file, "w") { |f| f.puts(newdoc) }
542
+ File.open(file+".bak", "w") { |f| f.puts(olddoc) } # (RS_Mdc_) # remove this if needed
543
+ @log.debug(" cleaned: \"#{file}\"")
544
+ else
545
+ @log.debug("was clean: \"#{file}\"")
546
+ end
547
+ #TODO: error handling here
548
+ else
549
+ @log.error("error calling pandoc - please watch the screen output")
550
+ end
551
+ end
552
+
553
+
554
+ # this replaces the text snippets in files
555
+ def replace_snippets_in_file(infile, snippets)
556
+ input_data = File.open(infile) { |f| f.readlines.join }
557
+ output_data=input_data.clone
558
+
559
+ @log.debug("replacing snippets in #{infile}")
560
+
561
+ replace_snippets_in_text(output_data, snippets)
562
+
563
+ if (not input_data == output_data)
564
+ File.open(infile, "w") { |f| f.puts output_data }
565
+ end
566
+ end
567
+
568
+ # this replaces the snippets in a text
569
+ def replace_snippets_in_text(text, snippets)
570
+ changed=false
571
+ text.gsub!(SNIPPET_PATTERN) { |m|
572
+ replacetext_raw=snippets[$2.to_sym]
573
+
574
+ if replacetext_raw
575
+ changed=true
576
+ unless $1.nil? then
577
+ leading_whitespace=$1.split("\n", 100)
578
+ leading_lines =leading_whitespace[0..-1].join("\n")
579
+ leading_spaces =leading_whitespace.last || ""
580
+ replacetext =leading_lines+replacetext_raw.gsub("\n", "\n#{leading_spaces}")
581
+ end
582
+ @log.debug("replaced snippet #{$2} with #{replacetext}")
583
+ else
584
+ replacetext=m
585
+ @log.warn("Snippet not found: #{$2}")
586
+ end
587
+ replacetext
588
+ }
589
+ #recursively process nested snippets
590
+ #todo: this approach might rais undefined snippets twice if there are defined and undefined ones
591
+ replace_snippets_in_text(text, snippets) if changed==true
592
+ end
593
+
594
+
595
+ #
596
+ # Ths determines the view filter
597
+ #
598
+ # @param [String] line - the current input line
599
+ # @param [String] view - the currently selected view
600
+ #
601
+ # @return true/false if a view-command is found, else nil
602
+ def get_filter_command(line, view)
603
+ r = line.match(@view_pattern)
604
+
605
+ if not r.nil?
606
+ found = r[1].split(" ")
607
+ result = (found & [view, "all"].flatten).any?
608
+ else
609
+ result = nil
610
+ end
611
+
612
+ result
613
+ end
614
+
615
+ #
616
+ # This filters the document according to the target audience
617
+ #
618
+ # @param [String] inputfile name of inputfile
619
+ # @param [String] outputfile name of outputfile
620
+ # @param [String] view - name of intended view
621
+
622
+ def filter_document_variant(inputfile, outputfile, view)
623
+
624
+ input_data = File.open(inputfile) { |f| f.readlines }
625
+
626
+ output_data = Array.new
627
+ is_active = true
628
+ input_data.each { |l|
629
+ switch=self.get_filter_command(l, view)
630
+ l.gsub!(@view_pattern, "")
631
+ is_active = switch unless switch.nil?
632
+ @log.debug "select edtiion #{view}: #{is_active}: #{l.strip}"
633
+
634
+ output_data << l if is_active
635
+ }
636
+
637
+ File.open(outputfile, "w") { |f| f.puts output_data.join }
638
+ end
639
+
640
+ #
641
+ # This filters the document according to the target audience
642
+ #
643
+ # @param [String] inputfile name of inputfile
644
+ # @param [String] outputfile name of outputfile
645
+ # @param [String] view - name of intended view
646
+
647
+ def process_debug_info(inputfile, outputfile, view)
648
+
649
+ input_data = File.open(inputfile) { |f| f.readlines }
650
+
651
+ output_data = Array.new
652
+
653
+ input_data.each { |l|
654
+ l.gsub!(@view_pattern) { |p|
655
+ if $1.strip == "all" then
656
+ color="black"
657
+ else
658
+ color="red"
659
+ end
660
+
661
+ "\\color{#{color}}\\rule{2cm}{0.5mm}\\newline\\marginpar{#{$1.strip}}"
662
+
663
+ }
664
+
665
+ l.gsub!(/todo:|TODO:/) { |p| "#{p}\\marginpar{TODO}" }
666
+
667
+ output_data << l
668
+ }
669
+
670
+ File.open(outputfile, "w") { |f| f.puts output_data.join }
671
+ end
672
+
673
+
674
+ # This compiles the input documents to one single file
675
+ # it also beautifies the input files
676
+ #
677
+ # @param [Array of String] input - the input files to be processed in the given sequence
678
+ # @param [String] output - the the name of the output file
679
+ def collect_document(input, output)
680
+ inputs =input.map { |xx| xx.esc.to_osPath }.join(" ") # qoute cond combine the inputs
681
+ inputname=File.basename(input.first)
682
+
683
+ #now combine the input files
684
+ @log.debug("combining the input files #{inputname} et al")
685
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} --standalone -t markdown#{@markdown_output_switches} -o #{output} --ascii #{inputs}" # note that inputs is already quoted
686
+ system(cmd)
687
+ if $?.success? then
688
+ PandocBeautifier.new().beautify(output)
689
+ end
690
+ end
691
+
692
+ #
693
+ # This loads snipptes from xlsx file
694
+ # @param [String] file name of the xlsx file
695
+ # @return [Hash] a hash with the snippetes
696
+ #
697
+ def load_snippets_from_xlsx(file)
698
+ temp_filename = "#{@tempdir}/snippett.xlsx"
699
+ FileUtils::copy(file, temp_filename)
700
+ wb =RubyXL::Parser.parse(temp_filename)
701
+ result={}
702
+ wb.first.each { |row|
703
+ key, the_value = row
704
+ unless key.nil?
705
+ unless the_value.nil?
706
+ result[key.value.to_sym] = resolve_xml_entities(the_value.value) rescue ""
707
+ end
708
+ end
709
+ }
710
+ result
711
+ end
712
+
713
+ #
714
+ # this resolves xml entities in Text (lt, gt, amp)
715
+ # @param [String] text with entities
716
+ # @return [String] text with replaced entities
717
+ def resolve_xml_entities(text)
718
+ result=text
719
+ result.gsub!("&lt;", "<")
720
+ result.gsub!("&gt;", ">")
721
+ result.gsub!("&amp;", "&")
722
+ result
723
+ end
724
+
725
+ #
726
+ # This generates the final document
727
+ #
728
+ # It actually does this in two steps:
729
+ #
730
+ # 1. process front matter to laTeX
731
+ # 2. process documents
732
+ #
733
+ # @param [Array of String] input the input files to be processed in the given sequence
734
+ # @param [String] outdir the output directory
735
+ # @param [String] outname the base name of the output file. It is a basename in case the
736
+ # output format requires multiple files
737
+ # @param [Array of String] format list of formats which shall be generated.
738
+ # supported formats: "pdf", "latex", "html", "docx", "rtf", txt
739
+ # @param [Hash] vars - the variables passed to pandoc
740
+ # @param [Hash] editions - the editions to process; default nil - no edition processing
741
+ # @param [Array of String] snippetfiles the list of files containing snippets
742
+ # @param [String] frontmatter file path to frontmatter the file to processed as frontmatter
743
+ # @param [ProoConfig] config - the configuration file to be used
744
+ def generateDocument(input, outdir, outname, format, vars, editions=nil, snippetfiles=nil, frontmatter=nil, config=nil)
745
+
746
+ # combine the input files
747
+
748
+ temp_filename = "#{@tempdir}/x.md".to_osPath
749
+ temp_frontmatter = "#{@tempdir}/xfrontmatter.md".to_osPath unless frontmatter.nil?
750
+ collect_document(input, temp_filename)
751
+ collect_document(frontmatter, temp_frontmatter) unless frontmatter.nil?
752
+
753
+ # process the snippets
754
+
755
+ if not snippetfiles.nil?
756
+ snippets={}
757
+ snippetfiles.each { |f|
758
+ if File.exists?(f)
759
+ type=File.extname(f)
760
+ case type
761
+ when ".yaml"
762
+ x=YAML.load(File.new(f))
763
+ when ".xlsx"
764
+ x=load_snippets_from_xlsx(f)
765
+ else
766
+ @log.error("Unsupported File format for snipptets: #{type}")
767
+ x={}
768
+ end
769
+ snippets.merge!(x)
770
+ else
771
+ @log.error("Snippet file not found: #{f}")
772
+ end
773
+ }
774
+
775
+ replace_snippets_in_file(temp_filename, snippets)
776
+ end
777
+
778
+ vars_frontmatter =vars.clone
779
+ vars_frontmatter[:usetoc] = "nousetoc"
780
+
781
+
782
+ if editions.nil?
783
+ # there are no editions
784
+ unless frontmatter.nil? then
785
+ render_document(temp_frontmatter, tempdir, temp_frontmatter, ["frontmatter"], vars_frontmatter)
786
+ vars[:frontmatter] = "#{tempdir}/#{temp_frontmatter}.latex"
787
+ end
788
+ render_document(temp_filename, outdir, outname, format, vars, config)
789
+ else
790
+ # process the editions
791
+ editions.each { |edition_name, properties|
792
+ edition_out_filename = "#{outname}_#{properties[:filepart]}"
793
+ edition_temp_frontmatter = "#{@tempdir}/#{edition_out_filename}_frontmatter.md" unless frontmatter.nil?
794
+ edition_temp_filename = "#{@tempdir}/#{edition_out_filename}.md"
795
+ vars[:title] = properties[:title]
796
+
797
+ editionformats = properties[:format] || format
798
+
799
+ if properties[:debug]
800
+ process_debug_info(temp_frontmatter, edition_temp_frontmatter, edition_name.to_s) unless frontmatter.nil?
801
+ process_debug_info(temp_filename, edition_temp_filename, edition_name.to_s)
802
+ lvars =vars.clone
803
+ lvars[:linenumbers] = "true"
804
+ unless frontmatter.nil? # frontmatter
805
+ lvars[:usetoc] = "nousetoc"
806
+ render_document(edition_temp_frontmatter, @tempdir, "xfrontmatter", ["frontmatter"], lvars)
807
+ lvars[:usetoc] = vars[:usetoc] || "usetoc"
808
+ lvars[:frontmatter] = "#{@tempdir}/xfrontmatter.latex"
809
+ end
810
+ render_document(edition_temp_filename, outdir, edition_out_filename, ["pdf", "latex"], lvars, config)
811
+ else
812
+ unless frontmatter.nil? # frontmatter
813
+ filter_document_variant(temp_frontmatter, edition_temp_frontmatter, edition_name.to_s)
814
+ render_document(edition_temp_frontmatter, @tempdir, "xfrontmatter", ["frontmatter"], vars_frontmatter)
815
+ vars[:frontmatter]="#{@tempdir}/xfrontmatter.latex"
816
+ end
817
+
818
+ filter_document_variant(temp_filename, edition_temp_filename, edition_name.to_s)
819
+ render_document(edition_temp_filename, outdir, edition_out_filename, editionformats, vars, config)
820
+ end
821
+ }
822
+ end
823
+ end
824
+
825
+ #
826
+
827
+ # render a single file
828
+ # @param input [String] path to the inputfile
829
+ # @param outdir [String] path to the output directory
830
+ # @param format [Array of String] formats
831
+ # @return [nil] no useful return value
832
+ def render_single_document(input, outdir, format)
833
+ outname=File.basename(input, ".*")
834
+ render_document(input, outdir, outname, format, { :geometry => "a4paper" })
835
+ end
836
+
837
+ #
838
+ # This renders the final document
839
+ # @param [String] input the input file
840
+ # @param [String] outdir the output directory
841
+ # @param [String] outname the base name of the output file. It is a basename in case the
842
+ # output format requires multiple files
843
+ # @param [Array of String] format list of formats which shall be generated.
844
+ # supported formats: "pdf", "latex", "html", "docx", "rtf", txt
845
+ # @param [Hash] vars - the variables passed to pandoc
846
+
847
+ # @param config [ProoConfig] the entire config object (for future extensions)
848
+ # @return nil
849
+
850
+ def render_document(input, outdir, outname, format, vars, config=nil)
851
+
852
+ #TODO: Clarify the following
853
+ # on Windows, Tempdir contains a drive letter. But drive letter
854
+ # seems not to work in pandoc -> pdf if the path separator ist forward
855
+ # slash. There are two options to overcome this
856
+ #
857
+ # 1. set tempdir such that it does not contain a drive letter
858
+ # 2. use Dir.mktempdir but ensure that all provided file names
859
+ # use the platform specific SEPARATOR
860
+ #
861
+ # for whatever Reason, I decided for 2.
862
+
863
+ tempfile = input
864
+ tempfilePdf = "#{@tempdir}/x.TeX.md".to_osPath
865
+ tempfileHtml = "#{@tempdir}/x.html.md".to_osPath
866
+ outfile = "#{outdir}/#{outname}".to_osPath
867
+ outfilePdf = "#{outfile}.pdf"
868
+ outfileDocx = "#{outfile}.docx"
869
+ outfileHtml = "#{outfile}.html"
870
+ outfileRtf = "#{outfile}.rtf"
871
+ outfileLatex = "#{outfile}.latex"
872
+ outfileText = "#{outfile}.txt"
873
+ outfileSlide = "#{outfile}.slide.html"
874
+
875
+
876
+ ## format handle
877
+
878
+ # todo: use this information ...
879
+
880
+ format_config = {
881
+ 'pdf' => {
882
+ tempfile: :pdf,
883
+ outfile: "#{outfile}.pdf"
884
+ },
885
+ 'html' => {
886
+ tempfile: :html,
887
+ outfile: "#{outfile}.html"
888
+ },
889
+ 'docx' => {
890
+ tempfile: :html,
891
+ outfile: "#{outfile}.docx"
892
+ },
893
+ 'rtf' => {
894
+ tempfile: :html,
895
+ outfile: "#{outfile}.rtf"
896
+ },
897
+ 'latex' => {
898
+ tempfile: :pdf,
899
+ outfile: "#{outfile}.latex"
900
+ },
901
+ 'text' => {
902
+ tempfile: :html,
903
+ outfile: "#{outfile}.text"
904
+ },
905
+ 'dzslides' => {
906
+ tempfile: :html,
907
+ outfile: "#{outfile}.slide.html"
908
+ },
909
+
910
+ :beamer => {
911
+ tempfile: :pdf,
912
+ outfile: "#{outfile}.beamer.pdf"
913
+ },
914
+
915
+ 'markdown' => {
916
+ tempfile: :html,
917
+ outfile: "#{outfile}.slide.html"
918
+ }
919
+ }
920
+
921
+ tempfile_config = {
922
+ pdf: "#{@tempdir}/x.TeX.md".to_osPath,
923
+ html: "#{@tempdir}/x.html.md".to_osPath
924
+ }
925
+
926
+
927
+ if vars.has_key? :frontmatter
928
+ latexTitleInclude = "--include-before-body=#{vars[:frontmatter].esc}"
929
+ else
930
+ latexTitleInclude
931
+ end
932
+
933
+ #todo: make config required, so it can be reduced to the else part
934
+ if config.nil? then
935
+ latexStyleFile = File.dirname(File.expand_path(__FILE__))+"/../../resources/default.wortsammler.latex"
936
+ latexStyleFile = File.expand_path(latexStyleFile).to_osPath
937
+ css_style_file = File.dirname(File.expand_path(__FILE__))+"/../../resources/default.wortsammler.css"
938
+ css_style_file = File.expand_path(css_style_file).to_osPath
939
+ else
940
+ latexStyleFile = config.stylefiles[:latex]
941
+ css_style_file = config.stylefiles[:css]
942
+ end
943
+
944
+
945
+ toc = "--toc"
946
+ toc = "" if vars[:usetoc]=="nousetoc"
947
+
948
+ if vars[:documentclass]=="book"
949
+ option_chapters = "--chapters"
950
+ else
951
+ option_chapter = ""
952
+ end
953
+
954
+ begin
955
+ vars_string=vars.map.map { |key, value| "-V #{key}=#{value.esc}" }.join(" ")
956
+ rescue
957
+ #todo require 'pry'; binding.pry
958
+ end
959
+
960
+ @log.info("rendering #{outname} as [#{format.join(', ')}]")
961
+
962
+ supported_formats=["pdf", "latex", "frontmatter", "docx", "html", "txt", "rtf", "slidy", "md", "beamer"]
963
+ wrong_format =format - supported_formats
964
+ wrong_format.each { |f| @log.error("format not supported: #{f}") }
965
+
966
+ begin
967
+
968
+ if format.include?("frontmatter") then
969
+
970
+ ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
971
+
972
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfilePdf.esc} --pdf-engine xelatex #{vars_string} --ascii -t latex+smart -o #{outfileLatex.esc}"
973
+ `#{cmd}`
974
+ end
975
+
976
+
977
+ if (format.include?("pdf") | format.include?("latex")) then
978
+ @log.debug("creating #{outfileLatex}")
979
+ ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
980
+
981
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfilePdf.esc} #{toc} --standalone #{option_chapters} --pdf-engine xelatex --number-sections #{vars_string}" +
982
+ " --template #{latexStyleFile.esc} --ascii -t latex+smart -o #{outfileLatex.esc} #{latexTitleInclude}"
983
+ `#{cmd}`
984
+
985
+ end
986
+
987
+
988
+
989
+ if format.include?("pdf") then
990
+ @log.debug("creating #{outfilePdf}")
991
+ ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfilePdf)
992
+ #cmd="#{PANDOC_EXE} -S #{tempfilePdf.esc} #{toc} --standalone #{option_chapters} --latex-engine xelatex --number-sections #{vars_string}" +
993
+ # " --template #{latexStyleFile.esc} --ascii -o #{outfilePdf.esc} #{latexTitleInclude}"
994
+ cmd ="#{LATEX_EXE} -halt-on-error -interaction nonstopmode -output-directory=#{outdir.esc} #{outfileLatex.esc}"
995
+ #cmdmkindex = "makeindex \"#{outfile.esc}.idx\""
996
+
997
+ latex=LatexHelper.new.set_latex_command(cmd).setlogger(@log)
998
+ latex.run(outfileLatex)
999
+
1000
+ messages=latex.log_analyze("#{outdir}/#{outname}.log")
1001
+
1002
+ removeables = ["toc", "aux", "bak", "idx", "ilg", "ind"]
1003
+ removeables << "log" unless messages > 0
1004
+
1005
+
1006
+ removeables << "latex" unless format.include?("latex")
1007
+ removeables = removeables.map { |e| "#{outdir}/#{outname}.#{e}" }.select { |f| File.exists?(f) }
1008
+ removeables.each { |e|
1009
+ @log.debug "removing file: #{e}"
1010
+ FileUtils.rm e
1011
+ }
1012
+ end
1013
+
1014
+ if format.include?("html") then
1015
+ #todo: handle css
1016
+ @log.debug("creating #{outfileHtml}")
1017
+
1018
+ ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
1019
+
1020
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
1021
+ " -t html+smart -o #{outfileHtml.esc}"
1022
+
1023
+ `#{cmd}`
1024
+ end
1025
+
1026
+ if format.include?("docx") then
1027
+ #todo: handle style file
1028
+ @log.debug("creating #{outfileDocx}")
1029
+
1030
+ ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
1031
+
1032
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} #{toc} --standalone --self-contained --ascii --number-sections #{vars_string}" +
1033
+ " -f docx+smart -o #{outfileDocx.esc}"
1034
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
1035
+ " -t docx+smart -o #{outfileDocx.esc}"
1036
+ `#{cmd}`
1037
+ end
1038
+
1039
+ if format.include?("rtf") then
1040
+ @log.debug("creating #{outfileRtf}")
1041
+ ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
1042
+
1043
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
1044
+ " -t rtf+smart -o #{outfileRtf.esc}"
1045
+ `#{cmd}`
1046
+ end
1047
+
1048
+ if format.include?("txt") then
1049
+ @log.debug("creating #{outfileText}")
1050
+
1051
+ ReferenceTweaker.new("pdf").prepareFile(tempfile, tempfileHtml)
1052
+
1053
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} --toc --standalone --self-contained --ascii --number-sections #{vars_string}" +
1054
+ " -t plain+smart -o #{outfileText.esc}"
1055
+ `#{cmd}`
1056
+ end
1057
+
1058
+ if format.include?("slidy") then
1059
+ @log.debug("creating #{outfileSlide}")
1060
+
1061
+ ReferenceTweaker.new("html").prepareFile(tempfile, tempfileHtml)
1062
+ #todo: handle stylefile
1063
+ cmd="#{PANDOC_EXE} -f markdown#{@markdown_input_switches} #{tempfileHtml.esc} --toc --standalone --self-contained #{vars_string}" +
1064
+ " --ascii -t s5+smart --slide-level 1 -o #{outfileSlide.esc}"
1065
+ `#{cmd}`
1066
+ end
1067
+
1068
+ if format.include?("beamer") then
1069
+ outfile = format_config[:beamer][:outfile]
1070
+ tempformat = format_config[:beamer][:tempfile]
1071
+ tempfile_out = tempfile_config[tempformat]
1072
+ @log.debug("creating #{outfile}")
1073
+ ReferenceTweaker.new(tempformat).prepareFile(tempfile, tempfile_out)
1074
+
1075
+ cmd = %Q{#{PANDOC_EXE} -t beamer #{tempfile_out.esc} -V theme:Warsaw -o #{outfile.esc}}
1076
+ `#{cmd}`
1077
+
1078
+ #messages=latex.log_analyze("#{outdir}/#{outname}.log")
1079
+ messages = 0
1080
+
1081
+ removeables = ["toc", "aux", "bak", "idx", "ilg", "ind"]
1082
+ removeables << "log" unless messages > 0
1083
+
1084
+
1085
+ removeables << "latex" unless format.include?("latex")
1086
+ removeables = removeables.map { |e| "#{outdir}/#{outname}.#{e}" }.select { |f| File.exists?(f) }
1087
+ removeables.each { |e|
1088
+ @log.debug "removing file: #{e}"
1089
+ FileUtils.rm e
1090
+ }
1091
+ end
1092
+
1093
+
1094
+ rescue Exception => e
1095
+ @log.error "failed to perform #{cmd}, \n#{e.message}"
1096
+ @log.error e.backtrace.join("\n")
1097
+ #TODO make a try catch block kere
1098
+ end
1099
+ nil
1100
+ end
1101
+
1102
+ end