deplate 0.7.3

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 (145) hide show
  1. data/AUTHORS.TXT +26 -0
  2. data/CHANGES.TXT +177 -0
  3. data/LICENSE.TXT +340 -0
  4. data/NEWS.TXT +29 -0
  5. data/README.TXT +86 -0
  6. data/TODO.TXT +202 -0
  7. data/VERSION.TXT +1 -0
  8. data/bin/deplate +3 -0
  9. data/bin/deplate.bat +2 -0
  10. data/etc/deplate.ini +361 -0
  11. data/lib/deplate.rb +31 -0
  12. data/lib/deplate/abstract-class.rb +30 -0
  13. data/lib/deplate/builtin.rb +11 -0
  14. data/lib/deplate/cache.rb +59 -0
  15. data/lib/deplate/commands.rb +693 -0
  16. data/lib/deplate/common.rb +335 -0
  17. data/lib/deplate/converter.rb +99 -0
  18. data/lib/deplate/core.rb +2705 -0
  19. data/lib/deplate/css/article.css +545 -0
  20. data/lib/deplate/css/deplate.css +699 -0
  21. data/lib/deplate/css/heading-navbar.css +29 -0
  22. data/lib/deplate/css/layout-deplate-print.css +540 -0
  23. data/lib/deplate/css/layout-deplate.css +764 -0
  24. data/lib/deplate/css/sans-serif.css +160 -0
  25. data/lib/deplate/css/serif-e.css +170 -0
  26. data/lib/deplate/css/serif-rel.css +121 -0
  27. data/lib/deplate/css/serif.css +190 -0
  28. data/lib/deplate/css/slides.css +11 -0
  29. data/lib/deplate/css/tabbar-left.css +91 -0
  30. data/lib/deplate/css/tabbar-right-ie.css +14 -0
  31. data/lib/deplate/css/tabbar-right.css +118 -0
  32. data/lib/deplate/css/tabbar-top.css +64 -0
  33. data/lib/deplate/css/tabbar.css +81 -0
  34. data/lib/deplate/css/text-sans-serif.css +154 -0
  35. data/lib/deplate/css/text-serif.css +175 -0
  36. data/lib/deplate/define.rb +439 -0
  37. data/lib/deplate/docbook.rb +738 -0
  38. data/lib/deplate/elements.rb +1355 -0
  39. data/lib/deplate/etc.rb +199 -0
  40. data/lib/deplate/external.rb +135 -0
  41. data/lib/deplate/fmt/dbk-article-4.1.2.rb +21 -0
  42. data/lib/deplate/fmt/dbk-article.rb +46 -0
  43. data/lib/deplate/fmt/dbk-book.rb +46 -0
  44. data/lib/deplate/fmt/dbk-ref.rb +105 -0
  45. data/lib/deplate/fmt/dbk-slides.rb +47 -0
  46. data/lib/deplate/fmt/dbk-snippet.rb +21 -0
  47. data/lib/deplate/fmt/html-snippet.rb +21 -0
  48. data/lib/deplate/fmt/html.rb +1696 -0
  49. data/lib/deplate/fmt/htmlsite.rb +419 -0
  50. data/lib/deplate/fmt/htmlslides.rb +21 -0
  51. data/lib/deplate/fmt/htmlwebsite.rb +70 -0
  52. data/lib/deplate/fmt/latex-snippet.rb +22 -0
  53. data/lib/deplate/fmt/latex.rb +1242 -0
  54. data/lib/deplate/fmt/php.rb +19 -0
  55. data/lib/deplate/fmt/phpsite.rb +19 -0
  56. data/lib/deplate/fmt/plain.rb +598 -0
  57. data/lib/deplate/fmt/template.rb +34 -0
  58. data/lib/deplate/fmt/xhtml10t.rb +41 -0
  59. data/lib/deplate/formatter-snippet.rb +17 -0
  60. data/lib/deplate/formatter.rb +1210 -0
  61. data/lib/deplate/input.rb +492 -0
  62. data/lib/deplate/input/deplate-headings.rb +48 -0
  63. data/lib/deplate/input/deplate-restricted.rb +70 -0
  64. data/lib/deplate/input/deplate.rb +28 -0
  65. data/lib/deplate/input/rdoc.rb +277 -0
  66. data/lib/deplate/input/template.rb +29 -0
  67. data/lib/deplate/lib/latex/highlight-extra.sty +15 -0
  68. data/lib/deplate/lib/latex/highlight-typical.sty +15 -0
  69. data/lib/deplate/lib/tabmenu.js +146 -0
  70. data/lib/deplate/locale/de.latin1 +708 -0
  71. data/lib/deplate/locale/ru.koi8-r +48 -0
  72. data/lib/deplate/locale/zh_cn.gb2312 +35 -0
  73. data/lib/deplate/macros.rb +639 -0
  74. data/lib/deplate/messages.rb +120 -0
  75. data/lib/deplate/metadata.rb +77 -0
  76. data/lib/deplate/metadata/marshal.rb +24 -0
  77. data/lib/deplate/metadata/xml.rb +42 -0
  78. data/lib/deplate/metadata/yaml.rb +26 -0
  79. data/lib/deplate/mod/anyword.rb +56 -0
  80. data/lib/deplate/mod/babelfish.rb +27 -0
  81. data/lib/deplate/mod/code-gvim.rb +52 -0
  82. data/lib/deplate/mod/code-highlight.rb +91 -0
  83. data/lib/deplate/mod/colored-log.rb +17 -0
  84. data/lib/deplate/mod/de.rb +19 -0
  85. data/lib/deplate/mod/en.rb +17 -0
  86. data/lib/deplate/mod/endnotes.rb +60 -0
  87. data/lib/deplate/mod/fr.rb +46 -0
  88. data/lib/deplate/mod/html-asciimath.rb +40 -0
  89. data/lib/deplate/mod/html-deplate-button.rb +15 -0
  90. data/lib/deplate/mod/html-headings-navbar.rb +39 -0
  91. data/lib/deplate/mod/html-obfuscate-email.rb +47 -0
  92. data/lib/deplate/mod/html-sidebar.rb +232 -0
  93. data/lib/deplate/mod/htmlslides-navbar-fh.rb +32 -0
  94. data/lib/deplate/mod/iconv.rb +35 -0
  95. data/lib/deplate/mod/imgurl.rb +30 -0
  96. data/lib/deplate/mod/inlatex-compound.rb +69 -0
  97. data/lib/deplate/mod/koma.rb +109 -0
  98. data/lib/deplate/mod/latex-emph-table-head.rb +38 -0
  99. data/lib/deplate/mod/latex-styles.rb +461 -0
  100. data/lib/deplate/mod/latex-verbatim-small.rb +29 -0
  101. data/lib/deplate/mod/makefile.rb +194 -0
  102. data/lib/deplate/mod/mark-external-urls.rb +38 -0
  103. data/lib/deplate/mod/markup-1-warn.rb +37 -0
  104. data/lib/deplate/mod/markup-1.rb +41 -0
  105. data/lib/deplate/mod/navbar-png.rb +33 -0
  106. data/lib/deplate/mod/noindent.rb +32 -0
  107. data/lib/deplate/mod/numpara.rb +40 -0
  108. data/lib/deplate/mod/particle-math.rb +34 -0
  109. data/lib/deplate/mod/php-extra.rb +44 -0
  110. data/lib/deplate/mod/pstoedit.rb +71 -0
  111. data/lib/deplate/mod/recode.rb +57 -0
  112. data/lib/deplate/mod/ru_koi8-r.rb +20 -0
  113. data/lib/deplate/mod/smiley.rb +50 -0
  114. data/lib/deplate/mod/soffice.rb +23 -0
  115. data/lib/deplate/mod/symbols-latin1.rb +58 -0
  116. data/lib/deplate/mod/symbols-od-utf-8.rb +16 -0
  117. data/lib/deplate/mod/symbols-plain.rb +58 -0
  118. data/lib/deplate/mod/symbols-sgml.rb +97 -0
  119. data/lib/deplate/mod/symbols-utf-8.rb +81 -0
  120. data/lib/deplate/mod/symbols-xml.rb +34 -0
  121. data/lib/deplate/mod/syntax-region-alt.rb +37 -0
  122. data/lib/deplate/mod/utf8.rb +49 -0
  123. data/lib/deplate/mod/validate-html.rb +35 -0
  124. data/lib/deplate/mod/xmlrpc.rb +233 -0
  125. data/lib/deplate/mod/zh-cn-autospace.rb +108 -0
  126. data/lib/deplate/mod/zh-cn.rb +59 -0
  127. data/lib/deplate/once-method.rb +44 -0
  128. data/lib/deplate/output.rb +249 -0
  129. data/lib/deplate/particles.rb +815 -0
  130. data/lib/deplate/regions.rb +1076 -0
  131. data/lib/deplate/structured.rb +763 -0
  132. data/lib/deplate/template.rb +430 -0
  133. data/lib/deplate/templates/html-doc.html +28 -0
  134. data/lib/deplate/templates/html-left-tabbar-js.html +37 -0
  135. data/lib/deplate/templates/html-left-tabbar.html +31 -0
  136. data/lib/deplate/templates/html-tabbar-right-table.html +43 -0
  137. data/lib/deplate/templates/html-tabbar-right.html +23 -0
  138. data/lib/deplate/templates/html-tabbar-top.html +43 -0
  139. data/lib/deplate/templates/html-tabbar.html +31 -0
  140. data/lib/deplate/wiki-markup.rb +117 -0
  141. data/lib/deplate/xml.rb +109 -0
  142. data/lib/deplate/zh-cn.rb +59 -0
  143. data/lib/ps2ppm.rb +239 -0
  144. data/man/man1/deplate.1 +692 -0
  145. metadata +210 -0
