liquidoc 0.7.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/liquidoc/version.rb +1 -1
- data/lib/liquidoc.rb +191 -29
- metadata +2 -3
- data/lib/yaml_plus.rb +0 -60
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 680eeb03f5bdca5f09d010b37c2ba8a4fa2f7060
|
4
|
+
data.tar.gz: c5b06675ef4d7e2fcdcf5e0e1838295864edf094
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9658b656c88006e9fae3d16e35c00f12132f19a57fc963241650bc5a6b2849ce7efa4198f101c51f319f0b674936d624c29cd67ae5d292082cda773bebe5e77b
|
7
|
+
data.tar.gz: d63050fcd42fce26362c7cf9300a75f2a51213da2606e3cfb2fed84792b249c7b9a317da21120dc91194657124e11fdb1e9dc0a2378f0aa41f2046b2382d0f8c
|
data/lib/liquidoc/version.rb
CHANGED
data/lib/liquidoc.rb
CHANGED
@@ -9,6 +9,7 @@ require 'logger'
|
|
9
9
|
require 'csv'
|
10
10
|
require 'crack/xml'
|
11
11
|
require 'fileutils'
|
12
|
+
require 'jekyll'
|
12
13
|
|
13
14
|
# ===
|
14
15
|
# Table of Contents
|
@@ -32,6 +33,8 @@ require 'fileutils'
|
|
32
33
|
|
33
34
|
@base_dir_def = Dir.pwd + '/'
|
34
35
|
@base_dir = @base_dir_def
|
36
|
+
@build_dir_def = @base_dir + '_build'
|
37
|
+
@build_dir = @build_dir_def
|
35
38
|
@configs_dir = @base_dir + '_configs'
|
36
39
|
@templates_dir = @base_dir + '_templates/'
|
37
40
|
@data_dir = @base_dir + '_data/'
|
@@ -43,12 +46,20 @@ require 'fileutils'
|
|
43
46
|
@attributes = {}
|
44
47
|
@passed_attrs = {}
|
45
48
|
@verbose = false
|
49
|
+
@quiet = false
|
50
|
+
@explicit = false
|
46
51
|
|
52
|
+
# Instantiate the main Logger object, which is always running
|
47
53
|
@logger = Logger.new(STDOUT)
|
48
|
-
@logger.level = Logger::INFO
|
49
54
|
@logger.formatter = proc do |severity, datetime, progname, msg|
|
50
55
|
"#{severity}: #{msg}\n"
|
51
56
|
end
|
57
|
+
@logger.level = Logger::INFO # suppresses DEBUG-level messages
|
58
|
+
|
59
|
+
|
60
|
+
FileUtils::mkdir_p("#{@build_dir}") unless File.exists?("#{@build_dir}")
|
61
|
+
FileUtils::mkdir_p("#{@build_dir}/pre") unless File.exists?("#{@build_dir}/pre")
|
62
|
+
|
52
63
|
|
53
64
|
# ===
|
54
65
|
# Executive procs
|
@@ -77,6 +88,7 @@ def iterate_build cfg
|
|
77
88
|
for step in cfg.steps # iterate through each node in the 'config' object, which should start with an 'action' parameter
|
78
89
|
stepcount = stepcount + 1
|
79
90
|
step = BuildConfigStep.new(step) # create an instance of the Action class, validating the top-level step hash (now called 'step') in the process
|
91
|
+
@explainer.info step.message
|
80
92
|
type = step.type
|
81
93
|
case type # a switch to evaluate the 'action' parameter for each step in the iteration...
|
82
94
|
when "parse"
|
@@ -85,7 +97,8 @@ def iterate_build cfg
|
|
85
97
|
for bld in builds
|
86
98
|
build = Build.new(bld, type) # create an instance of the Build class; Build.new accepts a 'bld' hash & action 'type'
|
87
99
|
if build.template
|
88
|
-
|
100
|
+
@explainer.info build.message
|
101
|
+
liquify(data, build.template, build.output, build.variables) # perform the liquify operation
|
89
102
|
else
|
90
103
|
regurgidata(data, build.output)
|
91
104
|
end
|
@@ -96,12 +109,14 @@ def iterate_build cfg
|
|
96
109
|
copy_assets(step.source, step.target, inclusive)
|
97
110
|
when "render"
|
98
111
|
validate_file_input(step.source, "source") if step.source
|
99
|
-
doc = AsciiDocument.new(step.source)
|
100
|
-
attrs = ingest_attributes(step.data) if step.data # Set attributes in from YAML files
|
101
|
-
doc.add_attrs!(attrs) # Set attributes from the action-level data file
|
102
112
|
builds = step.builds
|
103
113
|
for bld in builds
|
114
|
+
doc = AsciiDocument.new(step.source)
|
115
|
+
attrs = ingest_attributes(step.data) if step.data # Set attributes from from YAML files
|
116
|
+
doc.add_attrs!(attrs) # Set attributes from the action-level data file
|
104
117
|
build = Build.new(bld, type) # create an instance of the Build class; Build.new accepts a 'bld' hash & action 'type' string
|
118
|
+
build.set("backend", derive_backend(doc.type, build.output) ) unless build.backend
|
119
|
+
@explainer.info build.message
|
105
120
|
render_doc(doc, build) # perform the render operation
|
106
121
|
end
|
107
122
|
when "deploy"
|
@@ -145,6 +160,27 @@ def validate_config_structure config
|
|
145
160
|
# TODO More validation needed
|
146
161
|
end
|
147
162
|
|
163
|
+
def explainer_init out=nil
|
164
|
+
unless @explainer
|
165
|
+
if out == "STDOUT"
|
166
|
+
@explainer = Logger.new(STDOUT)
|
167
|
+
else
|
168
|
+
out = "#{@build_dir}/pre/config-explainer.adoc" if out.nil?
|
169
|
+
File.open(out, 'w') unless File.exists?(out)
|
170
|
+
file = File.open(out, File::WRONLY)
|
171
|
+
begin
|
172
|
+
@explainer = Logger.new(file)
|
173
|
+
rescue Exception => ex
|
174
|
+
@logger.error ex
|
175
|
+
raise "ExplainerCreateError"
|
176
|
+
end
|
177
|
+
end
|
178
|
+
@explainer.formatter = proc do |severity, datetime, progname, msg|
|
179
|
+
"#{msg}\n"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
148
184
|
# ===
|
149
185
|
# Core classes
|
150
186
|
# ===
|
@@ -211,10 +247,59 @@ class BuildConfigStep
|
|
211
247
|
return @step['options']
|
212
248
|
end
|
213
249
|
|
250
|
+
def stage
|
251
|
+
return @step['stage']
|
252
|
+
end
|
253
|
+
|
214
254
|
def builds
|
215
255
|
return @step['builds']
|
216
256
|
end
|
217
257
|
|
258
|
+
def message
|
259
|
+
# dynamically build a human-friendly log message, possibly appending a reason
|
260
|
+
unless @step['message']
|
261
|
+
reason = ", #{@step['reason']}" if @step['reason']
|
262
|
+
noninclusively = ", without carrying the parent directory" if self.options.is_a?(Hash) && self.options['inclusive'] == false && File.directory?(self.source)
|
263
|
+
stage = "" ; stage = "[#{self.stage}] " if self.stage
|
264
|
+
case self.type
|
265
|
+
when "migrate"
|
266
|
+
text = ". #{stage}Copies `#{self.source}` to `#{self.target}`#{noninclusively}#{reason}."
|
267
|
+
when "parse"
|
268
|
+
if self.data.is_a? Array
|
269
|
+
if self.data.count > 1
|
270
|
+
text = ". Draws data from the following files:"
|
271
|
+
self.data.each do |file|
|
272
|
+
text.concat("\n * `#{file}`.")
|
273
|
+
end
|
274
|
+
text.concat("\n")
|
275
|
+
else
|
276
|
+
text = ". #{stage}Draws data from `#{self.data[0]}`"
|
277
|
+
end
|
278
|
+
else
|
279
|
+
text = ". #{stage}Draws data from `#{self.data['file']}`"
|
280
|
+
end
|
281
|
+
text.concat("#{reason},") if reason
|
282
|
+
text.concat(" and parses it as follows:")
|
283
|
+
return text
|
284
|
+
when "render"
|
285
|
+
if self.source
|
286
|
+
text = ". #{stage}Using the index file `#{self.source}` as a map#{reason}, and ingesting AsciiDoc attributes from "
|
287
|
+
if self.data.is_a? Array
|
288
|
+
text.concat("the following data files:")
|
289
|
+
self.data.each do |file|
|
290
|
+
text.concat("\n * `#{file}`.")
|
291
|
+
end
|
292
|
+
else
|
293
|
+
text.concat("`#{self.data}`")
|
294
|
+
end
|
295
|
+
return text
|
296
|
+
end
|
297
|
+
end
|
298
|
+
else
|
299
|
+
return @step['message']
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
218
303
|
def validate
|
219
304
|
case self.type
|
220
305
|
when "parse"
|
@@ -267,6 +352,58 @@ class Build
|
|
267
352
|
@build['props']
|
268
353
|
end
|
269
354
|
|
355
|
+
def variables
|
356
|
+
@build['variables']
|
357
|
+
end
|
358
|
+
|
359
|
+
def message
|
360
|
+
# dynamically build a message, possibly appending a reason
|
361
|
+
unless @build['message']
|
362
|
+
reason = ", #{@build['reason']}" if @build['reason']
|
363
|
+
case @type
|
364
|
+
when "parse"
|
365
|
+
text = ".. Builds `#{self.output}` pressed with the template `#{self.template}`#{reason}."
|
366
|
+
when "render"
|
367
|
+
case self.backend
|
368
|
+
when "pdf"
|
369
|
+
text = ".. Uses Asciidoctor/Prawn to generate a PDF file `#{self.output}`"
|
370
|
+
text.concat("#{reason}") if reason
|
371
|
+
text.concat(".")
|
372
|
+
when "html5"
|
373
|
+
text = ".. Compiles a standard Asciidoctor HTML5 file, `#{self.output}`"
|
374
|
+
text.concat("#{reason}") if reason
|
375
|
+
text.concat(".")
|
376
|
+
when "jekyll"
|
377
|
+
text = ".. Uses Jekyll config files:\n+\n--"
|
378
|
+
files = self.props['files']
|
379
|
+
if files.is_a? String
|
380
|
+
if files.include? ","
|
381
|
+
files = files.split(",")
|
382
|
+
else
|
383
|
+
files = files.split
|
384
|
+
end
|
385
|
+
else
|
386
|
+
unless files.is_a? Array
|
387
|
+
@logger.error "The Jekyll configuration file must be a single filename, a comma-separated list of filenames, or an array of filenames."
|
388
|
+
end
|
389
|
+
end
|
390
|
+
files.each do |file|
|
391
|
+
text.concat("\n * `#{file}`")
|
392
|
+
end
|
393
|
+
text.concat("\n\nto generate a static site")
|
394
|
+
if self.props && self.props['arguments']
|
395
|
+
text.concat(" at `#{self.props['arguments']['destination']}`")
|
396
|
+
end
|
397
|
+
text.concat("#{reason}") if reason
|
398
|
+
text.concat(".\n--\n")
|
399
|
+
end
|
400
|
+
return text
|
401
|
+
end
|
402
|
+
else
|
403
|
+
@build['message']
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
270
407
|
def prop_files_array
|
271
408
|
if props
|
272
409
|
if props['files']
|
@@ -333,7 +470,7 @@ class Build
|
|
333
470
|
end
|
334
471
|
end
|
335
472
|
|
336
|
-
end #class Build
|
473
|
+
end # class Build
|
337
474
|
|
338
475
|
class DataSrc
|
339
476
|
# initialization means establishing a proper hash for the 'data' param
|
@@ -355,8 +492,12 @@ class DataSrc
|
|
355
492
|
else
|
356
493
|
if datasrc.is_a? String
|
357
494
|
@datasrc['ext'] = File.extname(datasrc)
|
358
|
-
else
|
359
|
-
|
495
|
+
else
|
496
|
+
if datasrc.is_a? Array
|
497
|
+
|
498
|
+
else
|
499
|
+
raise "InvalidDataSource"
|
500
|
+
end
|
360
501
|
end
|
361
502
|
end
|
362
503
|
end
|
@@ -522,9 +663,13 @@ def parse_regex data_file, pattern
|
|
522
663
|
end
|
523
664
|
|
524
665
|
# Parse given data using given template, generating given output
|
525
|
-
def liquify datasrc, template_file, output
|
666
|
+
def liquify datasrc, template_file, output, variables=nil
|
526
667
|
data = get_data(datasrc)
|
527
668
|
validate_file_input(template_file, "template")
|
669
|
+
if variables
|
670
|
+
vars = { "vars" => variables }
|
671
|
+
data.merge!vars
|
672
|
+
end
|
528
673
|
begin
|
529
674
|
template = File.read(template_file) # reads the template file
|
530
675
|
template = Liquid::Template.parse(template) # compiles template
|
@@ -546,7 +691,7 @@ def liquify datasrc, template_file, output
|
|
546
691
|
raise "FileNotBuilt"
|
547
692
|
end
|
548
693
|
if File.exists?(output_file)
|
549
|
-
@logger.info "File built: #{
|
694
|
+
@logger.info "File built: #{output_file}"
|
550
695
|
else
|
551
696
|
@logger.error "Hrmp! File not built."
|
552
697
|
raise "FileNotBuilt"
|
@@ -638,7 +783,11 @@ def ingest_attributes attr_file
|
|
638
783
|
raise "AttributeBlockError"
|
639
784
|
end
|
640
785
|
begin
|
641
|
-
|
786
|
+
if new_attrs.is_a? Hash
|
787
|
+
attrs.merge!new_attrs
|
788
|
+
else
|
789
|
+
@logger.warn "The AsciiDoc attributes file #{filename} is not formatted as a hash, so its data was not ingested."
|
790
|
+
end
|
642
791
|
rescue Exception => ex
|
643
792
|
raise "AttributesMergeError #{ex.message}"
|
644
793
|
end
|
@@ -657,7 +806,6 @@ def derive_backend type, out_file
|
|
657
806
|
end
|
658
807
|
|
659
808
|
def render_doc doc, build
|
660
|
-
build.set("backend", derive_backend(doc.type, build.output) ) unless build.backend
|
661
809
|
case build.backend
|
662
810
|
when "html5", "pdf"
|
663
811
|
asciidocify(doc, build)
|
@@ -734,15 +882,15 @@ def generate_site doc, build
|
|
734
882
|
attrs.merge!(build.attributes) if build.attributes
|
735
883
|
attrs = {"asciidoctor" => {"attributes" => attrs} }
|
736
884
|
attrs_yaml = attrs.to_yaml # Convert it all back to Yaml, as we're going to write a file to feed back to Jekyll
|
737
|
-
|
738
|
-
|
739
|
-
build.add_config_file("build/pre/_attributes.yml")
|
885
|
+
File.open("#{@build_dir}/pre/_attributes.yml", 'w') { |file| file.write(attrs_yaml) }
|
886
|
+
build.add_config_file("#{@build_dir}/pre/_attributes.yml")
|
740
887
|
config_list = build.prop_files_array.join(',') # flatten the Array back down for the CLI
|
741
888
|
opts_args = ""
|
889
|
+
quiet = "--quiet" if @quiet || @explicit
|
742
890
|
if build.props['arguments']
|
743
891
|
opts_args = build.props['arguments'].to_opts_args
|
744
892
|
end
|
745
|
-
command = "bundle exec jekyll build --config #{config_list} #{opts_args}"
|
893
|
+
command = "bundle exec jekyll build --config #{config_list} #{opts_args} #{quiet}"
|
746
894
|
end
|
747
895
|
@logger.info "Running #{command}"
|
748
896
|
@logger.debug "AsciiDoc attributes: #{doc.attributes.to_yaml} "
|
@@ -839,6 +987,8 @@ end
|
|
839
987
|
|
840
988
|
# Extending Liquid filters/text manipulation
|
841
989
|
module CustomFilters
|
990
|
+
include Jekyll::Filters
|
991
|
+
|
842
992
|
def plainwrap input
|
843
993
|
input.wrap
|
844
994
|
end
|
@@ -875,7 +1025,7 @@ Liquid::Template.register_filter(CustomFilters)
|
|
875
1025
|
command_parser = OptionParser.new do|opts|
|
876
1026
|
opts.banner = "Usage: liquidoc [options]"
|
877
1027
|
|
878
|
-
opts.on("-a KEY=VALUE", "For passing an AsciiDoc attribute parameter to Asciidoctor. Ex: -a
|
1028
|
+
opts.on("-a KEY=VALUE", "For passing an AsciiDoc attribute parameter to Asciidoctor. Ex: -a imagesdir=some/path -a custom_var='my value'") do |n|
|
879
1029
|
pair = {}
|
880
1030
|
k,v = n.split('=')
|
881
1031
|
pair[k] = v
|
@@ -884,7 +1034,11 @@ command_parser = OptionParser.new do|opts|
|
|
884
1034
|
|
885
1035
|
# Global Options
|
886
1036
|
opts.on("-b PATH", "--base=PATH", "The base directory, relative to this script. Defaults to `.`, or pwd." ) do |n|
|
887
|
-
@
|
1037
|
+
@base_dir = n
|
1038
|
+
end
|
1039
|
+
|
1040
|
+
opts.on("-B PATH", "--build=PATH", "The directory under which LiquiDoc should save automatically preprocessed files. Defaults to #{@base_dir}_build. Can be absolute or relative to the base path (-b/--base=). Do NOT append '/' to the build path." ) do |n|
|
1041
|
+
@build_dir = n
|
888
1042
|
end
|
889
1043
|
|
890
1044
|
opts.on("-c", "--config=PATH", "Configuration file, enables preset source, template, and output.") do |n|
|
@@ -911,17 +1065,25 @@ command_parser = OptionParser.new do|opts|
|
|
911
1065
|
@template_file = @base_dir + n
|
912
1066
|
end
|
913
1067
|
|
914
|
-
opts.on("--verbose", "Run verbose") do |n|
|
1068
|
+
opts.on("--verbose", "Run verbose debug logging.") do |n|
|
915
1069
|
@logger.level = Logger::DEBUG
|
916
1070
|
@verbose = true
|
917
1071
|
end
|
918
1072
|
|
919
|
-
opts.on("--
|
920
|
-
@
|
1073
|
+
opts.on("--quiet", "Run with only WARN- and error-level logs written to console.") do |n|
|
1074
|
+
@logger.level = Logger::WARN
|
1075
|
+
@quiet = true
|
1076
|
+
end
|
1077
|
+
|
1078
|
+
opts.on("--explicit", "Log explicit step descriptions to console as build progresses. (Otherwise writes to file at #{@build_dir}/pre/config-explainer.adoc .)") do |n|
|
1079
|
+
explainer_init("STDOUT")
|
1080
|
+
@explainer.level = Logger::INFO
|
1081
|
+
@logger.level = Logger::WARN # Suppress all those INFO-level messages
|
1082
|
+
@explicit = true
|
921
1083
|
end
|
922
1084
|
|
923
|
-
opts.on("--
|
924
|
-
@
|
1085
|
+
opts.on("--stdout", "Puts the output in STDOUT instead of writing to a file.") do
|
1086
|
+
@output_type = "stdout"
|
925
1087
|
end
|
926
1088
|
|
927
1089
|
opts.on("--deploy", "EXPERIMENTAL: Trigger a jekyll serve operation against the destination dir of a Jekyll render step.") do
|
@@ -938,21 +1100,21 @@ end
|
|
938
1100
|
command_parser.parse!
|
939
1101
|
|
940
1102
|
# Upfront debug output
|
941
|
-
@logger.debug "Base dir: #{@base_dir}"
|
942
|
-
|
1103
|
+
@logger.debug "Base dir: #{@base_dir} (The path from which LiquiDoc CLI commands are relative.)"
|
1104
|
+
|
1105
|
+
explainer_init
|
943
1106
|
|
944
1107
|
# ===
|
945
1108
|
# Execute
|
946
1109
|
# ===
|
947
|
-
|
948
|
-
FileUtils.remove_dir(@clean_dir)
|
949
|
-
end
|
1110
|
+
|
950
1111
|
unless @config_file
|
1112
|
+
@logger.debug "Executing config-free build based on API/CLI arguments alone."
|
951
1113
|
if @data_file
|
952
1114
|
liquify(@data_file, @template_file, @output_file)
|
953
1115
|
end
|
954
1116
|
if @index_file
|
955
|
-
@logger.warn "
|
1117
|
+
@logger.warn "Rendering via command line arguments is not yet implemented. Use a config file."
|
956
1118
|
end
|
957
1119
|
else
|
958
1120
|
@logger.debug "Executing... config_build"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: liquidoc
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Dominick
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -51,7 +51,6 @@ files:
|
|
51
51
|
- bin/liquidoc
|
52
52
|
- lib/liquidoc.rb
|
53
53
|
- lib/liquidoc/version.rb
|
54
|
-
- lib/yaml_plus.rb
|
55
54
|
homepage: https://github.com/scalingdata/liquidoc
|
56
55
|
licenses:
|
57
56
|
- MIT
|
data/lib/yaml_plus.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
require 'safe_yaml/load'
|
2
|
-
module YamlPlus
|
3
|
-
|
4
|
-
def self.parse data
|
5
|
-
puts data
|
6
|
-
return
|
7
|
-
# self.new.load((::SafeYAML.load raw_data), theme_data)
|
8
|
-
# input_data ||= ::OpenStruct.new
|
9
|
-
# return input_data unless ::Hash === data
|
10
|
-
# data.inject(input_data) {|data, (key, val)| process_entry key, val, data }
|
11
|
-
# input_data.base_align ||= 'left'
|
12
|
-
# input_data
|
13
|
-
end
|
14
|
-
|
15
|
-
def process_entry key, val, data
|
16
|
-
if ::Hash === val
|
17
|
-
val.each do |key2, val2|
|
18
|
-
process_entry %(#{key}_#{key2.tr '-', '_'}), val2, data
|
19
|
-
end
|
20
|
-
else
|
21
|
-
data[key] = evaluate val, data
|
22
|
-
end
|
23
|
-
data
|
24
|
-
end
|
25
|
-
|
26
|
-
def evaluate expr, vars
|
27
|
-
case expr
|
28
|
-
when ::String
|
29
|
-
evaluate_math(expand_vars expr, vars)
|
30
|
-
when ::Array
|
31
|
-
expr.map {|e| evaluate e, vars }
|
32
|
-
else
|
33
|
-
expr
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def expand_vars expr, vars
|
38
|
-
if (idx = (expr.index '$'))
|
39
|
-
if idx == 0 && expr =~ LoneVariableRx
|
40
|
-
if vars.respond_to? $1
|
41
|
-
vars[$1]
|
42
|
-
else
|
43
|
-
warn %(asciidoctor: WARNING: unknown variable reference: $#{$1})
|
44
|
-
expr
|
45
|
-
end
|
46
|
-
else
|
47
|
-
expr.gsub(VariableRx) {
|
48
|
-
if vars.respond_to? $1
|
49
|
-
vars[$1]
|
50
|
-
else
|
51
|
-
warn %(asciidoctor: WARNING: unknown variable reference: $#{$1})
|
52
|
-
$&
|
53
|
-
end
|
54
|
-
}
|
55
|
-
end
|
56
|
-
else
|
57
|
-
expr
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|