wortsammler 1.0.3 → 2.0.0.dev1

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