litbuild 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +1 -0
- data/LICENSE +14 -0
- data/README +135 -0
- data/bin/lb +69 -0
- data/gpl-3.0.txt +674 -0
- data/lib/litbuild/ascii_doc_visitor.rb +557 -0
- data/lib/litbuild/bash_script_visitor.rb +547 -0
- data/lib/litbuild/blueprint.rb +232 -0
- data/lib/litbuild/blueprint_library.rb +122 -0
- data/lib/litbuild/blueprint_parser.rb +210 -0
- data/lib/litbuild/commands.rb +37 -0
- data/lib/litbuild/driver.rb +66 -0
- data/lib/litbuild/errors.rb +39 -0
- data/lib/litbuild/logfile_namer.rb +23 -0
- data/lib/litbuild/multi_part_visitor.rb +35 -0
- data/lib/litbuild/narrative.rb +16 -0
- data/lib/litbuild/package.rb +143 -0
- data/lib/litbuild/section.rb +73 -0
- data/lib/litbuild/service_dir.rb +59 -0
- data/lib/litbuild/source_code_manager.rb +143 -0
- data/lib/litbuild/source_files_visitor.rb +26 -0
- data/lib/litbuild/url_visitor.rb +29 -0
- data/lib/litbuild/version.rb +5 -0
- data/lib/litbuild/visitor.rb +42 -0
- data/lib/litbuild.rb +16 -0
- data.tar.gz.sig +0 -0
- metadata +94 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,557 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stringio'
|
4
|
+
require 'litbuild/multi_part_visitor'
|
5
|
+
require 'litbuild/service_dir'
|
6
|
+
|
7
|
+
module Litbuild
|
8
|
+
##
|
9
|
+
# This class writes AsciiDoc fragments to directories, rooted at the
|
10
|
+
# DOCUMENT_DIR directory. It adds a sequential number prefix on each
|
11
|
+
# fragment written to a directory, and ensures that no duplicate
|
12
|
+
# fragments are written: if a fragment for a specific blueprint (or
|
13
|
+
# blueprint phase) has already been written to any directory,
|
14
|
+
# AsciiDocVisitor will ignore subsequent requests to write that
|
15
|
+
# blueprint (phase). AsciiDocVisitor can also write a top-level
|
16
|
+
# AsciiDoc document that includes all top-level fragments.
|
17
|
+
class AsciiDocVisitor < MultiPartVisitor
|
18
|
+
def initialize(parameters:)
|
19
|
+
@parameters = parameters
|
20
|
+
super(directory: @parameters['DOCUMENT_DIR'])
|
21
|
+
@written = Hash.new { |hash, key| hash[key] = [] }
|
22
|
+
@all_written_targets = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def visit_commands(commands:)
|
26
|
+
file_collector = {}
|
27
|
+
extra_section = proc do |doc|
|
28
|
+
render_files(doc, commands, file_collector)
|
29
|
+
end
|
30
|
+
write(blueprint: commands,
|
31
|
+
location: cwd,
|
32
|
+
extra: extra_section) do |doc, directive, values|
|
33
|
+
case directive
|
34
|
+
when 'commands' then write_commands(doc, values)
|
35
|
+
when 'file' then write_file_chunk(doc, file_collector, values)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def visit_narrative(narrative:)
|
41
|
+
write(blueprint: narrative, location: cwd)
|
42
|
+
end
|
43
|
+
|
44
|
+
def visit_package(package:)
|
45
|
+
write(blueprint: package,
|
46
|
+
location: cwd,
|
47
|
+
summary: summary_block_for(package)) do |doc, directive, value|
|
48
|
+
case directive
|
49
|
+
when 'configure-commands'
|
50
|
+
write_stage_commands(doc, value, 'Configuration')
|
51
|
+
when 'compile-commands'
|
52
|
+
write_stage_commands(doc, value, 'Compilation')
|
53
|
+
when 'test-commands'
|
54
|
+
write_stage_commands(doc, value, 'Test')
|
55
|
+
when 'install-commands'
|
56
|
+
write_stage_commands(doc, value, 'Installation')
|
57
|
+
when 'before-build-as-root'
|
58
|
+
write_stage_commands(doc, value, 'Pre-build (as `root`)')
|
59
|
+
when 'after-install-as-root'
|
60
|
+
write_stage_commands(doc, value, 'Post-installation (as `root`)')
|
61
|
+
when 'patches'
|
62
|
+
write_patch_block(doc, package, value)
|
63
|
+
when 'in-tree-sources'
|
64
|
+
write_in_tree_block(doc, package, value)
|
65
|
+
when 'build-dir'
|
66
|
+
write_build_dir(doc, value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def visit_section(section:)
|
72
|
+
# The files that need to be included by the section -- explicitly
|
73
|
+
# declared, added for dependencies, implicitly included by phase
|
74
|
+
# or whatever. Explicit blueprints (and their dependencies, if
|
75
|
+
# any) will be written into the narrative wherever they appear;
|
76
|
+
# the others will be written at the end by
|
77
|
+
# `write_remaining_blueprints`.
|
78
|
+
files = @written[File.join(cwd, section.name)].clone
|
79
|
+
write_remaining_blueprints = proc do |doc|
|
80
|
+
files.each do |file|
|
81
|
+
doc.puts
|
82
|
+
doc.puts("include::./#{section.name}/#{file}[leveloffset=+1]")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
write(blueprint: section,
|
86
|
+
location: cwd,
|
87
|
+
extra: write_remaining_blueprints) do |doc, directive, value|
|
88
|
+
case directive
|
89
|
+
when 'blueprints'
|
90
|
+
write_blueprint_includes(doc, value, section.name, files)
|
91
|
+
when 'package-list-with-versions'
|
92
|
+
write_package_list(doc, section)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def write_toplevel_doc(blueprint:)
|
98
|
+
doc_dir = @parameters['DOCUMENT_DIR']
|
99
|
+
return if @written[doc_dir].empty?
|
100
|
+
|
101
|
+
doc_name = File.join(doc_dir, "#{blueprint.file_name}.adoc")
|
102
|
+
File.open(doc_name, 'w') do |f|
|
103
|
+
top_header = blueprint.header_text
|
104
|
+
top_header += " (#{blueprint.active_phase})" if blueprint.active_phase
|
105
|
+
f.puts("= #{top_header}")
|
106
|
+
f.puts(blueprint.value('author')) if blueprint['author']
|
107
|
+
f.puts(blueprint.value('revision')) if blueprint['revision']
|
108
|
+
f.puts(':sectnums:')
|
109
|
+
f.puts(':doctype: book') unless other_parts.empty?
|
110
|
+
write_toplevel_includes(location: doc_dir, document: f)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
##
|
117
|
+
# Write an AsciiDoc fragment.
|
118
|
+
#
|
119
|
+
# blueprint: the blueprint for which to write a fragment.
|
120
|
+
# location: the directory where the fragment should be written.
|
121
|
+
# extra: a proc that will render extra sections to be added at the
|
122
|
+
# end, if any.
|
123
|
+
# summary: a proc that will render a summary block, if one is
|
124
|
+
# needed. This will be called with the document I/O and with a
|
125
|
+
# `for_phase` parameter of `false` when writing an unphased
|
126
|
+
# blueprint or the base narrative for a phased blueprint, or
|
127
|
+
# `true` if writing the phase narrative of a phased blueprint.
|
128
|
+
#
|
129
|
+
# This method should be called with a block that accepts:
|
130
|
+
# - the document I/O, which may be written to as needed;
|
131
|
+
# - the name of a directive being rendered; and
|
132
|
+
# - the directive values.
|
133
|
+
# The block should render whatever AsciiDoc is suitable based on the
|
134
|
+
# directives passed to it. If a directive need not result in any
|
135
|
+
# output, just ignore it.
|
136
|
+
def write(blueprint:, location:, extra: proc {}, summary: proc {}, &block)
|
137
|
+
return if @all_written_targets.include?(blueprint.target_name)
|
138
|
+
|
139
|
+
FileUtils.mkdir_p(location)
|
140
|
+
doc_name = format('%<count>02d-%<doc>s',
|
141
|
+
count: @written[location].size,
|
142
|
+
doc: blueprint.file_name + '.adoc')
|
143
|
+
File.open(File.join(location, doc_name), 'w') do |f|
|
144
|
+
f.write(to_asciidoc(blueprint, extra, summary, block))
|
145
|
+
end
|
146
|
+
@written[location] << doc_name
|
147
|
+
@all_written_targets << blueprint.target_name
|
148
|
+
end
|
149
|
+
|
150
|
+
##
|
151
|
+
# Should the base grafs of the blueprint be rendered? This is always
|
152
|
+
# true for unphased blueprints (since there is nothing else to
|
153
|
+
# render) and is true the _first_ time a phased blueprint is
|
154
|
+
# rendered.
|
155
|
+
def render_base_grafs?(blueprint)
|
156
|
+
return true unless blueprint.phases?
|
157
|
+
|
158
|
+
@all_written_targets.none? { |target| target =~ /^#{blueprint.name}::/ }
|
159
|
+
end
|
160
|
+
|
161
|
+
def to_asciidoc(blueprint, extra, summary, block)
|
162
|
+
doc = StringIO.new
|
163
|
+
if render_base_grafs?(blueprint)
|
164
|
+
doc.puts("[[#{blueprint.name},#{blueprint.header_text}]]")
|
165
|
+
doc.puts("= #{blueprint.header_text}")
|
166
|
+
doc.puts
|
167
|
+
summary.call(doc, false)
|
168
|
+
doc.puts("== Overview\n\n") if blueprint.active_phase
|
169
|
+
render_grafs(doc, blueprint.base_grafs, block)
|
170
|
+
doc.puts(phase_heading('==', blueprint)) if blueprint.active_phase
|
171
|
+
else
|
172
|
+
doc.puts(phase_heading('=', blueprint))
|
173
|
+
nm = blueprint.name
|
174
|
+
doc.puts("_For an overview of #{nm}, see <<#{nm}>>._\n\n")
|
175
|
+
end
|
176
|
+
if blueprint.active_phase
|
177
|
+
summary.call(doc, true)
|
178
|
+
render_grafs(doc, blueprint.phase_grafs, block)
|
179
|
+
end
|
180
|
+
extra.call(doc)
|
181
|
+
# If there are any extraneous blank lines in the document, get rid
|
182
|
+
# of them.
|
183
|
+
doc.string.gsub(/\n\n\n+/, "\n\n").strip + "\n"
|
184
|
+
end
|
185
|
+
|
186
|
+
def phase_heading(level, blueprint)
|
187
|
+
"[[#{blueprint.name_with_phase},#{blueprint.header_text_with_phase}]]\n" \
|
188
|
+
"#{level} #{blueprint.header_text_with_phase}\n\n"
|
189
|
+
end
|
190
|
+
|
191
|
+
def render_grafs(doc, grafs, block)
|
192
|
+
grafs.each do |graf|
|
193
|
+
case graf
|
194
|
+
when String then doc.puts(graf)
|
195
|
+
when Hash then handle_directives(doc, graf, block)
|
196
|
+
end
|
197
|
+
doc.puts
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
##
|
202
|
+
# Render some AsciiDoc for a directive hash into the specified
|
203
|
+
# document. Any directive that may be specified as part of any
|
204
|
+
# blueprint should be handled directly here. Other types of
|
205
|
+
# directives will be passed into the block given originally to the
|
206
|
+
# `write` method; each visit method may define a block that does the
|
207
|
+
# correct thing for all directives that may be defined in that type
|
208
|
+
# of blueprint.
|
209
|
+
def handle_directives(doc, dir_hash, block)
|
210
|
+
dir_hash.each do |directive, values|
|
211
|
+
if directive == 'parameter'
|
212
|
+
write_parameter(doc, values)
|
213
|
+
elsif directive == 'environment'
|
214
|
+
write_environment(doc, values)
|
215
|
+
elsif directive == 'depends-on'
|
216
|
+
write_dependencies(doc, values)
|
217
|
+
elsif directive == 'configuration-files'
|
218
|
+
write_cfgfiles(doc, values)
|
219
|
+
elsif directive == 'servicedir'
|
220
|
+
write_servicedir(doc, values)
|
221
|
+
elsif directive == 'service-pipeline'
|
222
|
+
write_servicepipe(doc, values)
|
223
|
+
elsif block
|
224
|
+
block.call(doc, directive, values)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def write_servicedir(doc, svcdefs)
|
230
|
+
svcdefs.each do |svcdef|
|
231
|
+
sd = ServiceDir.new(svcdef)
|
232
|
+
head = ".Service Directory: #{sd.type.capitalize} `#{sd.name}`"
|
233
|
+
head += " (in bundle `#{sd.bundle}`)" if sd.bundle
|
234
|
+
doc.puts(head)
|
235
|
+
doc.puts("[%autowidth,cols=\"d,d\",caption=]\n|===\n\n")
|
236
|
+
write_servicedir_body(doc, sd)
|
237
|
+
doc.puts('|===')
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def write_servicepipe(doc, pipedefs)
|
242
|
+
pipedefs.each do |pipedef|
|
243
|
+
head = ".Service Pipeline: `#{pipedef['name'].first}`"
|
244
|
+
head += " (in bundle `#{pipedef['bundle'].first}`)" if pipedef['bundle']
|
245
|
+
doc.puts(head)
|
246
|
+
doc.puts("[%autowidth,cols=\"d,d\",caption=]\n|===\n\n")
|
247
|
+
pipedef['servicedirs'].each_with_index do |svcdir, idx|
|
248
|
+
sd = ServiceDir.new(svcdir)
|
249
|
+
doc.puts("2+h|Service #{idx + 1}: #{sd.type.capitalize} `#{sd.name}`")
|
250
|
+
write_servicedir_body(doc, sd)
|
251
|
+
end
|
252
|
+
doc.puts('|===')
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def write_servicedir_body(doc, svc)
|
257
|
+
svc.oneline_files.keys.sort.each do |fn|
|
258
|
+
next if fn == 'type'
|
259
|
+
|
260
|
+
doc.puts("|#{fn}|`#{svc.oneline_files[fn]}`")
|
261
|
+
end
|
262
|
+
unless svc.dependencies.empty?
|
263
|
+
doc.puts("|Dependencies\na|")
|
264
|
+
svc.dependencies.each do |dep|
|
265
|
+
doc.puts("- `#{dep}`")
|
266
|
+
end
|
267
|
+
doc.puts
|
268
|
+
end
|
269
|
+
unless svc.env.empty?
|
270
|
+
doc.puts("|Environment\na|")
|
271
|
+
svc.env.keys.sort.each do |var|
|
272
|
+
doc.puts("`#{var}`:: `#{svc.env[var]}`")
|
273
|
+
end
|
274
|
+
doc.puts
|
275
|
+
end
|
276
|
+
svc.multiline_files.keys.sort.each do |filename|
|
277
|
+
doc.puts("|#{filename.capitalize} script\nl|")
|
278
|
+
doc.puts(svc.multiline_files[filename])
|
279
|
+
doc.puts
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def write_parameter(doc, params)
|
284
|
+
params.each do |param|
|
285
|
+
pname = param['name'].first
|
286
|
+
pdefault = param['default'].first.gsub(/\\\n /, '')
|
287
|
+
default_string = if pdefault == '(empty)'
|
288
|
+
'not set'
|
289
|
+
else
|
290
|
+
"`#{pdefault}`"
|
291
|
+
end
|
292
|
+
|
293
|
+
value = @parameters[pname].gsub(/\\\n /, '')
|
294
|
+
val_string = if value == ''
|
295
|
+
'not set'
|
296
|
+
else
|
297
|
+
"`#{value}`"
|
298
|
+
end
|
299
|
+
|
300
|
+
doc.print("Parameter: #{pname}:: ")
|
301
|
+
if val_string == default_string
|
302
|
+
doc.puts("Value: #{val_string} _(default)_")
|
303
|
+
else
|
304
|
+
doc.puts("Value: #{val_string} _(default: #{default_string})_")
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def write_environment(doc, env_directives)
|
310
|
+
env_directives.each do |variables|
|
311
|
+
variables.each do |varname, value|
|
312
|
+
env_val = if value.first.empty?
|
313
|
+
'_(should not be set)_'
|
314
|
+
else
|
315
|
+
"`#{value.first}`"
|
316
|
+
end
|
317
|
+
doc.puts("Environment variable: #{varname}:: #{env_val}")
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def dependency_anchor_list(dep_directives)
|
323
|
+
anchors = dep_directives.map { |d| "<<#{d}>>" }
|
324
|
+
anchors.join(', ')
|
325
|
+
end
|
326
|
+
|
327
|
+
def write_dependencies(doc, dep_directives)
|
328
|
+
doc.puts("Dependencies:: #{dependency_anchor_list(dep_directives)}.")
|
329
|
+
end
|
330
|
+
|
331
|
+
def write_cfgfiles(doc, files)
|
332
|
+
doc.puts('.Configuration Files')
|
333
|
+
files.each do |file|
|
334
|
+
doc.puts("- `#{file}`")
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def write_build_dir(doc, build_dir)
|
339
|
+
doc.puts("Build Directory:: `#{build_dir.first}`")
|
340
|
+
end
|
341
|
+
|
342
|
+
def write_commands(doc, commands)
|
343
|
+
doc.puts('.Commands:')
|
344
|
+
doc.puts('[source,bash]')
|
345
|
+
doc.puts('----')
|
346
|
+
commands.each { |cmd| doc.puts(cmd) }
|
347
|
+
doc.puts('----')
|
348
|
+
end
|
349
|
+
|
350
|
+
def write_file_chunk(doc, file_collector, file_directive)
|
351
|
+
file = file_directive.first
|
352
|
+
filename = file['name'].first
|
353
|
+
if file_collector.key?(filename)
|
354
|
+
doc.puts(".File #{filename} (continued):")
|
355
|
+
file_collector[filename] = file_collector[filename] + file['content']
|
356
|
+
else
|
357
|
+
doc.puts(".File #{filename}:")
|
358
|
+
file_collector[filename] = file['content']
|
359
|
+
end
|
360
|
+
doc.puts('[source,indent=0]')
|
361
|
+
doc.puts('----')
|
362
|
+
doc.puts(file['content'])
|
363
|
+
doc.puts('----')
|
364
|
+
end
|
365
|
+
|
366
|
+
def render_files(doc, blueprint, files)
|
367
|
+
return if files.empty?
|
368
|
+
|
369
|
+
doc.puts("[[#{blueprint.name_with_phase}_files]]")
|
370
|
+
doc.puts("== Complete text of files\n\n")
|
371
|
+
files.keys.sort.each do |filename|
|
372
|
+
doc.puts("=== #{filename}\n\n")
|
373
|
+
doc.puts('[source,indent=0]')
|
374
|
+
doc.puts('----')
|
375
|
+
doc.puts(files[filename])
|
376
|
+
doc.puts('----')
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def summary_block_for(package)
|
381
|
+
proc do |doc, for_phase|
|
382
|
+
write_full_block = !package.phases? || !for_phase
|
383
|
+
next unless write_full_block || package.build_dir ||
|
384
|
+
package.directives['environment']
|
385
|
+
|
386
|
+
doc.puts("[%autowidth,cols=\"h,d\"]\n|===\n\n")
|
387
|
+
if write_full_block
|
388
|
+
doc.puts("|Name|#{package.full_name}\n|Version|#{package.version}")
|
389
|
+
write_url_rows(doc, package)
|
390
|
+
write_list_row(doc, 'Patches', package.patch_files) do |pf|
|
391
|
+
"- `#{pf}`"
|
392
|
+
end
|
393
|
+
write_list_row(doc, 'Built In-Tree', package.in_tree) do |pkg|
|
394
|
+
"- #{pkg[0]} #{pkg[1]}"
|
395
|
+
end
|
396
|
+
write_dependency_row(doc, package)
|
397
|
+
end
|
398
|
+
if !package.phases? || for_phase
|
399
|
+
write_environment_row(doc, package)
|
400
|
+
if package.build_dir
|
401
|
+
doc.puts("|Build Directory| `#{package.build_dir}`")
|
402
|
+
end
|
403
|
+
end
|
404
|
+
doc.puts("|===\n\n")
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def write_dependency_row(doc, blueprint)
|
409
|
+
dependencies = blueprint.deduped_dependency_names
|
410
|
+
return if dependencies.empty?
|
411
|
+
|
412
|
+
doc.puts("|Dependencies|#{dependency_anchor_list(dependencies)}")
|
413
|
+
end
|
414
|
+
|
415
|
+
def write_environment_row(doc, package)
|
416
|
+
return unless package['environment']
|
417
|
+
|
418
|
+
doc.puts('|Environment')
|
419
|
+
doc.puts('a|')
|
420
|
+
package['environment'].each do |env_hash|
|
421
|
+
env_hash.each do |var, value|
|
422
|
+
if value.first.empty?
|
423
|
+
doc.puts("- unset `#{var}`")
|
424
|
+
else
|
425
|
+
doc.puts("- `#{var}`: `#{value.first}`")
|
426
|
+
end
|
427
|
+
end
|
428
|
+
end
|
429
|
+
doc.puts
|
430
|
+
end
|
431
|
+
|
432
|
+
def write_list_row(doc, header, items)
|
433
|
+
return if items.nil? || items.empty?
|
434
|
+
|
435
|
+
doc.puts("|#{header}")
|
436
|
+
doc.puts('a|')
|
437
|
+
items.each { |pf| doc.puts(yield(pf)) }
|
438
|
+
doc.puts
|
439
|
+
end
|
440
|
+
|
441
|
+
def write_url_rows(doc, package)
|
442
|
+
write_url_row(doc, package.url('project'), 'Project URL')
|
443
|
+
write_url_row(doc, package.url('scm'), 'SCM URL')
|
444
|
+
write_url_row(doc, package.url('download'), 'Download URL')
|
445
|
+
end
|
446
|
+
|
447
|
+
def write_url_row(doc, url, header)
|
448
|
+
if url.match?(/\(.*\)/)
|
449
|
+
doc.puts("|#{header}|_#{url}_")
|
450
|
+
else
|
451
|
+
doc.puts("|#{header}|#{url}")
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
def write_stage_commands(doc, cmds, stage_name)
|
456
|
+
doc.puts(".#{stage_name} commands:")
|
457
|
+
doc.puts('[source,bash]')
|
458
|
+
doc.puts('----')
|
459
|
+
cmds.each { |cmd| doc.puts(cmd) }
|
460
|
+
doc.puts('----')
|
461
|
+
end
|
462
|
+
|
463
|
+
def write_patch_block(doc, package, patches_to_render)
|
464
|
+
doc.puts('.Patch:')
|
465
|
+
patches_to_render.each do |patch|
|
466
|
+
doc.puts("- #{package.name_and_version}-#{patch}.patch")
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def write_in_tree_block(doc, package, intree_to_render)
|
471
|
+
doc.puts('.In-Tree Sources:')
|
472
|
+
intree_to_render.each do |intree|
|
473
|
+
package, version, path = intree.split
|
474
|
+
doc.puts("- #{package} #{version} (at `#{path}`)")
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def write_blueprint_includes(doc, blueprints, section_name, files)
|
479
|
+
until blueprints.empty?
|
480
|
+
bp = blueprints.shift
|
481
|
+
bpfile = bp.sub('::', '-').tr(' ', '-')
|
482
|
+
rendered_bpfile = files.detect { |f| f =~ /[0-9]-#{bpfile}.adoc/ }
|
483
|
+
unless rendered_bpfile
|
484
|
+
msg = "specifies blueprint #{bp} but it has already been rendered"
|
485
|
+
raise(Litbuild::UnrenderedComponent, "Section #{section_name} #{msg}")
|
486
|
+
end
|
487
|
+
loop do
|
488
|
+
to_render = files.shift
|
489
|
+
doc.puts("include::./#{section_name}/#{to_render}[leveloffset=+1]")
|
490
|
+
doc.puts
|
491
|
+
break if to_render == rendered_bpfile
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
def write_package_list(doc, section)
|
497
|
+
packages = section.components.select { |c| c.is_a? Package }
|
498
|
+
sorted = packages.sort_by(&:name)
|
499
|
+
doc.puts('.Package Versions:')
|
500
|
+
sorted.each do |pkg|
|
501
|
+
doc.puts("- #{pkg.name} #{pkg.version}")
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# The include directives are a little bit complicated because in
|
506
|
+
# different cases we want to write them with different styles.
|
507
|
+
# - Usually we want to swallow the level-0 header in the included
|
508
|
+
# fragment, so that the document header becomes the level-0 header
|
509
|
+
# and the first section becomes a preamble. We do this by
|
510
|
+
# including the fragment with lines="3..-1" so that the first two
|
511
|
+
# lines are ignored.
|
512
|
+
# - When we are writing a multi-part book, we do that for the first
|
513
|
+
# part but include the other fragments without any other
|
514
|
+
# arguments, so their level-0 header stays a level-0 header and
|
515
|
+
# becomes the part header.
|
516
|
+
# - When we are writing a single-part document (article, not book),
|
517
|
+
# and we have multiple fragments, that means that the requested
|
518
|
+
# target has dependencies and those dependencies are being written
|
519
|
+
# before the requested target. In that case, the document header
|
520
|
+
# is still the level-0 header, but we don't want to ignore the
|
521
|
+
# level-0 header in the included fragments -- we increase
|
522
|
+
# leveloffset so they become level-1 headers, and so on.
|
523
|
+
# - For appendices, we always use a multi-part document style and
|
524
|
+
# include appendices as siblings to the parts. Since we want a
|
525
|
+
# level-0 header for the appendix itself but want other headers to
|
526
|
+
# start at level-3, we write a level-0 header into the top level
|
527
|
+
# document, swallow the level-0 header in the included fragment,
|
528
|
+
# and _also_ increase the header level in the reset of the
|
529
|
+
# fragment so we can skip the level-2 headers.
|
530
|
+
def write_toplevel_includes(location:, document:)
|
531
|
+
fragments = @written[location]
|
532
|
+
if fragments.size > 1 && other_parts.empty?
|
533
|
+
fragments.each do |fragment|
|
534
|
+
document.puts
|
535
|
+
document.puts("include::./#{fragment}[leveloffset=+1]")
|
536
|
+
end
|
537
|
+
else
|
538
|
+
main_section = fragments.first
|
539
|
+
document.puts
|
540
|
+
document.puts("include::./#{main_section}[lines=\"3..-1\"]")
|
541
|
+
other_parts.each do |part|
|
542
|
+
partfile = fragments.detect { |x| x =~ /^[0-9]+-#{part}.adoc$/ }
|
543
|
+
document.puts
|
544
|
+
document.puts("include::./#{partfile}[]")
|
545
|
+
end
|
546
|
+
appendices.each do |appendix|
|
547
|
+
appname = appendix.file_name
|
548
|
+
appfile = fragments.detect { |x| x =~ /^[0-9]+-#{appname}.adoc$/ }
|
549
|
+
document.puts("\n[appendix]")
|
550
|
+
document.puts("[[#{appendix.name},#{appendix.header_text}]]")
|
551
|
+
document.puts("= #{appendix.header_text}\n\n")
|
552
|
+
document.puts("include::./#{appfile}[leveloffset=+1,lines=\"3..-1\"]")
|
553
|
+
end
|
554
|
+
end
|
555
|
+
end
|
556
|
+
end
|
557
|
+
end
|