@@ -0,0 +1,335 @@
1
+ # common.rb -- The base class for deplate building blocks
2
+ # @Author: Thomas Link (samul AT web.de)
3
+ # @Website: http://deplate.sf.net/
4
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5
+ # @Created: 02-Aug-2004.
6
+ # @Last Change: 01-Nov-2005.
7
+ # @Revision: 0.636
8
+ #
9
+ # = Description:
10
+ # Misc classes
11
+
12
+ # require 'forwardable'
13
+
14
+ module Deplate::CommonGround
15
+ #### pointer to the master deplater
16
+ attr_reader :deplate
17
+ attr_writer :container
18
+ attr_accessor :args
19
+ attr_accessor :styles
20
+ attr_writer :doc_type
21
+ attr_accessor :doc_slot
22
+ attr_reader :label_mode
23
+ attr_accessor :keep_whitespace
24
+ attr_accessor :match
25
+ attr_accessor :level_as_string
26
+ attr_accessor :level_as_list
27
+ attr_accessor :level
28
+
29
+ #### the element's parsed value
30
+ attr_accessor :elt
31
+ attr_accessor :text
32
+ attr_accessor :source
33
+ attr_accessor :prologue
34
+ attr_accessor :epilogue
35
+ # attr_accessor :expected
36
+
37
+ def initialize(deplate, args={})
38
+ @deplate = deplate
39
+ @level_as_string = args[:level_as_string] || deplate.get_current_heading
40
+ @level_as_list = args[:level_as_string] || deplate.current_heading.dup
41
+ @elt = args[:elt] || nil
42
+ @text = args[:text] || nil
43
+ @styles = args[:styles] || []
44
+ @source = args[:source] || nil
45
+ @expected = args[:expected] || nil
46
+ # @args = {:class => self.class, :self => self, :deplate => deplate}
47
+ @args = args[:args] || {:self => self, :deplate => deplate}
48
+ @container = args[:container] || nil
49
+ @prologue = args[:prologue] || []
50
+ @epilogue = args[:epilogue] || []
51
+ @can_be_labelled = args[:can_be_labelled] || true
52
+ @keep_whitespace = args[:keep_whitespace] || false
53
+ @doc_slot = args[:doc_slot] || nil
54
+ @doc_type = args[:doc_type] || nil
55
+ @label_mode = args[:label_mode] || nil
56
+ end
57
+
58
+ def container
59
+ if @container
60
+ return @container.container || @container
61
+ else
62
+ return nil
63
+ end
64
+ end
65
+
66
+ def update_args(opts={})
67
+ update_styles
68
+ update_id(opts)
69
+ end
70
+
71
+ def update_styles(styles=nil)
72
+ styles = @args["style"]
73
+ if styles
74
+ @styles += styles.split(/[ ,;]/)
75
+ @styles.uniq!
76
+ end
77
+ end
78
+
79
+ def update_id(opts={})
80
+ my_id = @args['id']
81
+ if my_id != @args[:id]
82
+ aid = @args[:id]
83
+ @label << aid if aid
84
+ @args[:id] = my_id
85
+ if block_given?
86
+ yield
87
+ end
88
+ end
89
+ end
90
+
91
+ def get_explicit_id
92
+ @args[:id] || @args['id']
93
+ end
94
+
95
+ def get_id
96
+ # get_explicit_id || @label[0]
97
+ get_explicit_id
98
+ end
99
+
100
+ def styles_as_string(sep=" ")
101
+ if @styles.empty?
102
+ nil
103
+ else
104
+ @styles.join(sep)
105
+ end
106
+ end
107
+
108
+ def can_be_labelled
109
+ @can_be_labelled && doc_type == :body
110
+ end
111
+
112
+ def log(text, condition=nil)
113
+ @deplate.log(text, condition, @source)
114
+ end
115
+
116
+ def match_expected(expected=nil, invoker=self)
117
+ if kind_of?(Deplate::Regions::Inlatex)
118
+ return
119
+ elsif expected
120
+ if defined?(@prototype)
121
+ cc = element_or_particle(@prototype)
122
+ cl = @prototype.class
123
+ else
124
+ cc = expected
125
+ cl = self.class
126
+ end
127
+ unless kind_of?(expected) or expected != cc
128
+ invoker.log(["Expected something of a different class", expected, cl, invoker.class], :error)
129
+ end
130
+ # else
131
+ # invoker.log("Neither element nor particle", :anyway)
132
+ end
133
+ end
134
+
135
+ def element_or_particle(obj)
136
+ if obj.kind_of?(Deplate::Element::Clip)
137
+ obj = obj.elt.first
138
+ end
139
+ for c in [Deplate::Element, Deplate::Particle]
140
+ if obj.kind_of?(c)
141
+ return c
142
+ end
143
+ end
144
+ log(["Neither block element nor inline text particle", self.class], :error)
145
+ return nil
146
+ end
147
+
148
+ def doc_slot(default=:body, overwrite=false)
149
+ if (overwrite and default) or !@doc_slot
150
+ if @args["slot"]
151
+ @doc_slot = @args["slot"]
152
+ else
153
+ @doc_slot = default
154
+ end
155
+ end
156
+ return @doc_slot
157
+ end
158
+
159
+ def doc_type(default=:body, overwrite=false)
160
+ if (overwrite and default) or !@doc_type
161
+ @doc_type = @args["type"] || default
162
+ end
163
+ return @doc_type
164
+ end
165
+
166
+ def output(*body)
167
+ @deplate.formatter.output(self, *body)
168
+ end
169
+
170
+ def output_preferably_at(type, slot, *body)
171
+ @deplate.formatter.output_at(doc_type(type), doc_slot(slot), *body)
172
+ end
173
+
174
+ def output_at(type, slot, *body)
175
+ @deplate.formatter.output_at(type, slot, *body)
176
+ end
177
+
178
+ def warn_unpexpected(expected, got)
179
+ msg = "Expected %s but got %s" % [expected, got]
180
+ if @invoker
181
+ @invoker.log(msg, :error)
182
+ else
183
+ Deplate::Core.log(msg, :error, @source)
184
+ end
185
+ end
186
+
187
+ def format_element(agent, *args)
188
+ return @deplate.formatter.format_element(agent, *args)
189
+ end
190
+
191
+ def plain_caption?
192
+ @deplate.variables["headings"] == "plain" or @args["plain"]
193
+ end
194
+
195
+ def output_file_name(args={})
196
+ obj = args[:object] || self
197
+ label = args[:label]
198
+ relative = args[:relative]
199
+ basename = args[:basename]
200
+ level_as_string = args[:level_as_string]
201
+ if level_as_string
202
+ rv = @deplate.file_name_by_level(level_as_string)
203
+ elsif label
204
+ rv = @deplate.get_filename_for_label(self, label) || ""
205
+ else
206
+ if obj.kind_of?(Deplate::BaseParticle) or obj.kind_of?(Deplate::BaseParticle)
207
+ obj = obj.container
208
+ end
209
+ if obj.kind_of?(Deplate::BaseElement)
210
+ th = obj.top_heading
211
+ rv = th.destination
212
+ end
213
+ end
214
+ if rv
215
+ if basename
216
+ return File.basename(rv)
217
+ elsif relative == ""
218
+ return File.basename(rv)
219
+ elsif relative
220
+ dir = File.dirname(relative.output_file_name)
221
+ return @deplate.relative_path(rv, dir)
222
+ else
223
+ return rv
224
+ end
225
+ else
226
+ log(["Internal error in #output_file_name", obj.class.name], :error)
227
+ raise Exception
228
+ end
229
+ end
230
+
231
+ def labels_sans_id
232
+ id = get_id
233
+ lbl = @label.dup
234
+ lbl.delete(id)
235
+ lbl
236
+ end
237
+
238
+ def output_location(args={})
239
+ location = [output_file_name(args)]
240
+ id = get_id
241
+ location << id if id
242
+ location.join("#")
243
+ end
244
+ end
245
+
246
+
247
+ module Deplate::CommonParticle
248
+ attr_accessor :current_heading
249
+ attr_reader :context
250
+
251
+ def top_heading
252
+ @container.top_heading
253
+ end
254
+
255
+ def destination
256
+ @container.destination
257
+ end
258
+
259
+ def plain_text(*args)
260
+ @deplate.formatter.plain_text(*args)
261
+ end
262
+
263
+ def format_particle(agent, *args)
264
+ return @deplate.formatter.format_particle(agent, *args)
265
+ end
266
+
267
+ def format_as_string(object)
268
+ # <+TBD+>: Doesn't work because the particles don't give info
269
+ # about their formatting method
270
+ # @deplate.formatter.format_particle_as_string(object)
271
+ end
272
+ end
273
+
274
+
275
+ module Deplate::CommonElement
276
+ attr_accessor :top_heading
277
+ #### accumulated lines
278
+ attr_accessor :accum
279
+
280
+ def format_as_string(object)
281
+ klass = object.class
282
+ fm = klass.formatter
283
+ if fm
284
+ return @deplate.formatter.format_element_as_string(object, fm)
285
+ else
286
+ log(["Don't know how to format an object of this class", klass], :error)
287
+ end
288
+ end
289
+
290
+ def add_metadata(source, metadata)
291
+ if @deplate.options.metadata_model
292
+ @registered_metadata << @deplate.get_metadata(source, metadata)
293
+ end
294
+ end
295
+
296
+ def is_explicit?
297
+ false
298
+ end
299
+
300
+ def update_id(opts={})
301
+ super {@deplate.add_label(self, get_explicit_id, @level_as_string, :anyway => true)}
302
+ end
303
+ end
304
+
305
+
306
+ class Deplate::Base
307
+ include Deplate::CommonGround
308
+
309
+ class << self
310
+ attr_reader :formatter, :formatter2
311
+
312
+ def set_rx(rx)
313
+ @rx = rx
314
+ end
315
+
316
+ def set_formatter(formatter, alt=false)
317
+ if alt
318
+ @formatter2 = formatter
319
+ else
320
+ @formatter = formatter
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+
327
+ class Deplate::BaseElement < Deplate::Base
328
+ include Deplate::CommonElement
329
+ end
330
+
331
+
332
+ class Deplate::BaseParticle < Deplate::Base
333
+ include Deplate::CommonParticle
334
+ end
335
+
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env ruby
2
+ # converter.rb
3
+ # @Author: Thomas Link (samul AT web.de)
4
+ # @Website: http://deplate.sf.net/
5
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
6
+ # @Created: 14-Okt-2004.
7
+ # @Last Change: 23-Okt-2005.
8
+ # @Revision: 0.125
9
+ #
10
+ # = Description
11
+ # = Usage
12
+ #
13
+ # require "deplate/converter"
14
+ # t = <<EOF
15
+ # * Introduction
16
+ #
17
+ # ''deplate'' is a tool for converting wiki-like markup to latex, html, or
18
+ # "html-slides".
19
+ # EOF
20
+ # to_html = Deplate::Converter.new
21
+ # puts to_html.convert_string(t)
22
+ #
23
+ # = TODO
24
+ # = CHANGES
25
+
26
+ require "deplate"
27
+
28
+ class Deplate::Converter
29
+ attr_reader :options, :deplate
30
+
31
+ @setup_done = false
32
+
33
+ class << self
34
+ attr_reader :setup_done
35
+
36
+ def setup
37
+ unless @setup_done
38
+ Deplate::Core.collect_standard
39
+ @setup_done = true
40
+ end
41
+ end
42
+ end
43
+
44
+ def initialize(formatter="html", args={})
45
+ Deplate::Converter.setup
46
+ @master = args[:master]
47
+ @options = args[:options]
48
+ @options ||= @master.options.dup if @master
49
+ @options ||= Deplate::Core.deplate_options
50
+ @options.fmt = formatter
51
+ @options.modules ||= args[:modules] || []
52
+ Deplate::Core.require_standard(@options)
53
+ if block_given?
54
+ yield(self)
55
+ end
56
+ @deplate = Deplate::Core.new(formatter, :options => @options)
57
+ vars = args[:variables]
58
+ vars ||= @master.variables.dup if @master
59
+ @deplate.instance_eval do
60
+ @variables = vars if vars
61
+ end
62
+ @formatter_method = "to_%s" % formatter.gsub(/[^a-zA-Z_]/, "_")
63
+ end
64
+
65
+ def convert_string(string)
66
+ @deplate.send(@formatter_method, string)
67
+ end
68
+
69
+ def convert_file(filename)
70
+ @deplate.send(@formatter_method, nil, filename)
71
+ end
72
+ end
73
+
74
+ # if __FILE__ == $0
75
+ # t = <<EOF
76
+ # * Introduction
77
+ #
78
+ # ''deplate'' is a tool for converting wiki-like markup to latex, html, or
79
+ # "html-slides".
80
+ # EOF
81
+ # to_html = Deplate::Converter.new
82
+ # to_latex = Deplate::Converter.new("latex")
83
+ # to_html_i = Deplate::Converter.new do |cvt|
84
+ # cvt.options.included = true
85
+ # end
86
+ # # to_dbk = Deplate::Converter.new("dbk-article")
87
+ #
88
+ # puts "----------------------------------------------------------------"
89
+ # puts to_html.convert_string(t)
90
+ # puts "----------------------------------------------------------------"
91
+ # puts to_html_i.convert_string(t)
92
+ # puts "----------------------------------------------------------------"
93
+ # puts to_latex.convert_string(t)
94
+ # puts "----------------------------------------------------------------"
95
+ # # puts to_dbk.convert_string(t)
96
+ # puts "----------------------------------------------------------------"
97
+ #
98
+ # end
99
+ #
@@ -0,0 +1,2705 @@
1
+ # core.rb -- Convert wiki-like plain text pseudo markup to something else
2
+ # @Author: Thomas Link (samul AT web.de)
3
+ # @Website: http://deplate.sf.net/
4
+ # @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5
+ # @Created: 24-Feb-2004.
6
+ # @Last Change: 02-Nov-2005.
7
+
8
+ require 'uri'
9
+ require 'optparse'
10
+ require 'ostruct'
11
+ require 'rbconfig'
12
+ require 'ftools'
13
+ require 'forwardable'
14
+ require 'pathname'
15
+
16
+ module Deplate; end
17
+
18
+ # Deplate::Core is responsible for managing the conversion process.
19
+ # Deplate::Core.deplate parses command line arguments, creates a
20
+ # preconfigured instance of Deplate::Core and initiates the conversion
21
+ # process.
22
+ #
23
+ # If you want to use deplate as a library, you should probably use the
24
+ # Deplate::Converter convenience class.
25
+
26
+ class Deplate::Core
27
+ extend Forwardable
28
+
29
+ Version = '0.7.3'
30
+ MicroRev = '2132'
31
+ if ENV['HOME']
32
+ CfgDir = File.join(ENV['HOME'].gsub(/\\/, '/'), '.deplate')
33
+ elsif ENV['USERPROFILE']
34
+ CfgDir = File.join(ENV['USERPROFILE'].gsub(/\\/, '/'), 'deplate.rc')
35
+ else
36
+ if ENV['WINDIR']
37
+ CfgDir = File.join(File.dirname(ENV['WINDIR'].gsub(/\\/, '/')) ,'deplate.rc')
38
+ else
39
+ CfgDir = '/etc/deplate.rc'
40
+ end
41
+ # CfgDir = File.join(Dir.pwd.gsub(/\\/, '/'), 'deplate.rc')
42
+ puts <<MESSAGE
43
+ Can't find your personal configuration directory. Neither HOME nor
44
+ USERPROFILE is set. I will look for configuration files in:
45
+ #{CfgDir}
46
+ MESSAGE
47
+ end
48
+ LibDir = File.dirname(__FILE__)
49
+ DataDir = File.join(Config::CONFIG['datadir'], 'deplate')
50
+ # FileCache = File.join(CfgDir, 'file_list.dat')
51
+ FileCache = nil
52
+
53
+ # The structure containing index definitions
54
+ IndexEntry = Struct.new('DeplateIndexEntry', :name, :synonymes,
55
+ :label, :file, :level_as_string)
56
+
57
+ # If true, don't load user configuration files.
58
+ @vanilla = false
59
+
60
+ # see #log_valid_condition?
61
+ @log_treshhold = 4
62
+ # see #log_valid_condition?
63
+ @log_events = [:unknown_macro, :newbie]
64
+
65
+ # shut up
66
+ @@quiet = false
67
+ # color output
68
+ @@colored_output = false
69
+
70
+ # A hash of known modules and their ruby require filenames
71
+ @@modules = {}
72
+ # A hash of known formatters and their ruby require filenames
73
+ @@formatters = {}
74
+ # A hash of known css files and their ruby file names
75
+ @@css = {}
76
+ # A hash of known templates and their ruby file names
77
+ @@templates = {}
78
+ # A hash of known encodings and the corresponding module
79
+ @@symbols = {}
80
+ # A hash of known input definitions and their ruby require filenames
81
+ @@input_defs = {}
82
+ # A hash of known metadata formatters and their ruby require filenames
83
+ @@metadata_formats = {}
84
+
85
+ # This variable (array of strings) can contain some deplate markup that will
86
+ # be prepended to every file read
87
+ @@deplate_template = []
88
+
89
+ # A hash of {lang => message class}.
90
+ @@messages = {}
91
+ # The class of the message catalog that was loaded last
92
+ @@messages_last = nil
93
+ # An instance of Deplate::Messages that is used for translating
94
+ # messages.
95
+ @@message_object = nil
96
+
97
+ # The values for slot names are currently pre-defined in this hash. In the
98
+ # future they will be calculated dynamically as required.
99
+ # <+TBD+>This will be subject of change.
100
+ @@slot_names = {
101
+ #pre matter
102
+ :prematter_begin => 0,
103
+ :doc_def => 5,
104
+ :doc_beg => 8,
105
+ :head_beg => 10,
106
+ :fmt_packages => 13,
107
+ :mod_packages => 15,
108
+ :user_packages => 20,
109
+ :head => 30,
110
+ :head_meta => 31,
111
+ :head_identifier => 32,
112
+ :head_title => 33,
113
+ :head_extra => 34,
114
+ :user_head => 35,
115
+ :mod_head => 40,
116
+ :user_head => 50,
117
+ :htmlsite_prev => 55,
118
+ :htmlsite_up => 56,
119
+ :htmlsite_next => 57,
120
+ # Synonyms for the above 3
121
+ :htmlslides_prev => 55,
122
+ :htmlslides_up => 56,
123
+ :htmlslides_next => 57,
124
+ :html_relations => 58,
125
+ :css => 60,
126
+ :javascript => 70,
127
+ :head_end => 80,
128
+ :body_beg => 90,
129
+ :header => 95,
130
+ :prematter_end => 100,
131
+ :body_pre => 105,
132
+
133
+ #body
134
+ :body_begin => 0,
135
+ :navbar_js => 4,
136
+ :navbar_top => 5,
137
+ :body_title => 20,
138
+ :body => 50,
139
+ :footnotes => 75,
140
+ :navbar_bottom => 95,
141
+ :body_end => 100,
142
+
143
+ #post matter
144
+ :body_post => 0,
145
+ :postmatter_begin => 1,
146
+ :footer => 5,
147
+ :html_pageicons_beg => 10,
148
+ :html_pageicons => 11,
149
+ :html_pageicons_end => 12,
150
+ :pre_body_end => 15,
151
+ :body_end => 20,
152
+ :doc_end => 50,
153
+ :postmatter_end => 100,
154
+ }
155
+
156
+ # A hash of formatter names and corresponding classes
157
+ @@formatter_classes = {}
158
+
159
+ # A hash of names of input formats and corresponding classes
160
+ @@input_classes = {}
161
+
162
+ # The IO where to display messages.
163
+ @@log_destination = $stderr
164
+
165
+ class << self
166
+ # Do what has to be done. This is the method that gets called when
167
+ # invoking deplate from the command line. It checks the command line
168
+ # arguments, sets up a Deplate::Core object, and makes it convert the
169
+ # input files.
170
+ def deplate(args=ARGV)
171
+ if ENV['DeplateOptions']
172
+ for keyval in ENV['DeplateOptions'].split(/\s*;\s*/)
173
+ key, val = keyval.split(/\s*=\s*/)
174
+ case key
175
+ when 'vanilla'
176
+ @vanilla = true
177
+ end
178
+ end
179
+ end
180
+ modules, formatters, csss, templates, input_defs, meta_fmts = collect_standard
181
+
182
+ options = deplate_options
183
+ opts = OptionParser.new do |opts|
184
+ opts.banner = 'Usage: deplate.rb [OPTIONS] FILE [OTHER FILES ...]'
185
+ opts.separator ''
186
+ opts.separator 'deplate is a free software with ABSOLUTELY NO WARRANTY under'
187
+ opts.separator 'the terms of the GNU General Public License version 2.'
188
+ opts.separator ''
189
+
190
+ opts.separator 'General Options:'
191
+
192
+ opts.on('-a', '--[no-]ask',
193
+ 'On certain actions, query user before overwriting files') do |bool|
194
+ log("options.ask_user = #{bool}")
195
+ options.ask_user = bool
196
+ end
197
+
198
+ opts.on('-A', '--allow ALLOW', 'Allow certain things: x, X, $') do |string|
199
+ options.allow += string.strip.split(/[,; ]+/)
200
+ log("options.allow = #{string}")
201
+ end
202
+
203
+ opts.on('-c', '--config FILE', String,
204
+ 'Alternative user cfg file') do |file|
205
+ log("options.cfg = #{file}")
206
+ options.cfg = file
207
+ end
208
+
209
+ opts.on('--[no-]clean', 'Clean up temporary files') do |b|
210
+ log("options.clean = #{b}")
211
+ options.clean = b
212
+ end
213
+
214
+ opts.on('--color', 'Colored output') do |b|
215
+ log("options.color = #{b}")
216
+ enable_color(options)
217
+ end
218
+
219
+ opts.on('--css NAME', csss,
220
+ 'Copy NAME.css to the destination directory, if inexistent') do |file|
221
+ log("options.css = #{file}")
222
+ options.css << [file]
223
+ end
224
+
225
+ opts.on('--copy-css NAME', csss,
226
+ 'Copy NAME.css to the destination directory') do |file|
227
+ log("options.css = #{file}")
228
+ options.css << [file, true]
229
+ end
230
+
231
+ opts.on('-d', '--dir DIR', String, 'Output directory') do |dir|
232
+ log("options.dir = #{dir}")
233
+ if dir.kind_of?(String) and File.directory?(dir)
234
+ options.dir = dir
235
+ else
236
+ log(["Directory doesn't exist", dir], :error)
237
+ end
238
+ end
239
+
240
+ opts.on('-D', '--define NAME=VALUE', String,
241
+ 'Define a document option') do |text|
242
+ m = /^(\w+?)(=(.*))?$/.match(text)
243
+ if m
244
+ k = m[1]
245
+ v = m[3]
246
+ if v
247
+ v = remove_backslashes(v.gsub(/(^|[^\\])~/, '\\1 '))
248
+ else
249
+ v ='1'
250
+ end
251
+ log(%{options.variables[#{k}] = "#{v}"})
252
+ options.variables[k] = v
253
+ else
254
+ log(["Malformed variable definition on command line", text], :error)
255
+ end
256
+ end
257
+
258
+ opts.on('-e', '--[no-]each', 'Handle each file separately') do |bool|
259
+ log("options.each = #{bool}")
260
+ options.each = bool
261
+ end
262
+
263
+ opts.on('--[no-]force', 'Force output') do |bool|
264
+ log("options.force = #{bool}")
265
+ options.force = bool
266
+ end
267
+
268
+ opts.on('-f', '--format FORMAT', String,
269
+ 'Output format (default: html)') do |fmt|
270
+ log("options.fmt = #{fmt}")
271
+ if formatters.include?(fmt)
272
+ options.fmt = fmt
273
+ else
274
+ log(["Unknown formatter", fmt, formatters], :error)
275
+ exit 5
276
+ end
277
+ end
278
+
279
+ opts.on('--[no-]included', 'Output body only') do |bool|
280
+ log("options.included = #{bool}")
281
+ options.included = bool
282
+ end
283
+
284
+ opts.on('-i', '--input NAME', String, 'Input definition') do |str|
285
+ log("options.input_def = #{str}")
286
+ options.input_def = str
287
+ end
288
+
289
+ opts.on('--list FILE', String,
290
+ 'A file that contains a list of input files') do |file|
291
+ log("options.list = #{file}")
292
+ options.list = file
293
+ end
294
+
295
+ opts.on('--log FILE', String,
296
+ 'A file (or - for stdout) where to put the log') do |file|
297
+ case file
298
+ when '-'
299
+ file = $stdout
300
+ else
301
+ file = File.expand_path(file)
302
+ end
303
+ log("options.log = #{file}")
304
+ @@log_destination = file
305
+ end
306
+
307
+ opts.on('--[no-]loop', 'Read from stdin forever and ever') do |bool|
308
+ log("options.loop = #{bool}")
309
+ options.loop = bool
310
+ end
311
+
312
+ opts.on('--metadata [NAME]', meta_fmts,
313
+ 'Save metadata in this format (default: yaml)') do |str|
314
+ str ||= 'yaml'
315
+ log("options.metadata_model = #{str}")
316
+ unstopable_require(@@metadata_formats[str])
317
+ end
318
+
319
+ opts.on('-m', '--module MODULE', modules, 'Load a module') do |str|
320
+ log("options.modules << #{str}")
321
+ options.modules << str
322
+ end
323
+
324
+ opts.on('-o', '--out FILE', String, "Output to file or stdout ('-')") do |file|
325
+ log("options.out = #{file}")
326
+ options.out = file
327
+ options.explicitOut = true
328
+ end
329
+
330
+ opts.on('-p', '--pattern GLOBPATTERN', String, 'File name pattern') do |str|
331
+ log("options.file_pattern = #{remove_backslashes(str)}")
332
+ options.file_pattern = remove_backslashes(str)
333
+ end
334
+
335
+ opts.on('-P', '--exclude GLOBPATTERN', String,
336
+ 'Excluded file name pattern') do |str|
337
+ log("options.file_excl_pattern = #{remove_backslashes(str)}")
338
+ options.file_excl_pattern = remove_backslashes(str)
339
+ end
340
+
341
+ opts.on('-r', '--[no-]recurse', 'Recurse into directories') do |bool|
342
+ log("options.recurse = #{bool}")
343
+ options.recurse = bool
344
+ end
345
+
346
+ opts.on('--reset-filecache', 'Reset the file database') do |bool|
347
+ log("options.reset_filecache = #{bool}")
348
+ if File.exist?(FileCache)
349
+ File.delete(FileCache)
350
+ log("Deleting file database. Files will be re-scanned on next run.", :anyway)
351
+ end
352
+ exit 0
353
+ end
354
+
355
+ opts.on('-R', '--[no-]Recurse', 'Recurse and rebuild hierarchy') do |bool|
356
+ log("options.recurse_hierarchy = #{bool}")
357
+ options.recurse = bool
358
+ options.recurse_hierarchy = bool
359
+ end
360
+
361
+ opts.on('--[no-]simple-names', 'Disable simple wiki names') do |bool|
362
+ unless bool
363
+ options.disabled_particles << Deplate::HyperLink::Simple
364
+ end
365
+ end
366
+
367
+ opts.on('--suffix SUFFIX', String, 'Suffix for output files') do |str|
368
+ log("options.suffix = #{str}")
369
+ options.suffix = str
370
+ end
371
+
372
+ opts.on('-t', '--template NAME', String, 'Template to use') do |str|
373
+ if templates.include?(str) or File.exist?(str)
374
+ log("options.template = #{@@templates[str]}")
375
+ options.template = str
376
+ else
377
+ log(["Template not found", str], :error)
378
+ exit 5
379
+ end
380
+ end
381
+
382
+ opts.on('--[no-]vanilla', 'Ignore user configuration') do |bool|
383
+ log("options.vanilla = #{bool}")
384
+ options.vanilla = bool
385
+ end
386
+
387
+ opts.on('-x', '--allow-ruby [RUBY SAFE]', Integer,
388
+ 'Allow the execution of ruby code') do |level|
389
+ if level
390
+ options.allow_ruby = level
391
+ options.allow << level
392
+ else
393
+ options.allow_ruby = true
394
+ options.allow << 'x'
395
+ end
396
+ log("options.allow_ruby = #{options.allow_ruby}")
397
+ end
398
+
399
+ opts.on('-X', '--[no-]allow-exec', '--[no-]external',
400
+ 'Allow the execution of helper applications') do |bool|
401
+ options.allow_external = bool
402
+ options.allow << 'X'
403
+ log("options.allow_external = #{bool}")
404
+ end
405
+
406
+ opts.separator ""
407
+ opts.separator "LaTeX Formatter:"
408
+
409
+ opts.on('--[no-]pdf', 'Prepare for use with pdf(la)tex') do |bool|
410
+ log("options.pdftex = #{bool}")
411
+ options.pdftex = bool
412
+ options.variables['pdfOutput'] = true
413
+ end
414
+
415
+ opts.separator ''
416
+ opts.separator 'Available input defintions:'
417
+ opts.separator input_defs.join(', ')
418
+
419
+ opts.separator ''
420
+ opts.separator 'Available formatters:'
421
+ opts.separator formatters.join(', ')
422
+
423
+ opts.separator ''
424
+ opts.separator 'Available metadata formats:'
425
+ opts.separator meta_fmts.join(', ')
426
+
427
+ opts.separator ''
428
+ opts.separator 'Available modules:'
429
+ opts.separator modules.join(', ')
430
+
431
+ opts.separator ''
432
+ opts.separator 'Available css files:'
433
+ opts.separator csss.join(', ')
434
+
435
+ opts.separator ''
436
+ opts.separator 'Available templates:'
437
+ opts.separator templates.join(', ')
438
+
439
+ opts.separator ''
440
+ opts.separator 'Other Options:'
441
+
442
+ opts.on('--debug [LEVEL]', Integer, 'Show debug messages') do |v|
443
+ if v
444
+ @log_treshhold = v
445
+ end
446
+ $DEBUG = TRUE
447
+ $VERBOSE = TRUE
448
+ end
449
+
450
+ opts.on('--[no-]profile', 'Profile execution') do |b|
451
+ log("profile = #{b}")
452
+ # require "profile" if b
453
+ end
454
+
455
+ opts.on('--[no-]quiet', 'Be quiet') do |bool|
456
+ log("quiet = #{bool}")
457
+ @@quiet = bool
458
+ end
459
+
460
+ opts.on('-v', '--[no-]verbose', 'Run verbosely') do |v|
461
+ log("verbose = #{v}")
462
+ $VERBOSE = v
463
+ end
464
+
465
+ opts.on_tail('-h', '--help', 'Show this message') do
466
+ puts opts
467
+ exit 1
468
+ end
469
+
470
+ opts.on_tail('--version', 'Show version') do
471
+ puts version
472
+ exit 0
473
+ end
474
+
475
+ opts.on_tail('--microversion', 'Show version') do
476
+ puts microversion
477
+ exit 0
478
+ end
479
+ end
480
+
481
+ @@command_line_args = args.dup
482
+ options.files = opts.parse!(args)
483
+
484
+ if options.list
485
+ accum = []
486
+ File.open(options.list) do |io|
487
+ io.each {|l| accum << l.chomp}
488
+ end
489
+ options.files = accum + options.files
490
+ else
491
+ if options.files.empty?
492
+ failhelp(opts, 'No input files given!')
493
+ end
494
+ end
495
+
496
+ options.fmt ||= 'html'
497
+ require_standard(options)
498
+
499
+ formatter_class = @@formatter_classes[options.fmt]
500
+
501
+ # if options.multi_file_output
502
+ # if !options.dir
503
+ # failhelp(opts, "Output to multiple files requires the --dir option!")
504
+ # elsif options.each
505
+ # failhelp(opts, "Cannot use --each and multi-file output at the same time!")
506
+ # end
507
+ # end
508
+
509
+ if formatter_class
510
+ f = options.files[0]
511
+ options.dir ||= '.'
512
+ case f
513
+ when '-'
514
+ options.ext = ''
515
+ options.srcdir ||= Dir.pwd
516
+ options.out ||= '-'
517
+ else
518
+ options.ext = File.extname(f)
519
+ options.srcdir ||= File.dirname(f)
520
+ options.suffix ||= formatter_class.suffix
521
+ if options.out
522
+ options.out = options.dir ? File.join(options.dir, options.out) : options.out
523
+ else
524
+ options.out = get_out_fullname(f, options.suffix, options)
525
+ end
526
+ end
527
+
528
+ prc = Deplate::Core.new(options.fmt,
529
+ :formatter => formatter_class,
530
+ :options => options,
531
+ :now => true)
532
+ else
533
+ log(["Unknown formatter", options.fmt], :error)
534
+ exit 5
535
+ end
536
+ end
537
+
538
+ # Load the formatter, all required modules, and the user configuration
539
+ # files.
540
+ def require_standard(options)
541
+ require_input(options)
542
+
543
+ require_formatter(options)
544
+
545
+ for m in options.modules
546
+ require_module(options, m) if m
547
+ end
548
+
549
+ require 'deplate/mod/en' unless @@messages_last
550
+
551
+ # load general user config
552
+ options.cfg ||= File.join(CfgDir, 'config.rb')
553
+ unless options.vanilla
554
+ user_config(options, options.cfg)
555
+ user_setup(options) if defined?(user_setup)
556
+ end
557
+ end
558
+
559
+ # Read CfgDir/deplate.ini
560
+ #
561
+ # This file knows the following commands/entries
562
+ # <tt>mod NAME</tt>:: Load module NAME
563
+ # <tt>fmt NAME</tt>:: Set the default formatter
564
+ # <tt>clip NAME=TEXT</tt>:: Set a clip
565
+ # <tt>wiki NAME.SUFFIX BASEURL</tt>:: Define an interwiki
566
+ # <tt>wikichars UPPER LOWER</tt>:: Define the set of allowed
567
+ # character in wiki names
568
+ # <tt>VAR=VALUE</tt>:: Set the variable VAR to VALUE
569
+ # <tt>$ENV=VALUE</tt>:: Set the environment variable VAR to VALUE
570
+ #
571
+ # Lines beginning with one of ';#%' are considered comments.
572
+ def read_ini(options)
573
+ for inifile in [
574
+ File.join(Dir.pwd, "deplate.rc", 'deplate.ini'),
575
+ File.join(CfgDir, 'deplate.ini'),
576
+ ]
577
+ if File.exist?(inifile)
578
+ ini = File.open(inifile) {|io| io.read}
579
+ mode = :normal
580
+ acc = []
581
+ endm = nil
582
+ setter = nil
583
+ for line in ini
584
+ line.chomp!
585
+ case mode
586
+ when :normal
587
+ if line =~ /^\s*[;#%*]/
588
+ # comment
589
+ next
590
+ elsif line =~ /^\s*mod\s+(\S+)/
591
+ options.modules << $1
592
+ elsif line =~ /^\s*fmt\s+(\S+)/
593
+ options.fmt = $1
594
+ elsif line =~ /^\s*clip\s+([^\s=]+)\s*=\s*(.+)/
595
+ options.clips[$1] = $2
596
+ elsif line =~ /^\s*wiki ([A-Z]+)(\.\w+)?\s*=\s*(.+)/
597
+ Deplate::InterWiki.add($1, $3, $2)
598
+ elsif line =~ /^\s*wikichars\s*(\S+)\s*(\S+)/
599
+ Deplate::HyperLink.setup($1, $2)
600
+ elsif line =~ /^\s*app\s*([_\w]+)\s*=\s*(.+)/
601
+ Deplate::External.def_app $1, $2
602
+ elsif line =~ /^\s*(option\s+|:)([_\w]+)([!~]|\s*([?%])?=\s*(.+))/
603
+ case $3
604
+ when '!'
605
+ val = true
606
+ when '~'
607
+ val = false
608
+ else
609
+ case $4
610
+ when '?'
611
+ case $5
612
+ when 'true', 'yes', 'on'
613
+ val = true
614
+ when 'false', 'no', 'off'
615
+ val = false
616
+ else
617
+ Deplate::Core.log(['Malformed configuration line', line], :error)
618
+ next
619
+ end
620
+ when '%'
621
+ val = $5.to_i
622
+ else
623
+ val = $5
624
+ end
625
+ end
626
+ options.send("#$2=", val)
627
+ elsif line =~ /^\s*\$(\S+)\s*=\s*(.+)/
628
+ ENV[$1] = $2
629
+ elsif line =~ /^\s*(\S+)\s*=<<(.+)/
630
+ mode = :multiline
631
+ endm = $2
632
+ setter = lambda {|val| options.variables[$1] = val}
633
+ elsif line =~ /^\s*(\S+)\s*=\s*(.+)/
634
+ options.variables[$1] = $2
635
+ end
636
+ when :multiline
637
+ if line == endm
638
+ setter.call(acc)
639
+ acc = []
640
+ mode = :normal
641
+ else
642
+ acc << line
643
+ end
644
+ else
645
+ raise "Invalid mode"
646
+ end
647
+ end
648
+ end
649
+ end
650
+ end
651
+
652
+ # Load the input definition if any.
653
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
654
+ def require_input(options, input_name=nil)
655
+ name = input_name || options.input_def
656
+ rb = @@input_defs[name]
657
+ if rb
658
+ require rb
659
+ end
660
+ end
661
+
662
+ # Load the formatter named in options.fmt.
663
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
664
+ def require_formatter(options)
665
+ fmt = options.fmt
666
+ if @@formatter_classes[fmt]
667
+ Deplate::Core.log(['Formatter already loaded', fmt])
668
+ else
669
+ Deplate::Core.log(['Require formatter', fmt])
670
+ fmtf = @@formatters[fmt]
671
+ require fmtf
672
+ end
673
+ user_config(options, File.join(CfgDir, 'after', 'fmt', '%s.rb' % fmt))
674
+ user_config(options, File.join(CfgDir, 'after', 'fmt', fmt))
675
+ end
676
+
677
+ # Load a module.
678
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
679
+ # module_name:: The name of the module to be loaded
680
+ def require_module(options, module_name)
681
+ Deplate::Core.log(["Require module", module_name])
682
+ mf = @@modules[module_name]
683
+ vsave, $VERBOSE = $VERBOSE, false
684
+ begin
685
+ require mf
686
+ user_config(options, File.join(CfgDir, 'after', 'mod', '%s.rb' % module_name))
687
+ user_config(options, File.join(CfgDir, 'after', 'mod', module_name))
688
+ rescue Exception => e
689
+ Deplate::Core.log(["Loading module failed", module_name, e], :error)
690
+ ensure
691
+ $VERBOSE = vsave
692
+ end
693
+ end
694
+
695
+ # require NAME but don't stop if an error occurs; takes an optional block as
696
+ # argument, which is called when the file was successfully loaded.
697
+ # name:: A string passed on to +require+
698
+ def unstopable_require(name)
699
+ begin
700
+ require name
701
+ if block_given?
702
+ yield
703
+ end
704
+ rescue Exception => e
705
+ log(["Require failed", name, e], :error)
706
+ end
707
+ end
708
+
709
+ # Load the user configuration for a file/module.
710
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
711
+ # file:: Either a file name or a directory; if it is a
712
+ # directory, all ruby files in that directory will be loaded
713
+ def user_config(options, file)
714
+ unless options.vanilla
715
+ if File.exist?(file)
716
+ if File.stat(file).directory?
717
+ for f in Dir[File.join(file, '*.rb')]
718
+ user_config(options, f)
719
+ end
720
+ else
721
+ Deplate::Core.log(["Loading", file])
722
+ load(file)
723
+ end
724
+ end
725
+ end
726
+ end
727
+
728
+ # Set up the standard options structure.
729
+ # inherit:: A OpenStruct; if provided, reuse it
730
+ def deplate_options(inherit=nil)
731
+ options = inherit || OpenStruct.new
732
+ options.modules ||= []
733
+ options.clean ||= true
734
+ options.force ||= true
735
+ options.css ||= []
736
+ options.variables ||= Deplate::Variables.new
737
+ options.clips ||= {}
738
+ options.ext ||= ''
739
+ options.dir ||= '.'
740
+ options.allow ||= []
741
+ options.disabled_particles ||= []
742
+ read_ini(options) unless inherit
743
+ return options
744
+ end
745
+
746
+ # Return the current version number as string.
747
+ def version
748
+ Deplate::Core::Version
749
+ end
750
+
751
+ # Return the current micorversion number as string.
752
+ def microversion
753
+ [Deplate::Core::Version, Deplate::Core::MicroRev].join('-')
754
+ end
755
+
756
+ # Enable colored log output
757
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
758
+ def enable_color(options)
759
+ unless @@colored_output
760
+ unstopable_require('term/ansicolor') do
761
+ eval <<-EOR
762
+ class Color
763
+ class << self
764
+ include Term::ANSIColor
765
+ end
766
+ end
767
+ EOR
768
+ @@colored_output = options.color = true
769
+ end
770
+ end
771
+ end
772
+
773
+ # Collect all available modules, formatters, css files, and templates.
774
+ def collect_standard
775
+ if FileCache and File.exist?(FileCache)
776
+ File.open(FileCache) do |f|
777
+ data = Marshal.load(f)
778
+ if data['version'] == Deplate::Core.microversion
779
+ modules, @@modules = data['modules']
780
+ formatters, @@formatters = data['formatters']
781
+ csss, @@css = data['css']
782
+ templates, @@templates = data['templates']
783
+ input_defs, @@input_defs = data['input']
784
+ meta_fmts, @@metadata_formats = data['metadata']
785
+ Deplate::Core.log(['Using file cache', FileCache])
786
+ return modules, formatters, csss, templates, input_defs, meta_fmts
787
+ else
788
+ Deplate::Core.log(['Old file cache', FileCache])
789
+ end
790
+ end
791
+ end
792
+ modules, @@modules = collect_deplate_options('modules', 'mod')
793
+ formatters, @@formatters = collect_deplate_options('formatters','fmt')
794
+ csss, @@css = collect_deplate_options('css', 'css', '.css')
795
+ templates, @@templates = collect_deplate_options('templates', 'templates', '')
796
+ input_defs, @@input_defs = collect_deplate_options('input', 'input')
797
+ meta_fmts, @@metadata_formats = collect_deplate_options('metadata', 'metadata')
798
+ if FileCache
799
+ File.open(FileCache, 'w+') do |f|
800
+ data = {
801
+ 'version' => Deplate::Core.microversion,
802
+ 'modules' => [modules, @@modules],
803
+ 'formatters' => [formatters, @@formatters],
804
+ 'css' => [csss, @@css],
805
+ 'templates' => [templates, @@templates],
806
+ 'input' => [input_defs, @@input_defs],
807
+ 'metadata' => [meta_fmts, @@metadata_formats],
808
+ }
809
+ Marshal.dump(data, f)
810
+ Deplate::Core.log(['Create file cache', FileCache])
811
+ end
812
+ end
813
+ return modules, formatters, csss, templates, input_defs, meta_fmts
814
+ end
815
+
816
+ # This is the actual logging method. Every log message should pass
817
+ # through this method.
818
+ def log(text, condition=nil, source=nil)
819
+ if log_valid_condition?(condition)
820
+ text = log_filter(text)
821
+ if source
822
+ msg = log_build_message(text, condition, source.file, source.begin, source.end)
823
+ else
824
+ msg = log_build_message(text, condition)
825
+ end
826
+ log_to(msg)
827
+ log_to(caller.join("\n")) if $DEBUG and condition == :error
828
+ end
829
+ end
830
+
831
+ # Remove all backslashes from +text+
832
+ def remove_backslashes(text)
833
+ return text.gsub(/\\(.)/, '\\1')
834
+ end
835
+
836
+ # Clean +idx+'s (a instance of Deplate::IndexEntry) name from backslashes
837
+ def get_index_name(idx)
838
+ # return remove_backslashes(idx.name.split(/\s*\|\s*/)[0])
839
+ return remove_backslashes(idx.name)
840
+ end
841
+
842
+ # Retrieve field information from a "property list" as used form, e.g.,
843
+ # specifying column widths in tables
844
+ def props(proplist, field)
845
+ if proplist
846
+ proplist.split(/\s*[,;]\s*/).collect do |c|
847
+ rv = nil
848
+ for key, val in c.scan(/(\w+?)[:.](\S+)/)
849
+ if key == field
850
+ rv = val
851
+ break
852
+ end
853
+ end
854
+ rv
855
+ end
856
+ else
857
+ []
858
+ end
859
+ end
860
+
861
+ # Return the canonic file name for +name+. +maj+ and +min+
862
+ # correspond to section numbers.
863
+ def canonic_file_name(name, sfx, maj=nil, min=nil, dir=nil)
864
+ name = File.basename(name, File.extname(name))
865
+ name = clean_file_name(name)
866
+ if !name or (maj and maj != 0)
867
+ canonic_numbered_file_name(name, sfx, maj, min, dir)
868
+ elsif min and min != 0
869
+ # fn = "%s.%02d%s" % [name, min, sfx]
870
+ canonic_numbered_file_name(name, sfx, 0, min, dir)
871
+ else
872
+ fn = name + (sfx || '')
873
+ dir ? File.join(dir, fn) : fn
874
+ end
875
+ end
876
+
877
+ # Return a valid file name based on +text+
878
+ def clean_file_name(text, replacement='_')
879
+ text.gsub(/[[:cntrl:].+*:"?<>|&\\\/]/, replacement) if text
880
+ end
881
+
882
+ # Make sure +dir+ exists
883
+ def ensure_dir_exists(dir)
884
+ unless File.exist?(dir) or dir.empty? or dir == '.'
885
+ parent = File.dirname(dir)
886
+ unless File.exist?(parent)
887
+ ensure_dir_exists(parent)
888
+ end
889
+ Deplate::Core.log(["Creating directory", dir])
890
+ Dir.mkdir(dir)
891
+ end
892
+ end
893
+
894
+ # Return the output directory for +fname+
895
+ def get_out_name_dir(fname, options)
896
+ if options.recurse_hierarchy
897
+ # dir = File.dirname(fname).split(Regexp.new(Regexp.escape(File::SEPARATOR)))[1..-1]
898
+ # dir = File.dirname(fname).split(Regexp.new(Regexp.escape(File::SEPARATOR)))
899
+ # return File.join(*dir)
900
+ return File.dirname(fname)
901
+ else
902
+ return '.'
903
+ end
904
+ end
905
+
906
+ # Purge *path and return it as sting
907
+ def get_path(*path)
908
+ path.compact!
909
+ path.delete(".")
910
+ path.delete("")
911
+ File.join(*path)
912
+ end
913
+
914
+ # Get the canonic output filename for fname.
915
+ # fname:: The input file name
916
+ # suffix:: The suffix to use
917
+ # options:: A OpenStruct as returned by Deplate::Core.deplate_options
918
+ # maj:: The major section/page number
919
+ # maj:: The minor section/page number
920
+ def get_out_fullname(fname, suffix, options, *args)
921
+ File.join(options.dir, get_out_name(fname, suffix, options, *args))
922
+ end
923
+
924
+ def get_out_name(fname, suffix, options, maj=nil, min=nil)
925
+ path = []
926
+ path << get_out_name_dir(fname, options)
927
+ if suffix
928
+ fn = File.basename(fname, options.ext)
929
+ path << canonic_file_name(fn, suffix, maj, min)
930
+ else
931
+ path << fname
932
+ end
933
+ get_path(*path)
934
+ end
935
+
936
+ def declare_input_format(input_class, name=nil)
937
+ @@input_classes[name || input_class.myname] = input_class
938
+ end
939
+
940
+ # Make a formatter class publically known.
941
+ def declare_formatter(formatter_class, name=nil)
942
+ @@formatter_classes[formatter_class.myname] = formatter_class
943
+ name = (name || formatter_class.myname).gsub(/\W/, '_')
944
+ self.class_eval %{
945
+ def to_#{name}(text, sourcename=nil)
946
+ format_with_formatter(#{formatter_class}, text, sourcename)
947
+ end
948
+ }
949
+ # class << self
950
+ # define_method("to_#{name}") do |text, *args|
951
+ # sourcename, _ = args
952
+ # format_with_formatter(formatter_class, text, sourcename)
953
+ # end
954
+ # end
955
+ end
956
+
957
+ # Collect all available modules/parts/libraries etc. Check the
958
+ # file system and the "builtin" modules (e.g., when using the
959
+ # win32 exerb distribution).
960
+ def collect_deplate_options(id=nil, subdir='', suffix='.rb')
961
+ hash = {}
962
+ for d in library_directories(subdir)
963
+ collect_deplate_options_in_hash(hash, suffix, Dir[File.join(d, '*%s' % suffix)])
964
+ end
965
+
966
+ builtin = "builtin_#{id}"
967
+ if id and respond_to?(builtin)
968
+ collect_deplate_options_in_hash(hash, suffix, send(builtin), File.join('deplate', subdir))
969
+ end
970
+
971
+ return hash.keys.sort, hash
972
+ end
973
+
974
+ # Return an array of directorys that could contain deplate
975
+ # files.
976
+ def library_directories(*subdirs)
977
+ @library_directories ||= {}
978
+ id = File.join(*subdirs)
979
+ unless @library_directories[id]
980
+ dirs = []
981
+ dirs << File.join(CfgDir, *subdirs) unless @vanilla
982
+ dirs << File.join(DataDir, *subdirs)
983
+ dirs << File.join(LibDir, *subdirs)
984
+ dirs.delete_if {|d| !File.exist?(d)}
985
+ @library_directories[id] = dirs
986
+ end
987
+ @library_directories[id]
988
+ end
989
+
990
+ private
991
+ # Return the localized text.
992
+ def msg(text)
993
+ if @@message_object
994
+ @@message_object.msg(text)
995
+ else
996
+ text
997
+ end
998
+ end
999
+
1000
+ # Return the proper numbered output filename for name.
1001
+ def canonic_numbered_file_name(name, sfx, maj, min, dir=nil)
1002
+ name = File.basename(name, File.extname(name))
1003
+ if min == 0
1004
+ idx = '%05d' % maj
1005
+ else
1006
+ idx = '%05d_%02d' % [maj, min]
1007
+ end
1008
+ fn = [name, idx, sfx].join
1009
+ dir ? File.join(dir, fn) : fn
1010
+ end
1011
+
1012
+ # Collect files in +array+ in +hash+.
1013
+ def collect_deplate_options_in_hash(hash, suffix, array, subdir=nil)
1014
+ for m in array
1015
+ key = File.basename(m, suffix) || m
1016
+ hash[key] ||= subdir ? File.join(subdir, m) : m
1017
+ hash[key] ||= subdir ? File.join(subdir, m) : m
1018
+ end
1019
+ end
1020
+
1021
+ # Display text and exit.
1022
+ def failhelp(opts, text)
1023
+ puts text
1024
+ puts
1025
+ puts opts
1026
+ exit 5
1027
+ end
1028
+
1029
+ # * If @@quiet is true, don't display any messages.
1030
+ # * If +condition+ is :anyway, :error, or +true+, display the
1031
+ # message.
1032
+ # * If +condition+ is :debug, display the message if $DEBUG is
1033
+ # set.
1034
+ # * If +condition+ is a numeric, display the message if +condition+
1035
+ # is less or equal @log_treshhold
1036
+ # * If +condition+ is a symbol, display the message if @log_events
1037
+ # contains +condition+.
1038
+ def log_valid_condition?(condition)
1039
+ if @@quiet
1040
+ return false
1041
+ elsif condition
1042
+ case condition
1043
+ when :anyway, :error, true
1044
+ return true
1045
+ when :debug
1046
+ return $DEBUG
1047
+ when Numeric
1048
+ return condition <= @log_treshhold
1049
+ when Symbol
1050
+ return @log_events.include?(condition)
1051
+ end
1052
+ else
1053
+ return $VERBOSE
1054
+ end
1055
+ end
1056
+
1057
+ # Convert text into a localized message.
1058
+ # text:: Either an array or a string.
1059
+ def log_filter(text)
1060
+ case text
1061
+ when Array
1062
+ m = []
1063
+ text.each_with_index do |t, i|
1064
+ if i == 0
1065
+ m << log_filter(t)
1066
+ else
1067
+ case t
1068
+ when Array
1069
+ m << t.join("\n")
1070
+ else
1071
+ m << t.to_s
1072
+ end
1073
+ end
1074
+ end
1075
+ if m.size > 1
1076
+ return "#{m[0]}: #{m[1..-1].join(", ")}"
1077
+ else
1078
+ return m[0]
1079
+ end
1080
+ when String, Symbol
1081
+ return msg(text)
1082
+ else
1083
+ raise msg(["Internal error: Bad log text", text])
1084
+ end
1085
+ end
1086
+
1087
+ # Delegate building the log message to
1088
+ # #log_build_colored_message or #log_build_monochrom_message
1089
+ # depending on whether we use colored output (@@colored_output).
1090
+ def log_build_message(*args)
1091
+ if @@colored_output
1092
+ log_build_colored_message(*args)
1093
+ else
1094
+ log_build_monochrom_message(*args)
1095
+ end
1096
+ end
1097
+
1098
+ # Build a non-colored log message.
1099
+ # text:: A string
1100
+ # condition:: A condition evaluated by #log_valid_condition?
1101
+ def log_build_monochrom_message(text, condition, file=nil, line_begin=nil, line_end=nil)
1102
+ msg = []
1103
+ if file
1104
+ msg << file
1105
+ msg << ':'
1106
+ if line_begin
1107
+ msg << line_begin
1108
+ msg << '-' << line_end if line_end and line_end != line_begin
1109
+ msg << ':'
1110
+ end
1111
+ end
1112
+ msg << text
1113
+ return msg.join
1114
+ end
1115
+
1116
+ # Build a colored log message.
1117
+ # text:: A string
1118
+ # condition:: A condition evaluated by #log_valid_condition?
1119
+ def log_build_colored_message(text, condition, file=nil, line_begin=nil, line_end=nil)
1120
+ msg = []
1121
+ if file
1122
+ msg << Color.green << Color.bold(file)
1123
+ msg << Color.yellow << ':'
1124
+ if line_begin
1125
+ msg << Color.cyan
1126
+ msg << line_begin
1127
+ msg << '-' << line_end if line_end and line_end != line_begin
1128
+ msg << Color.yellow
1129
+ msg << ':'
1130
+ end
1131
+ case condition
1132
+ when :error, :unknown_macro
1133
+ msg << Color.red
1134
+ else
1135
+ msg << Color.blue
1136
+ end
1137
+ msg << text
1138
+ else
1139
+ case condition
1140
+ when :error, :unknown_macro
1141
+ msg << Color.red
1142
+ else
1143
+ msg << Color.magenta
1144
+ end
1145
+ msg << text
1146
+ end
1147
+ msg << Color.clear
1148
+ return msg.join
1149
+ end
1150
+
1151
+ # Display text on io unless the @@quiet flag is set.
1152
+ def log_to(text, io=@@log_destination)
1153
+ unless @@quiet
1154
+ if io
1155
+ case io
1156
+ when String
1157
+ File.open(io, 'a') {|io| io.puts text}
1158
+ else
1159
+ io.puts text
1160
+ end
1161
+ else
1162
+ $stderr.puts msg('No log destination given!')
1163
+ end
1164
+ end
1165
+ end
1166
+
1167
+ # ???
1168
+ # def dispatch(src, object, method, modifier, *args)
1169
+ # disp = '%s_%s' % [method, modifier]
1170
+ # if object.methods.include?(disp)
1171
+ # return object.send(disp, *args)
1172
+ # else
1173
+ # return nil
1174
+ # end
1175
+ # end
1176
+ end
1177
+
1178
+ # A open structure that holds this instance's options.
1179
+ attr_reader :options
1180
+
1181
+ # The formatter this instance of deplate uses.
1182
+ attr_reader :formatter
1183
+
1184
+ # A hash that holds the current document's variables.
1185
+ attr_accessor :variables
1186
+
1187
+ # A hash holding all known document services (names => method).
1188
+ attr_accessor :doc_services
1189
+
1190
+ # Other document specific variables.
1191
+ # A hash containing the footnotes (id => object)
1192
+ attr_accessor :footnotes
1193
+ # An array holding already consumed footnotes IDs.
1194
+ attr_accessor :footnotes_used
1195
+ # A running index.
1196
+ attr_accessor :footnote_last_idx
1197
+
1198
+ # A hash (label => [IndexEntry]).
1199
+ attr_accessor :index
1200
+
1201
+ # A hash (level_string => object); currently only used by the
1202
+ # structured formatter.
1203
+ attr_accessor :headings
1204
+
1205
+ # A hash (id => object)
1206
+ attr_accessor :clips
1207
+
1208
+ # A hash (label => level_string). Use of this hash should be
1209
+ # replaced with uses of @label_aliases<+TBD+>
1210
+ attr_accessor :labels
1211
+ # An array (unused!!!???<+TBD+>)
1212
+ attr_accessor :labels_named
1213
+ # An array used to postpone labels until there is some regular
1214
+ # output.
1215
+ attr_accessor :labels_floating
1216
+ # A hash (label => corresponding elements).
1217
+ attr_accessor :label_aliases
1218
+
1219
+ # A hash (slot name => number).
1220
+ attr_reader :slot_names
1221
+
1222
+ # The current input filter.
1223
+ attr_reader :input
1224
+
1225
+ # The current output object (Deplate::Output).
1226
+ attr_reader :output
1227
+ # An ordered array of output filenames.
1228
+ attr_accessor :output_filename
1229
+ # An ordered array of top level/page headings.
1230
+ attr_accessor :output_headings
1231
+ # An array of collected output objects (@output).
1232
+ attr_accessor :collected_output
1233
+ # The base output file.
1234
+ attr_accessor :dest
1235
+ # An array holding the elements after reading the input files.
1236
+ attr_accessor :accum_elements
1237
+ # An array of Proc objects that will be evaluated before processing
1238
+ # any other elements.
1239
+ attr_accessor :preprocess
1240
+ # An array of Proc objects that will be evaluated after printing any
1241
+ # other elements.
1242
+ attr_accessor :postponed_print
1243
+
1244
+ attr_accessor :current_source, :current_heading, :current_table, :current_figure
1245
+ attr_accessor :current_source_stats, :current_top
1246
+ attr_accessor :table_of_contents, :table_of_tables, :table_of_figures
1247
+
1248
+ # a stack with if/elseif status; skip input if the top-switch is true
1249
+ attr_accessor :switches
1250
+
1251
+ # formatter_name:: A formatter name
1252
+ # args:: A hash
1253
+ def initialize(formatter_name='html', args={})
1254
+ @args = args
1255
+ @slot_names = @@slot_names.dup
1256
+ @options = Deplate::Core.deplate_options(args[:options])
1257
+ @sources = args[:sources] || @options.files
1258
+ @dest = args[:dest] || @options.out || ''
1259
+ @doc_services = args[:doc_services] || initialize_services
1260
+ @vanilla = args[:vanilla] || false
1261
+
1262
+ # set_safe
1263
+
1264
+ reset(!args[:inherit_options])
1265
+ @output = Deplate::Output.new(self)
1266
+ # @output.destination = File.join(@options.dir, @dest)
1267
+ @output.destination = @dest
1268
+
1269
+ call_methods_matching(self, /^deplate_initialize_/)
1270
+
1271
+ formatter_class = args[:formatter] || @@formatter_classes[formatter_name]
1272
+ if formatter_class
1273
+ Deplate::Core.log('Initializing formatter')
1274
+ @formatter = formatter_class.new(self, args)
1275
+ call_methods_matching(self, /^formatter_initialize_/)
1276
+ call_methods_matching(@formatter, /^formatter_initialize_/)
1277
+
1278
+ Deplate::Core.log('Initializing modules')
1279
+ call_methods_matching(self, /^module_initialize_/)
1280
+
1281
+ Deplate::Core.log('Setting up text scanner')
1282
+ call_methods_matching(self, /^input_initialize_/)
1283
+ call_methods_matching(self, /^hook_pre_input_initialize_/)
1284
+ initialize_input(args)
1285
+ call_methods_matching(self, /^hook_post_input_initialize_/)
1286
+
1287
+ Deplate::Core.log('Setting up formatter')
1288
+ call_methods_matching(@formatter, /^hook_pre_setup_/)
1289
+ @formatter.setup
1290
+ call_methods_matching(@formatter, /^hook_post_setup_/)
1291
+
1292
+ Deplate::Core.log('User initialization')
1293
+ user_initialize if defined?(user_initialize)
1294
+
1295
+ reset_output
1296
+ set_standard_clips
1297
+
1298
+ if args[:now]
1299
+ Deplate::Core.log('Here we go ...')
1300
+ call_methods_matching(self, /^hook_pre_go_/)
1301
+ call_methods_matching(@formatter, /^hook_pre_go_/)
1302
+ go_now
1303
+ call_methods_matching(self, /^hook_post_go_/)
1304
+ call_methods_matching(@formatter, /^hook_post_go_/)
1305
+ end
1306
+ else
1307
+ log(['No or unknown formatter', formatter_name], :error)
1308
+ end
1309
+ end
1310
+
1311
+ def_delegator(:@output, :pre_matter)
1312
+ def_delegator(:@output, :body)
1313
+ def_delegator(:@output, :post_matter)
1314
+ def_delegator(:@output, :destination)
1315
+ # def_delegator(:@output, :top_heading)
1316
+ def_delegator(:@output, :body_empty?)
1317
+ def_delegator(:@output, :add_at)
1318
+
1319
+ def_delegator(:@input, :initialize_particles)
1320
+ def_delegator(:@input, :register_particle)
1321
+ def_delegator(:@input, :parse_with_particles)
1322
+ def_delegator(:@input, :parse_with_source)
1323
+ def_delegator(:@input, :parse)
1324
+
1325
+ # (Re-)set @input
1326
+ # args:: A hash as passed to #initialize
1327
+ def initialize_input(args=@args)
1328
+ @input = args[:input] || @options.input
1329
+ unless @input
1330
+ input_class = args[:input_class] || @options.input_class || Deplate::Input
1331
+ @input = input_class.new(self, args)
1332
+ end
1333
+ end
1334
+
1335
+ def push_input_format(name)
1336
+ unless name
1337
+ return false
1338
+ end
1339
+ ic = @@input_classes[name]
1340
+ unless ic
1341
+ self.class.require_input(@options, name)
1342
+ ic = @@input_classes[name]
1343
+ end
1344
+ if ic
1345
+ @input_formats << @input
1346
+ @input = ic.new(self, @args)
1347
+ return true
1348
+ else
1349
+ log(['Unknown input format', name, @@input_classes.keys], :error)
1350
+ return false
1351
+ end
1352
+ end
1353
+
1354
+ def pop_input_format(name=nil)
1355
+ if @input_formats.empty?
1356
+ return false
1357
+ else
1358
+ if name and @input_formats.last.class.myname == name
1359
+ return false
1360
+ end
1361
+ @input = @input_formats.pop
1362
+ return true
1363
+ end
1364
+ end
1365
+
1366
+ # Define a new slot or reset the position of an already known slot.
1367
+ # <+TBD+>There is no information on whether the slot belongs to the
1368
+ # prematter/postmatter/body.
1369
+ # key:: The name (string)
1370
+ # val:: The position (integer)
1371
+ def set_slot_name(key, val)
1372
+ slot_names[key] = val
1373
+ end
1374
+
1375
+ # Return a slot position by its name.
1376
+ def slot_by_name(slot)
1377
+ if slot.kind_of?(Numeric)
1378
+ slotlist = slot_names.collect {|k,v| v == slot ? k : nil }.compact.join(", ")
1379
+ log(["Please refer to slots by their names", slot, slotlist], :error)
1380
+ log(caller[0..5].join("\n"), :error)
1381
+ slot
1382
+ elsif slot.is_a?(Symbol)
1383
+ slotstr = slot.to_s
1384
+ if slotstr =~ /^prepend_/
1385
+ modi = -1
1386
+ slot = slotstr[8..-1].to_sym
1387
+ else
1388
+ modi = 1
1389
+ end
1390
+ val = slot_names[slot]
1391
+ if val
1392
+ val * modi
1393
+ else
1394
+ nil
1395
+ end
1396
+ elsif slot.is_a?(String)
1397
+ pos = 0
1398
+ operator = "+"
1399
+ for i in slot.split(/([+-])/)
1400
+ case i
1401
+ when "-", "+"
1402
+ operator = i
1403
+ else
1404
+ j = slot_names[i.intern]
1405
+ unless j
1406
+ j = i.to_i
1407
+ end
1408
+ pos = pos.send(operator, j)
1409
+ end
1410
+ end
1411
+ pos
1412
+ end
1413
+ end
1414
+
1415
+ # Initialize symbol related modules.
1416
+ def initialize_symbols
1417
+ Deplate::Core.log("Initializing symbols")
1418
+ call_methods_matching(self, /^initialize_symbols_/)
1419
+ end
1420
+
1421
+ # Call all of obj's methods matching rx
1422
+ def call_methods_matching(obj, rx, *args)
1423
+ unless @vanilla
1424
+ for m in obj.methods.find_all {|m| m =~ rx }
1425
+ obj.send(m, *args)
1426
+ end
1427
+ end
1428
+ end
1429
+
1430
+ # Reset instance variables.
1431
+ # all:: reset really all variables (bool)
1432
+ def reset(all=false)
1433
+ @current_source = nil
1434
+ @current_source_stats = nil
1435
+ @auto_filenames = {}
1436
+
1437
+ @variables = @options.variables.dup
1438
+ @variables.deplate = self
1439
+
1440
+ @clips = {}
1441
+ @index = {}
1442
+ @index_last_idx = 0
1443
+ @labels = {}
1444
+ @label_aliases = {}
1445
+ @labels_named = []
1446
+ @labels_floating = []
1447
+ @ids = {}
1448
+ @preprocess = []
1449
+ @postponed_print = []
1450
+
1451
+ @footnotes = {}
1452
+ @footnote_last_idx = 0
1453
+ @footnotes_used = []
1454
+
1455
+ @headings = {}
1456
+
1457
+ @table_of_contents = []
1458
+ @table_of_tables = []
1459
+ @table_of_figures = []
1460
+
1461
+ @current_heading = []
1462
+ @accum_elements = []
1463
+ @switches = []
1464
+ @metadata = {}
1465
+
1466
+ @current_figure = 0
1467
+ @current_table = 0
1468
+
1469
+ set_lang(@@messages_last)
1470
+ set_standard_clips
1471
+
1472
+ if all
1473
+ @allsources = {}
1474
+ @input_formats = []
1475
+ @options.citations = []
1476
+ @options.bib = []
1477
+ @options.dont_index = []
1478
+ @options.author = []
1479
+ @options.heading_names = []
1480
+ end
1481
+ end
1482
+
1483
+ # Reset output-related variables.
1484
+ # inherit_null_output:: The new output obj inherits the settings from the
1485
+ # initial/anonymous output class (bool)
1486
+ def reset_output(inherit_null_output=true)
1487
+ log("Reset output", :debug)
1488
+ @collected_output = []
1489
+ @output_filename = []
1490
+ @output_headings = []
1491
+ @output_maj_min = [0, 0]
1492
+ @null_output = @output.dup
1493
+ if @options.multi_file_output
1494
+ dest = @variables["docBasename"] || @dest
1495
+ dest &&= File.basename(dest)
1496
+ # Deplate::Core.canonic_file_name(dest, @options.suffix, 0, 0)
1497
+ else
1498
+ dest = nil
1499
+ end
1500
+ dest = dest ? Deplate::Core.get_out_fullname(dest, nil, @options) : @dest
1501
+ heading = Deplate::NullTop.new(self, :destination => dest)
1502
+ @output_filename[0] = dest
1503
+ push_top_heading(heading)
1504
+ if inherit_null_output
1505
+ new_output(@null_output)
1506
+ else
1507
+ Deplate::Output.reset
1508
+ new_output(nil)
1509
+ end
1510
+ end
1511
+
1512
+ # Set the localization object.
1513
+ # lang:: The new language (string)
1514
+ def set_lang(lang)
1515
+ case lang
1516
+ when String
1517
+ msg_class = @@messages[lang]
1518
+ else
1519
+ msg_class = lang
1520
+ end
1521
+ if msg_class
1522
+ @options.messages = msg_class.new(self)
1523
+ @@message_object ||= @options.messages
1524
+ else
1525
+ log(["Bad language definition", lang, "(#{@@messages.keys.join(', ')})"], :error)
1526
+ end
1527
+ end
1528
+
1529
+ # See Deplate::Core.log
1530
+ def log(*args)
1531
+ self.class.log(*args)
1532
+ end
1533
+
1534
+ # See Deplate::Core.require_module.
1535
+ def require_module(m)
1536
+ Deplate::Core.require_module(@options, m)
1537
+ end
1538
+
1539
+ # Do something at last.
1540
+ def go_now
1541
+ if @options.each
1542
+ go_each
1543
+ elsif @options.loop
1544
+ loop do
1545
+ go
1546
+ reset(true)
1547
+ reset_output(false)
1548
+ end
1549
+ else
1550
+ @sources.uniq!
1551
+ go
1552
+ end
1553
+ end
1554
+
1555
+ # Process each in sources.
1556
+ def go_each(sources=@sources)
1557
+ rv = nil
1558
+ begin
1559
+ saved_sources = @sources
1560
+ for f in sources
1561
+ if File.stat(f).file?
1562
+ if to_be_included?(f)
1563
+ @sources = [f]
1564
+ @dest = Deplate::Core.get_out_fullname(f, @options.suffix, @options)
1565
+ reset(true)
1566
+ reset_output(false)
1567
+ if block_given?
1568
+ yield
1569
+ else
1570
+ rv = go
1571
+ end
1572
+ end
1573
+ elsif @options.recurse
1574
+ go_each(get_dir_listing(f))
1575
+ else
1576
+ log(["Is no file", f], :error)
1577
+ end
1578
+ end
1579
+ ensure
1580
+ @sources = saved_sources
1581
+ end
1582
+ rv
1583
+ end
1584
+
1585
+ # Read input file, process, write the output if writeFile is true.
1586
+ def go(writeFile=true)
1587
+ read_file
1588
+ process_document
1589
+ body_write if writeFile
1590
+ end
1591
+
1592
+ # Return the index for a top heading.
1593
+ def output_index(top=nil)
1594
+ if top
1595
+ top_heading_idx(top)
1596
+ else
1597
+ @collected_output.size - 1
1598
+ end
1599
+ end
1600
+
1601
+ # Should the file be included or not, e.g., because of a -P
1602
+ # command line option
1603
+ def to_be_included?(file)
1604
+ rv = get_dir_listing(File.dirname(file))
1605
+ rv = rv.include?(file)
1606
+ if rv
1607
+ log(["Should be included", file])
1608
+ else
1609
+ log(["Should be excluded", file])
1610
+ end
1611
+ return rv
1612
+ end
1613
+
1614
+ # Evaluate block in the working directory; take care
1615
+ # of the auxiliaryDirSuffix variable
1616
+ def in_working_dir(cwd=nil, &block)
1617
+ pwd = Dir.pwd
1618
+ cwd ||= auxiliary_dirname(true, true)
1619
+ Dir.chdir(cwd) unless cwd.empty?
1620
+ begin
1621
+ block.call
1622
+ ensure
1623
+ Dir.chdir(pwd)
1624
+ end
1625
+ end
1626
+
1627
+ # Get the name for automatically generated auxiliary files (e.g.,
1628
+ # when no ID was provided)
1629
+ def auxiliary_auto_filename(type, idx, body=nil, suffix=nil)
1630
+ fn = ["_#{type}_#{idx}"]
1631
+ fn << '.' << suffix if suffix
1632
+ return fn.join
1633
+ end
1634
+
1635
+ # Get the proper filename for an auxiliary file, respecting
1636
+ # the value of auxiliaryDirSuffix
1637
+ def auxiliary_filename(filename, full_name=false)
1638
+ sd = auxiliary_dirname(full_name)
1639
+ if sd
1640
+ return Deplate::Core.get_path(sd, filename)
1641
+ else
1642
+ return filename
1643
+ end
1644
+ end
1645
+
1646
+ # Get the dir for auxiliary files; take care of the
1647
+ # auxiliaryDirSuffix variable
1648
+ # - If auxiliaryDirSuffix isn't defined, return default if non-nil.
1649
+ # - Create dir if ensure_dir_exists is true.
1650
+ def auxiliary_dirname(full_name=false, ensure_dir_exists=false)
1651
+ sdsfx = @variables['auxiliaryDirSuffix']
1652
+ path = []
1653
+ path << File.dirname(@dest) if full_name
1654
+ path << File.basename(@dest, '.*') + sdsfx if sdsfx
1655
+ rv = Deplate::Core.get_path(*path)
1656
+ ensure_dir_exists(rv) if ensure_dir_exists
1657
+ rv
1658
+ end
1659
+
1660
+ # Get a directory listing while respecting the -p and -P command line options
1661
+ def get_dir_listing(dir)
1662
+ pwd = Dir.pwd
1663
+ begin
1664
+ Dir.chdir(dir)
1665
+ files = Dir["*"]
1666
+ match = Dir[@options.file_pattern || "*"]
1667
+ files.delete_if {|f| File.stat(f).file? and !match.include?(f)}
1668
+ if @options.file_excl_pattern
1669
+ antilist = Dir[@options.file_excl_pattern]
1670
+ for anti in antilist
1671
+ files.delete(anti)
1672
+ end
1673
+ end
1674
+ log(["DIR", dir])
1675
+ ensure
1676
+ Dir.chdir(pwd)
1677
+ end
1678
+ unless dir == "."
1679
+ files.collect! {|f| File.join(dir, f)}
1680
+ end
1681
+ return files
1682
+ end
1683
+
1684
+ # Format either text or, if text is nil, the file "sourcename".
1685
+ # This is the method called by the Deplate::Formatter's
1686
+ # to_whatsoever methods.
1687
+ def format_with_formatter(formatter_class, text, sourcename=nil)
1688
+ if text
1689
+ format_string_with_formatter(formatter_class, text, sourcename)
1690
+ elsif sourcename
1691
+ format_file_with_formatter(formatter_class, sourcename)
1692
+ end
1693
+ end
1694
+
1695
+ # Format a file by means of formatter_class that is a child of Deplate::Formatter
1696
+ def format_file_with_formatter(formatter_class, sourcename)
1697
+ with_formatter(formatter_class) do
1698
+ go_each([sourcename])
1699
+ end
1700
+ end
1701
+
1702
+ # Format text by means of formatter_class that is a child of Deplate::Formatter
1703
+ def format_string_with_formatter(formatter_class, text, sourcename=nil)
1704
+ with_formatter(formatter_class) do
1705
+ format_string(text, sourcename)
1706
+ end
1707
+ end
1708
+
1709
+ # Format text with the current formatter
1710
+ def format_string(text, sourcename=nil)
1711
+ reset_output(false)
1712
+ maintain_current_source(sourcename) do
1713
+ accum_elements = @accum_elements
1714
+ @accum_elements = Array.new
1715
+ include_each(text, @accum_elements, sourcename)
1716
+ process_document
1717
+ return body_string
1718
+ end
1719
+ end
1720
+
1721
+ # Read text from STDIN. End on EOF or due to a matching
1722
+ # pair of #BEGIN, #END pseudo commands
1723
+ def include_stdin(array)
1724
+ if $stdin.eof?
1725
+ log("No more input on STDIN", :anyway)
1726
+ exit 1
1727
+ end
1728
+ maintain_current_source("") do
1729
+ log("Including from STDIN")
1730
+ acc = []
1731
+ end_tag = nil
1732
+ $stdin.each_with_index do |l, i|
1733
+ if i == 0 and l =~ /^#BEGIN:/
1734
+ end_tag = "#END:" + l[7..-1]
1735
+ elsif end_tag and l == end_tag
1736
+ break
1737
+ else
1738
+ acc << l
1739
+ end
1740
+ end
1741
+ include_each(acc, array, "STDIN")
1742
+ end
1743
+ end
1744
+
1745
+ # Read a file and add the parsed elements to array
1746
+ def include_file(array, filename)
1747
+ maintain_current_source(filename) do
1748
+ log(["Including", filename])
1749
+ filename_abs = File.expand_path(filename)
1750
+ unless @options.included
1751
+ filename_label = file_label(filename_abs)
1752
+ @labels_floating << filename_label
1753
+ end
1754
+ File.open(filename, "r") do |io|
1755
+ include_each(io, array, filename)
1756
+ end
1757
+ end
1758
+ end
1759
+
1760
+ # Execute &block while maintaining @current_top
1761
+ def maintain_current_top(top, &block)
1762
+ begin
1763
+ current_top = @current_top
1764
+ @current_top = top
1765
+ block.call
1766
+ ensure
1767
+ @current_top = current_top
1768
+ end
1769
+ end
1770
+
1771
+ # Include each line in enum and accumulate parsed elements in array
1772
+ def include_each(enum, array, sourcename=nil)
1773
+ @input.include_enum(enum, array, 0)
1774
+ end
1775
+
1776
+ # Include strings as if read from a file and return the resulting array of parsed elements
1777
+ def parsed_array_from_strings(strings, linenumber=nil, src="[array]")
1778
+ array = []
1779
+ include_stringarray(strings, array, linenumber, src)
1780
+ return array
1781
+ end
1782
+
1783
+ # Include strings as if read from a file and push parsed elements onto array
1784
+ def include_stringarray(strings, array, linenumber=nil, src="[array]")
1785
+ maintain_current_source(src) do
1786
+ @input.include_enum(strings, array, linenumber || 0)
1787
+ end
1788
+ end
1789
+
1790
+ # Set the current top heading.
1791
+ # heading:: Heading object
1792
+ # text:: The output filename base
1793
+ def set_top_heading(heading, text)
1794
+ if heading.level == 1
1795
+ fname = nil
1796
+ sfx = @options.suffix
1797
+ dir = @options.dir
1798
+ if @output_headings.include?(heading)
1799
+ maj = top_heading_idx(heading)
1800
+ else
1801
+ heading.top_heading = heading
1802
+ push_top_heading(heading)
1803
+ maj = @output_headings.size - 1
1804
+ unless text or !@options.multi_file_output
1805
+ if @variables["autoFileNames"]
1806
+ fname = Deplate::Core.clean_file_name(heading.get_text)[0..20]
1807
+ c = @auto_filenames[fname]
1808
+ if c
1809
+ fname = Deplate::Core.canonic_file_name(fname, sfx, c, 0)
1810
+ @auto_filenames[fname] += 1
1811
+ else
1812
+ fname = Deplate::Core.canonic_file_name(fname, sfx)
1813
+ @auto_filenames[fname] = 0
1814
+ end
1815
+ else
1816
+ fname = File.basename(@dest, File.extname(@dest))
1817
+ fname = Deplate::Core.canonic_file_name(fname, sfx, maj, 0)
1818
+ end
1819
+ end
1820
+ end
1821
+ if !@options.multi_file_output
1822
+ # fname = ""
1823
+ fname = @dest
1824
+ else
1825
+ if text
1826
+ fname = Deplate::Core.canonic_file_name(text, sfx)
1827
+ end
1828
+ if fname
1829
+ fname = Deplate::Core.get_out_fullname(fname, nil, @options)
1830
+ end
1831
+ end
1832
+ # if fname and @options.multi_file_output
1833
+ if fname
1834
+ heading.destination = @output_filename[maj] = fname
1835
+ end
1836
+ log(["Set top heading", maj, (text||"nil"), fname], :debug)
1837
+ end
1838
+ end
1839
+
1840
+ # Get a top/page heading by its index.
1841
+ # idx:: The index (integer)
1842
+ def top_heading_by_idx(idx)
1843
+ @output_headings[idx || 0]
1844
+ end
1845
+
1846
+ # Get the top/page heading index (or get the current index if no top
1847
+ # heading object is provided.
1848
+ def top_heading_idx(top=nil)
1849
+ if top
1850
+ @output_headings.index(top)
1851
+ else
1852
+ @output_headings.size - 1
1853
+ end
1854
+ end
1855
+
1856
+ # Return the number of output pages.
1857
+ def number_of_outputs
1858
+ @output_headings.size
1859
+ end
1860
+
1861
+ # Return the nth output filename.
1862
+ # idx:: Top heading index (integer)
1863
+ def output_filename_by_idx(idx)
1864
+ if idx
1865
+ idx = idx.to_i if idx.kind_of?(String)
1866
+ @output_filename[idx]
1867
+ end
1868
+ end
1869
+
1870
+ # accum format elts in pre/body|matter/post
1871
+ def printable_strings(strings, linenumber=nil, src="[array]")
1872
+ output = []
1873
+ accum_elements = []
1874
+ include_stringarray(strings, accum_elements, linenumber, src)
1875
+ accum_elements.collect! do |e|
1876
+ e.doc_type = :array
1877
+ e.doc_slot = output
1878
+ e.process
1879
+ end
1880
+ accum_elements.compact!
1881
+ for e in accum_elements
1882
+ e.doc_type = :array
1883
+ e.doc_slot = output
1884
+ e.print
1885
+ end
1886
+ return output
1887
+ end
1888
+
1889
+ # Run a "service", i.e., a small, mostly autonomous function/method that
1890
+ # usually yields some formatted output.
1891
+ def invoke_service(name, args={}, text="")
1892
+ method = @doc_services[name]
1893
+ if method
1894
+ begin
1895
+ return send(method, args || {}, text || "")
1896
+ rescue Exception => e
1897
+ puts e.backtrace[0..10].join("\n")
1898
+ Deplate::Core.log(["Calling service failed", name, e], :error)
1899
+ end
1900
+ else
1901
+ Deplate::Core.log(["Unknown service", name], :error)
1902
+ end
1903
+ end
1904
+
1905
+ # Return whether ruby code may be evaluated.
1906
+ def allow_ruby
1907
+ return @options.allow_ruby
1908
+ end
1909
+
1910
+ # Caller requests calling ruby code with some args
1911
+ def eval_ruby(invoker, args, code)
1912
+ ar = allow_ruby
1913
+ case ar
1914
+ when true
1915
+ begin
1916
+ # +++ Run this in a thread and set $SAFE for this thread only
1917
+ context = (args['context'] || '').downcase
1918
+ case context
1919
+ when 'ruby'
1920
+ return eval(code)
1921
+ when 'deplate'
1922
+ return self.instance_eval(code)
1923
+ when 'self', 'this'
1924
+ return invoker.instance_eval(code)
1925
+ else
1926
+ return Deplate::Void.module_eval(code)
1927
+ end
1928
+ rescue Exception => e
1929
+ src = invoker ? invoker.source : nil
1930
+ invoker.log(["Error in ruby code", code, e], :error)
1931
+ end
1932
+ when 1,2,3,4,5
1933
+ result, error = Deplate::Safe.safe(ar, code)
1934
+ if error then
1935
+ invoker.log(["Error in ruby code", code, error.inspect], :error)
1936
+ else
1937
+ return result.to_s
1938
+ end
1939
+ else
1940
+ if args['alt']
1941
+ return args['alt']
1942
+ elsif caller
1943
+ invoker.log(["Disabled ruby command", code], :anyway)
1944
+ end
1945
+ end
1946
+ end
1947
+
1948
+ # Set a clip.
1949
+ # id:: The clip's name
1950
+ # value:: An instance of either Deplate::Element::Clip or
1951
+ # Deplate::Regions::Clip.
1952
+ def set_clip(id, value)
1953
+ @clips[id] = value
1954
+ end
1955
+
1956
+ # Get a clip.
1957
+ # id:: The clip's name
1958
+ def get_clip(id)
1959
+ @clips[id]
1960
+ end
1961
+
1962
+ # Set all clips.
1963
+ # clips:: A hash
1964
+ def set_all_clips(clips)
1965
+ @clips = clips
1966
+ end
1967
+
1968
+ # Get a hash on yet unprocessed clips. Obsolete?
1969
+ def get_unprocessed_clips
1970
+ @clips
1971
+ end
1972
+
1973
+ # Get all css files that are required by the current document.
1974
+ def collected_css
1975
+ @@css
1976
+ end
1977
+
1978
+ # Return whether +file+ was already included.
1979
+ def file_included?(file, dir=nil, try_suffix=nil)
1980
+ dir = dir || "."
1981
+ file = File.expand_path(file, dir)
1982
+ rv = @allsources.keys.include?(file)
1983
+ if !rv and try_suffix
1984
+ file = File.expand_path(file + try_suffix, dir)
1985
+ rv = @allsources.keys.include?(file)
1986
+ end
1987
+ return rv
1988
+ end
1989
+
1990
+ # Make +file+ a filename relative to +dir+.
1991
+ def relative_path(file, dir)
1992
+ fn1 = Pathname.new(File.expand_path(file))
1993
+ fn2 = Pathname.new(File.expand_path(dir))
1994
+ rv = fn1.relative_path_from(fn2).to_s
1995
+ return rv == "." ? "" : rv
1996
+ end
1997
+
1998
+ def relative_path_by_file(file, base_file)
1999
+ if base_file
2000
+ relative_path(file, File.dirname(base_file))
2001
+ else
2002
+ File.basename(file)
2003
+ end
2004
+ end
2005
+
2006
+ # Return the automatically generated label for an included file.
2007
+ def file_label(filename_abs)
2008
+ if filename_abs
2009
+ label = @allsources[filename_abs]
2010
+ unless label
2011
+ # rel = relative_path(filename_abs, Dir.pwd)
2012
+ label = "file%03d" % @allsources.size
2013
+ label.gsub!(/\W/, "00")
2014
+ @allsources[filename_abs] = label
2015
+ end
2016
+ return label
2017
+ else
2018
+ return nil
2019
+ end
2020
+ end
2021
+
2022
+ # Amend +file+'s suffix.
2023
+ def file_with_suffix(file, sfx=nil, filename_only=false)
2024
+ sfx = sfx || ""
2025
+ sfx0 = File.extname(file)
2026
+ fn = File.basename(file, sfx0)
2027
+ if filename_only
2028
+ return fn + sfx
2029
+ else
2030
+ dir = File.dirname(file)
2031
+ fname = fn + sfx
2032
+ if dir == "."
2033
+ return fname
2034
+ else
2035
+ return File.join(dir, fname)
2036
+ end
2037
+ end
2038
+ end
2039
+
2040
+ # Return the output file according to +level_as_string+.
2041
+ def file_name_by_level(level_as_string)
2042
+ if @options.multi_file_output and level_as_string
2043
+ if level_as_string.kind_of?(String)
2044
+ las = level_as_string
2045
+ else
2046
+ las = level_as_string.to_s
2047
+ end
2048
+ if las == ""
2049
+ top = top_heading_by_idx(0)
2050
+ else
2051
+ top = nil
2052
+ catch(:ok) do
2053
+ each_heading do |heading, title|
2054
+ if heading.level_as_string == las
2055
+ top = heading.top_heading
2056
+ throw :ok
2057
+ end
2058
+ end
2059
+ raise "Internal error: unknown level: #{level_as_string}"
2060
+ end
2061
+ end
2062
+ return top.destination
2063
+ elsif level_as_string == "0"
2064
+ return File.basename(@dest)
2065
+ else
2066
+ return ""
2067
+ end
2068
+ end
2069
+
2070
+ # Return the canonic name for an automatically generated label (e.g.,
2071
+ # figures, tables ...)
2072
+ def elt_label(prefix, text, plain=false)
2073
+ if text
2074
+ if plain
2075
+ return "%s00%s" % [prefix, text.sum]
2076
+ else
2077
+ return "%s00%s" % [prefix, text.gsub(/\W/, "00")]
2078
+ end
2079
+ else
2080
+ raise msg("No label")
2081
+ log(["No label", prefix], :error)
2082
+ return nil
2083
+ end
2084
+ end
2085
+
2086
+ # Create a new output and push it to @collected_output.
2087
+ def new_output(inherited_output=nil, args={})
2088
+ @output = Deplate::Output.new(self, inherited_output)
2089
+ @collected_output << @output
2090
+ @output.top_heading = top_heading_by_idx(@collected_output.size - 1)
2091
+ @output.index = @output_maj_min.dup
2092
+ increase_maj_min
2093
+ end
2094
+
2095
+ # Insert a page/output break.
2096
+ def break_output(minor=false)
2097
+ @output.body_flush
2098
+ new_output(@output)
2099
+ end
2100
+
2101
+ # Return all the formatted output as string.
2102
+ def body_string
2103
+ return @collected_output.collect {|o| o.join("\n")}.join("\n")
2104
+ end
2105
+
2106
+ # Write the output to the disk.
2107
+ def body_write
2108
+ rv = nil
2109
+ method = uri_method_name_with_prefix("body_write_to_", @dest)
2110
+ log(["Writing output file(s)", @collected_output.size])
2111
+ if method
2112
+ for output in @collected_output
2113
+ rv = send(method, @dest, output)
2114
+ end
2115
+ elsif @dest == "-"
2116
+ sep = @variables["stdoutSeparator"]
2117
+ for output in @collected_output
2118
+ puts(output.join("\n"))
2119
+ puts sep if sep
2120
+ end
2121
+ elsif @options.multi_file_output
2122
+ @collected_output.each_with_index do |output, i|
2123
+ if output.body_empty?
2124
+ log("Empty body ... skipping")
2125
+ else
2126
+ dest = output.destination
2127
+ rv ||= dest
2128
+ log(["Writing file", i, dest])
2129
+ write_file(dest) do |io|
2130
+ io.puts(output.join("\n"))
2131
+ end
2132
+ call_methods_matching(@formatter, /^hook_post_write_file_/)
2133
+ output.merge_metadata(@metadata)
2134
+ if @options.metadata_model and output.metadata_available?
2135
+ md_dest = File.join(@options.dir, output.metadata_destination)
2136
+ log(["Saving metadata", md_dest])
2137
+ write_metadata(md_dest, output)
2138
+ end
2139
+ end
2140
+ end
2141
+ else
2142
+ dest = @collected_output.first.destination
2143
+ # dest = @dest
2144
+ log(["Writing file", dest])
2145
+ write_file(dest) do |io|
2146
+ for output in @collected_output
2147
+ io.puts(output.join("\n"))
2148
+ end
2149
+ io.puts
2150
+ end
2151
+ call_methods_matching(@formatter, /^hook_post_write_file_/)
2152
+ if @options.metadata_model
2153
+ md = @metadata.dup
2154
+ for output in @collected_output
2155
+ if output.metadata_available?
2156
+ for key, data in output.metadata
2157
+ for e in data
2158
+ push_metadata(e, md)
2159
+ end
2160
+ end
2161
+ end
2162
+ end
2163
+ unless md.empty?
2164
+ output = @collected_output.first
2165
+ md_dest = auxiliary_filename(output.metadata_destination(@dest), true)
2166
+ log(["Saving metadata", md_dest])
2167
+ write_metadata(md_dest, output, md)
2168
+ end
2169
+ end
2170
+ end
2171
+ rv || @dest
2172
+ end
2173
+
2174
+ # Make sure +dir+ exists (create it if it doesn't).
2175
+ def ensure_dir_exists(dir)
2176
+ unless File.exist?(dir)
2177
+ if @options.force
2178
+ Deplate::Core.ensure_dir_exists(dir)
2179
+ else
2180
+ log(["Destination directory doesn't exist", dir, Dir.pwd], :error)
2181
+ exit 5
2182
+ end
2183
+ end
2184
+ end
2185
+
2186
+ # Actually write something to some file.
2187
+ def write_file(file, &block)
2188
+ if file
2189
+ # pwd = Dir.pwd
2190
+ begin
2191
+ # Dir.chdir(@options.dir)
2192
+ ensure_dir_exists(File.dirname(file))
2193
+ ok = if File.exist?(file) and @options.ask
2194
+ print "File '#{file}' already exists. Overwrite (y/N)? "
2195
+ gets.chomp == 'y'
2196
+ else
2197
+ true
2198
+ end
2199
+ if ok
2200
+ File.open(file, 'w') do |io|
2201
+ block.call(io)
2202
+ end
2203
+ end
2204
+ # ensure
2205
+ # Dir.chdir(pwd)
2206
+ rescue Exception => e
2207
+ log(['Error when writing file', file, e], :error)
2208
+ exit 5
2209
+ end
2210
+ else
2211
+ log(['No output file', file], :error)
2212
+ end
2213
+ end
2214
+
2215
+ # Increase the heading level.
2216
+ def increase_current_heading(level)
2217
+ if @current_heading.size < level
2218
+ while @current_heading.size < level
2219
+ @current_heading << 1
2220
+ end
2221
+ else
2222
+ while @current_heading.size > level
2223
+ @current_heading.pop
2224
+ end
2225
+ @current_heading << (@current_heading.pop + 1)
2226
+ end
2227
+ end
2228
+
2229
+ # Get the current section's level as string.
2230
+ def get_current_heading
2231
+ return @current_heading.join(".")
2232
+ end
2233
+
2234
+ # Get the current top heading object.
2235
+ def get_current_top
2236
+ top_heading_by_idx(top_heading_idx)
2237
+ end
2238
+
2239
+ # Register a new label.
2240
+ # invoker:: The labelled object
2241
+ # label:: The label name
2242
+ # level_as_string:: The section heading's level as string (redundant???)
2243
+ def add_label(invoker, label, level_as_string, opts={})
2244
+ if !opts[:anyway] and (@labels[label] or @label_aliases[label])
2245
+ invoker.log(["Label already defined", label, level_as_string], :error)
2246
+ else
2247
+ @labels[label] = level_as_string
2248
+ @label_aliases[label] = opts[:container] || invoker
2249
+ end
2250
+ end
2251
+
2252
+ # <+TBD+>This doesn't work as intended. Elements still have to be labelled in order to
2253
+ # be referred to by their ID
2254
+ def get_label_by_id(invoker, id)
2255
+ o = @ids[id]
2256
+ if o
2257
+ l = o.label
2258
+ l &&= l.first
2259
+ if l
2260
+ return l
2261
+ else
2262
+ return id
2263
+ # invoker.log(["Object has no label", id], :error)
2264
+ end
2265
+ else
2266
+ invoker.log(["No object with that id", id], :error)
2267
+ end
2268
+ end
2269
+
2270
+ # Get the filename of the object marked with +label+.
2271
+ def get_filename_for_label(invoker, label)
2272
+ f = @label_aliases[label]
2273
+ if f
2274
+ f = f.top_heading.destination
2275
+ d = invoker.top_heading.destination
2276
+ if f == d
2277
+ return ""
2278
+ else
2279
+ return relative_path(f, File.dirname(d))
2280
+ end
2281
+ else
2282
+ # puts caller
2283
+ invoker.log(["Reference to unknown label", label], :error)
2284
+ end
2285
+ end
2286
+
2287
+ # A dummy method to be overwritten by a metadata module.
2288
+ def dump_metadata(data)
2289
+ data
2290
+ end
2291
+
2292
+ # A dummy method to be overwritten by a metadata module.
2293
+ def put_metadata(io, metadata)
2294
+ io.puts(metadata)
2295
+ end
2296
+
2297
+ # A dummy method to be overwritten by a metadata module.
2298
+ def write_metadata(file, output, metadata=nil)
2299
+ write_file(file) do |io|
2300
+ md = metadata ? output.format_metadata(metadata) : output.format_metadata
2301
+ put_metadata(io, md)
2302
+ end
2303
+ end
2304
+
2305
+ # Return the metadata as hash.
2306
+ def get_metadata(source, metadata)
2307
+ if @options.metadata_model
2308
+ if source
2309
+ metadata['source_file'] = source.file
2310
+ metadata['source_begin'] = source.begin
2311
+ metadata['source_end'] = source.end
2312
+ if (stats = source.stats)
2313
+ if (mtime = stats.mtime)
2314
+ metadata['source_mtime'] = mtime
2315
+ end
2316
+ end
2317
+ end
2318
+ metadata
2319
+ else
2320
+ nil
2321
+ end
2322
+ end
2323
+
2324
+ # Register a new metadata entry.
2325
+ # source:: The related source filename
2326
+ # metadata:: A hash
2327
+ def register_metadata(source, metadata)
2328
+ if @options.metadata_model
2329
+ push_metadata(get_metadata(source, metadata))
2330
+ end
2331
+ end
2332
+
2333
+ # Actually save the metadata in some variable for later use.
2334
+ def push_metadata(data, array=@metadata)
2335
+ if @options.metadata_model
2336
+ type = data["type"]
2337
+ @metadata[type] ||= []
2338
+ @metadata[type] << data
2339
+ end
2340
+ end
2341
+
2342
+ # Register an object's ID.
2343
+ # <+TBD+>Not systematically used yet.
2344
+ def register_id(hash, obj)
2345
+ id = hash["id"]
2346
+ xid = hash["xid"]
2347
+ if id and xid
2348
+ log(["Option clash: both id and xid provided", id, xid], :error, obj.source)
2349
+ else
2350
+ id ||= xid
2351
+ end
2352
+ if id and !id.empty?
2353
+ if @ids[id]
2354
+ # obj.log(["ID with the same name already exists", id, @ids[id].level_as_string], :error)
2355
+ else
2356
+ obj.log(["Register id", id, obj.class], :debug)
2357
+ @ids[id] = obj
2358
+ end
2359
+ end
2360
+ end
2361
+
2362
+ # Register a new index entry.
2363
+ def add_index(container, names, level_as_string="")
2364
+ @index_last_idx += 1
2365
+ id = "idx00#{@index_last_idx}"
2366
+ words = names.split(/\s*\|\s*/)
2367
+ lname = Deplate::Core.remove_backslashes(words[0])
2368
+ level_as_string = container ? container.level_as_string : level_as_string
2369
+ if @options.dont_index.delete(lname)
2370
+ return nil
2371
+ else
2372
+ i = @index[lname]
2373
+ unless i
2374
+ i = @index.find do |k, a|
2375
+ a.find do |i|
2376
+ i.synonymes.find {|j| words.include?(j)}
2377
+ end
2378
+ end
2379
+ if i
2380
+ lname = i[0]
2381
+ i = @index[lname]
2382
+ end
2383
+ end
2384
+ if @options.each
2385
+ f = file_with_suffix(File.basename(@current_source), @options.suffix, true)
2386
+ elsif @options.multi_file_output
2387
+ f = nil
2388
+ else
2389
+ f = @dest
2390
+ end
2391
+ d = IndexEntry.new(lname, words, id, f, level_as_string)
2392
+ if i
2393
+ i << d
2394
+ else
2395
+ @index[lname] = [d]
2396
+ end
2397
+ return d
2398
+ end
2399
+ end
2400
+
2401
+ # Remove a registered index entry.
2402
+ def remove_index(containes, names)
2403
+ lname = Deplate::Core.remove_backslashes(names.split(/\s*\|\s*/)[0])
2404
+ i = @index[lname]
2405
+ if i
2406
+ i.pop
2407
+ @index.delete(lname) if i.empty?
2408
+ end
2409
+ end
2410
+
2411
+ # Return a localized version of text. (delegated)
2412
+ def msg(text)
2413
+ @options.messages.msg(text)
2414
+ end
2415
+
2416
+ # Class variable accessor.
2417
+ def symbols
2418
+ @@symbols
2419
+ end
2420
+
2421
+ # Class variable accessor.
2422
+ def templates
2423
+ @@templates
2424
+ end
2425
+
2426
+ # Join an array of particles into a string.
2427
+ def join_particles(particles)
2428
+ particles.join
2429
+ end
2430
+
2431
+ # Return an array of unprocessed particles as string.
2432
+ def format_particles(particles)
2433
+ return join_particles(particles.collect{|e| e.process; e.elt})
2434
+ end
2435
+
2436
+ # Parse +text+ and return a formatted string.
2437
+ def parse_and_format(container, text, alt=true, excluded=[])
2438
+ t = parse(container, text, alt, excluded)
2439
+ return format_particles(t)
2440
+ end
2441
+
2442
+ def parse_and_format_without_wikinames(container, text, alt=true)
2443
+ excluded = [
2444
+ Deplate::HyperLink::Simple,
2445
+ ]
2446
+ return parse_and_format(container, text, alt, excluded)
2447
+ end
2448
+
2449
+ # Evaluate block (args: heading, caption) with each heading.
2450
+ def each_heading(depth=nil, &block)
2451
+ case depth
2452
+ when :top
2453
+ arr = @output_headings
2454
+ depth = false
2455
+ else
2456
+ arr = @table_of_contents
2457
+ end
2458
+ for section in arr
2459
+ if !depth or (section and section.level <= depth)
2460
+ unless section and section.args["noList"]
2461
+ if section.kind_of?(Deplate::NullTop)
2462
+ v = section.caption
2463
+ else
2464
+ v = section.description.gsub(/<\/?[^>]*>/, "")
2465
+ v = [section.level_as_string, v].join(" ") unless section.plain_caption?
2466
+ end
2467
+ block.call(section, v)
2468
+ end
2469
+ end
2470
+ end
2471
+ end
2472
+
2473
+ # <+TBD+>
2474
+ def serve_object_by_id(args, text)
2475
+ if args["id"]
2476
+ o = @ids[args["id"]]
2477
+ if o
2478
+ return o
2479
+ else
2480
+ log(["Serving object failed because of unknown object", id], :error)
2481
+ end
2482
+ elsif args["array"]
2483
+ text = args["array"]
2484
+ sep = args["sep"]
2485
+ if sep
2486
+ sep = sep ? Regexp.escape(sep) : "\\s+"
2487
+ else
2488
+ sep = args["rx"]
2489
+ end
2490
+ if sep
2491
+ return text.split(Regexp.new(sep))
2492
+ else
2493
+ log("Serving array failed because no separator was defined", :error)
2494
+ end
2495
+ end
2496
+ end
2497
+
2498
+ # <+TBD+>
2499
+ def serve_formatted_object(args, text)
2500
+ id = args["id"] || text
2501
+ o = @variables[id]
2502
+ if o
2503
+ return o.format_as_string
2504
+ else
2505
+ log(["Formatting object failed because of unknown variable", id], :error)
2506
+ end
2507
+ end
2508
+
2509
+ private
2510
+ def initialize_services
2511
+ services = {}
2512
+ services['object'] = :serve_object_by_id
2513
+ services['format'] = :serve_formatted_object
2514
+ return services
2515
+ end
2516
+
2517
+ def set_safe
2518
+ if @options.allow_ruby and @options.allow_ruby.kind_of?(Integer)
2519
+ $SAFE = @options.allow_ruby
2520
+ end
2521
+ end
2522
+
2523
+ def set_standard_clips
2524
+ if defined?(@input) and @input
2525
+ for id, text in @options.clips
2526
+ src = '[clip]'
2527
+ text = parse_with_source(src, text, false)
2528
+ @options.clips[id] = Deplate::Element::Clip.new(text, self, src)
2529
+ end
2530
+ options.clips_initialized = true
2531
+ end
2532
+ @clips = @options.clips.dup if options.clips_initialized
2533
+ end
2534
+
2535
+ def reset_footones
2536
+ @footnote_last_idx = 0
2537
+ @footnotes_used = []
2538
+ end
2539
+
2540
+ # Execute block but make sure that @current_source remains unchanged
2541
+ def maintain_current_source(source, &block)
2542
+ begin
2543
+ current_source = @current_source
2544
+ current_source_stats = @current_source_stats
2545
+ @current_source = source
2546
+ if source and File.exist?(source)
2547
+ @current_source_stats = File.stat(source)
2548
+ else
2549
+ @current_source_stats = nil
2550
+ end
2551
+ block.call
2552
+ ensure
2553
+ @current_source = current_source
2554
+ @current_source_stats = current_source_stats
2555
+ end
2556
+ end
2557
+
2558
+ # Set @formatter to an instance of formatter_class, call block, and
2559
+ # restore the old @formatter
2560
+ def with_formatter(formatter_class, &block)
2561
+ if @formatter.instance_of?(formatter_class)
2562
+ block.call
2563
+ else
2564
+ formatter_orig = @formatter
2565
+ begin
2566
+ @formatter = formatter_class.new(self)
2567
+ block.call
2568
+ ensure
2569
+ @formatter = formatter_orig
2570
+ end
2571
+ end
2572
+ end
2573
+
2574
+ # Read the file
2575
+ def read_file(sources=@sources)
2576
+ for f in sources
2577
+ method = uri_method_name_with_prefix("read_from_", f)
2578
+ if method
2579
+ send(method, f)
2580
+ elsif f == "-"
2581
+ include_stringarray(@@deplate_template, @accum_elements, nil, "@@deplate_template")
2582
+ include_stdin(@accum_elements)
2583
+ elsif File.exists?(f)
2584
+ if File.stat(f).file?
2585
+ include_stringarray(@@deplate_template, @accum_elements, nil, "@@deplate_template")
2586
+ include_file(@accum_elements, f)
2587
+ elsif @options.recurse
2588
+ read_file(get_dir_listing(f))
2589
+ else
2590
+ log(['Is no file', f], :error)
2591
+ end
2592
+ else
2593
+ log(['File not found', f], :error)
2594
+ end
2595
+ end
2596
+ end
2597
+
2598
+ def process_document
2599
+ @formatter.pre_process
2600
+ process_etc
2601
+ process
2602
+ print_prepare
2603
+ print_etc
2604
+ print
2605
+ body_finish
2606
+ end
2607
+
2608
+ def process_etc
2609
+ unless @output_headings.empty?
2610
+ @output_headings.first.first_top = true
2611
+ @output_headings.last.last_top = true
2612
+ end
2613
+
2614
+ for p in preprocess
2615
+ p.call
2616
+ end
2617
+
2618
+ for fn in @footnotes.keys
2619
+ e = @footnotes[fn]
2620
+ @current_top = e.top_heading
2621
+ @footnotes[fn] = e.process
2622
+ end
2623
+
2624
+ for clp in @clips.keys
2625
+ e = @clips[clp]
2626
+ @current_top = e.top_heading
2627
+ @clips[clp] = e.process
2628
+ end
2629
+ end
2630
+
2631
+ # process elts
2632
+ def process
2633
+ log("Processing elements")
2634
+ call_methods_matching(@formatter, /^hook_pre_process_/)
2635
+ @accum_elements.collect! do |e|
2636
+ @current_top = e.top_heading
2637
+ e.process
2638
+ end
2639
+ @accum_elements.flatten!
2640
+ @accum_elements.compact!
2641
+ end
2642
+
2643
+ def print_prepare
2644
+ log("Preparing formatting")
2645
+ call_methods_matching(@formatter, /^hook_pre_prepare_/)
2646
+ @formatter.prepare
2647
+ call_methods_matching(@formatter, /^prepare_/)
2648
+ call_methods_matching(@formatter, /^hook_post_prepare_/)
2649
+ end
2650
+
2651
+ def print_etc
2652
+ reset_footones
2653
+ end
2654
+
2655
+ def print
2656
+ log("Formatting elements")
2657
+ for e in @accum_elements
2658
+ @current_top = e.top_heading
2659
+ e.print
2660
+ end
2661
+ for p in @postponed_print
2662
+ p.call
2663
+ end
2664
+
2665
+ end
2666
+
2667
+ def body_finish
2668
+ @output.body_flush
2669
+ end
2670
+
2671
+ def uri_method_name_with_prefix(prefix, uri)
2672
+ begin
2673
+ uri = URI.parse(uri)
2674
+ scheme = uri.scheme
2675
+ if scheme
2676
+ name = prefix + scheme
2677
+ if respond_to?(name)
2678
+ return name
2679
+ end
2680
+ end
2681
+ rescue URI::InvalidURIError => e
2682
+ end
2683
+ return nil
2684
+ end
2685
+
2686
+ # Set heading as the current top heading.
2687
+ def push_top_heading(heading)
2688
+ @output_headings << heading
2689
+ @current_top = heading
2690
+ end
2691
+
2692
+ def increase_maj_min(minor=false)
2693
+ if minor
2694
+ @output_maj_min[1] += 1
2695
+ else
2696
+ @output_maj_min[0] += 1
2697
+ end
2698
+ end
2699
+
2700
+ end
2701
+
2702
+ # vim: ff=unix
2703
+ # Local Variables:
2704
+ # revisionRx: MicroRev\s\+=\s\+\'
2705
+ # End: