liquidoc 0.12.0.pre.rc4 → 0.12.1.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/liquid/filters/date_filters.rb +107 -0
- data/lib/liquid/filters/grouping_filters.rb +65 -0
- data/lib/liquid/filters/jekyll.rb +488 -0
- data/lib/liquid/filters/url_filters.rb +63 -0
- data/lib/liquid/tags/highlight.rb +131 -0
- data/lib/liquid/tags/jekyll.rb +220 -0
- data/lib/liquid/tags/link.rb +37 -0
- data/lib/liquid/tags/post_url.rb +103 -0
- data/lib/liquidoc/version.rb +1 -1
- data/lib/liquidoc.rb +400 -108
- metadata +52 -16
data/lib/liquidoc.rb
CHANGED
@@ -9,9 +9,11 @@ require 'logger'
|
|
9
9
|
require 'csv'
|
10
10
|
require 'crack/xml'
|
11
11
|
require 'fileutils'
|
12
|
-
require 'jekyll'
|
13
12
|
require 'open3'
|
14
13
|
require 'highline'
|
14
|
+
require 'liquid/tags/jekyll'
|
15
|
+
require 'liquid/filters/jekyll'
|
16
|
+
require 'sterile'
|
15
17
|
|
16
18
|
# ===
|
17
19
|
# Table of Contents
|
@@ -25,7 +27,8 @@ require 'highline'
|
|
25
27
|
# 5a. parse procs def
|
26
28
|
# 5b. migrate procs def
|
27
29
|
# 5c. render procs def
|
28
|
-
#
|
30
|
+
# 5d. execute procs def
|
31
|
+
# 6. text manipulation filters
|
29
32
|
# 7. command/option parser def
|
30
33
|
# 8. executive proc calls
|
31
34
|
|
@@ -39,13 +42,14 @@ require 'highline'
|
|
39
42
|
@build_dir = @build_dir_def
|
40
43
|
@configs_dir = @base_dir + '_configs'
|
41
44
|
@templates_dir = @base_dir + '_templates/'
|
45
|
+
@includes_dirs_def = ['_templates','_templates/liquid','_templates/liquid/ops','_templates/ops','theme/_includes','theme/_layouts']
|
46
|
+
@includes_dirs = @includes_dirs_def
|
42
47
|
@data_dir = @base_dir + '_data/'
|
43
48
|
@data_files = nil
|
44
49
|
@attributes_file_def = '_data/asciidoctor.yml'
|
45
50
|
@attributes_file = @attributes_file_def
|
46
51
|
@pdf_theme_file = 'theme/pdf-theme.yml'
|
47
52
|
@fonts_dir = 'theme/fonts/'
|
48
|
-
@output_filename = 'index'
|
49
53
|
@attributes = {}
|
50
54
|
@passed_attrs = {}
|
51
55
|
@passed_vars = {}
|
@@ -80,7 +84,8 @@ FileUtils::mkdir_p("#{@build_dir}/pre") unless File.exists?("#{@build_dir}/pre")
|
|
80
84
|
def config_build config_file, config_vars={}, data_files=nil, parse=false
|
81
85
|
@logger.debug "Using config file #{config_file}."
|
82
86
|
validate_file_input(config_file, "config")
|
83
|
-
|
87
|
+
config_base = File.read(config_file)
|
88
|
+
if config_vars.length > 0 or data_files or parse or config_base.contains_liquid?
|
84
89
|
@logger.debug "Config_vars: #{config_vars.length}"
|
85
90
|
# If config variables are passed on the CLI, we want to parse the config file
|
86
91
|
# and use the parsed version for the rest fo this routine
|
@@ -94,8 +99,8 @@ def config_build config_file, config_vars={}, data_files=nil, parse=false
|
|
94
99
|
liquify(data_obj, config_file, config_out)
|
95
100
|
config_file = config_out
|
96
101
|
@logger.debug "Config parsed! Using #{config_out} for build."
|
97
|
-
validate_file_input(config_file, "config")
|
98
102
|
end
|
103
|
+
validate_file_input(config_file, "config")
|
99
104
|
begin
|
100
105
|
config = YAML.load_file(config_file)
|
101
106
|
rescue Exception => ex
|
@@ -106,6 +111,8 @@ def config_build config_file, config_vars={}, data_files=nil, parse=false
|
|
106
111
|
end
|
107
112
|
raise "ConfigFileError"
|
108
113
|
end
|
114
|
+
# TESTS
|
115
|
+
# puts config[0].argify
|
109
116
|
cfg = BuildConfig.new(config) # convert the config file to a new object called 'cfg'
|
110
117
|
if @safemode
|
111
118
|
commands = ""
|
@@ -146,6 +153,10 @@ def iterate_build cfg
|
|
146
153
|
# Prep & perform a Liquid-parsed build
|
147
154
|
@explainer.info build.message
|
148
155
|
build.add_data!(build.variables, "vars") if build.variables
|
156
|
+
includes_dirs = @includes_dirs
|
157
|
+
includes_dirs = build.includes_dirs if build.includes_dirs
|
158
|
+
includes_dirs.unshift(File.dirname(build.template))
|
159
|
+
build.add_data!(includes_dirs: includes_dirs)
|
149
160
|
liquify(build.data, build.template, build.output) # perform the liquify operation
|
150
161
|
else # Prep & perform a direct conversion
|
151
162
|
# Delete nested data and vars objects
|
@@ -153,7 +164,6 @@ def iterate_build cfg
|
|
153
164
|
build.data.remove_scope("vars")
|
154
165
|
# Add vars from CLI or config args
|
155
166
|
build.data.add_data!(build.variables) unless build.variables.empty?
|
156
|
-
build.data.add_data!(@passed_vars) unless @passed_vars.empty?
|
157
167
|
regurgidata(build.data, build.output)
|
158
168
|
end
|
159
169
|
end
|
@@ -170,8 +180,10 @@ def iterate_build cfg
|
|
170
180
|
builds = step.builds
|
171
181
|
for bld in builds
|
172
182
|
doc = AsciiDocument.new(step.source)
|
173
|
-
|
174
|
-
|
183
|
+
if step.data
|
184
|
+
attrs = ingest_attributes(step.data)
|
185
|
+
doc.add_attrs!(attrs) # Set attributes from the action-level data file
|
186
|
+
end
|
175
187
|
build = Build.new(bld, type) # create an instance of the Build class; Build.new accepts a 'bld' hash & action 'type' string
|
176
188
|
build.set("backend", derive_backend(doc.type, build.output) ) unless build.backend
|
177
189
|
@explainer.info build.message
|
@@ -210,31 +222,6 @@ def validate_file_input file, type
|
|
210
222
|
end
|
211
223
|
end
|
212
224
|
|
213
|
-
def validate_config_structure config
|
214
|
-
unless config.is_a? Array
|
215
|
-
message = "The configuration file is not properly structured."
|
216
|
-
@logger.error message
|
217
|
-
raise "ConfigStructError"
|
218
|
-
else
|
219
|
-
if (defined?(config['action'])).nil?
|
220
|
-
message = "Every listing in the configuration file needs an action type declaration."
|
221
|
-
@logger.error message
|
222
|
-
raise "ConfigStructError"
|
223
|
-
end
|
224
|
-
end
|
225
|
-
# TODO More validation needed
|
226
|
-
end
|
227
|
-
|
228
|
-
def contains_liquid filename
|
229
|
-
File.open(filename, "r") do |file_proc|
|
230
|
-
file_proc.each_line do |row|
|
231
|
-
if row.match(/.*\{\%.*\%\}.*|.*\{\{.*\}\}.*/)
|
232
|
-
return true
|
233
|
-
end
|
234
|
-
end
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
225
|
def explainer_init out=nil
|
239
226
|
unless @explainer
|
240
227
|
if out == "STDOUT"
|
@@ -285,12 +272,7 @@ class BuildConfig
|
|
285
272
|
if (defined?(config['compile'][0])) # The config is formatted for vesions < 0.3.0; convert it
|
286
273
|
config = deprecated_format(config)
|
287
274
|
end
|
288
|
-
|
289
|
-
# validations
|
290
|
-
unless config.is_a? Array
|
291
|
-
raise "ConfigStructError"
|
292
|
-
end
|
293
|
-
|
275
|
+
validate(config)
|
294
276
|
@cfg = config
|
295
277
|
end
|
296
278
|
|
@@ -307,6 +289,13 @@ class BuildConfig
|
|
307
289
|
return config['compile']
|
308
290
|
end
|
309
291
|
|
292
|
+
def validate config
|
293
|
+
unless config.is_a? Array
|
294
|
+
raise "ConfigStructError"
|
295
|
+
end
|
296
|
+
# TODO More validation needed
|
297
|
+
end
|
298
|
+
|
310
299
|
end #class BuildConfig
|
311
300
|
|
312
301
|
class BuildConfigStep
|
@@ -314,7 +303,7 @@ class BuildConfigStep
|
|
314
303
|
def initialize step
|
315
304
|
@step = step
|
316
305
|
if (defined?(@step['action'])).nil?
|
317
|
-
raise "
|
306
|
+
raise "StepStructError"
|
318
307
|
end
|
319
308
|
@step['options'] = nil unless defined?(step['options'])
|
320
309
|
validate()
|
@@ -415,7 +404,7 @@ class BuildConfigStep
|
|
415
404
|
for req in reqs
|
416
405
|
if (defined?(@step[req])).nil?
|
417
406
|
@logger.error "Every #{@step['action']}-type in the configuration file needs a '#{req}' declaration."
|
418
|
-
raise "
|
407
|
+
raise "ConfigStepError"
|
419
408
|
end
|
420
409
|
end
|
421
410
|
end
|
@@ -437,6 +426,10 @@ class Build
|
|
437
426
|
@build['template']
|
438
427
|
end
|
439
428
|
|
429
|
+
def includes_dirs
|
430
|
+
@build['includes_dirs']
|
431
|
+
end
|
432
|
+
|
440
433
|
def output
|
441
434
|
@build['output']
|
442
435
|
end
|
@@ -481,7 +474,7 @@ class Build
|
|
481
474
|
reason = ", #{@build['reason']}" if @build['reason']
|
482
475
|
case @type
|
483
476
|
when "parse"
|
484
|
-
text = ".. Builds `#{self.output}`
|
477
|
+
text = ".. Builds `#{self.output}` parsed with the template `#{self.template}`#{reason}."
|
485
478
|
when "render"
|
486
479
|
case self.backend
|
487
480
|
when "pdf"
|
@@ -646,14 +639,14 @@ class DataSrc
|
|
646
639
|
datatype = "yml"
|
647
640
|
end
|
648
641
|
else # If there's no 'type' defined, extract it from the filename and validate it
|
649
|
-
unless @datasrc['ext'].downcase.match(/\.yml|\.json|\.xml|\.csv/)
|
642
|
+
unless @datasrc['ext'].downcase.match(/\.yml|\.json|\.xml|\.csv|\.adoc/)
|
650
643
|
# @logger.error "Data file extension must be one of: .yml, .json, .xml, or .csv or else declared in config file."
|
651
644
|
raise "FileExtensionUnknown"
|
652
645
|
end
|
653
646
|
datatype = self.ext
|
654
647
|
datatype = datatype[1..-1] # removes leading dot char
|
655
648
|
end
|
656
|
-
unless datatype.downcase.match(/yml|json|xml|csv|regex/) # 'type' must be one of these permitted vals
|
649
|
+
unless datatype.downcase.match(/yml|json|xml|csv|regex|adoc/) # 'type' must be one of these permitted vals
|
657
650
|
# @logger.error "Declared data type must be one of: yaml, json, xml, csv, or regex."
|
658
651
|
raise "DataTypeUnrecognized"
|
659
652
|
end
|
@@ -834,6 +827,13 @@ def ingest_data datasrc
|
|
834
827
|
@logger.error "You must supply a regex pattern with your free-form data file."
|
835
828
|
raise "MissingRegexPattern"
|
836
829
|
end
|
830
|
+
when "adoc"
|
831
|
+
begin
|
832
|
+
doc = Asciidoctor.load_file(datasrc.file)
|
833
|
+
data = doc.attributes
|
834
|
+
rescue
|
835
|
+
@logger.error "Problem with AsciiDoc source file. Attributes not ingested."
|
836
|
+
end
|
837
837
|
end
|
838
838
|
return data
|
839
839
|
end
|
@@ -865,8 +865,10 @@ def parse_regex data_file, pattern
|
|
865
865
|
end
|
866
866
|
|
867
867
|
# Parse given data using given template, generating given output
|
868
|
-
def liquify data_obj, template_file, output
|
868
|
+
def liquify data_obj, template_file, output="stdout"
|
869
869
|
validate_file_input(template_file, "template")
|
870
|
+
# inject :includes_dirs as needed
|
871
|
+
data_obj.add_data!({'includes_dirs' => @includes_dirs}) unless data_obj.data['includes_dirs']
|
870
872
|
begin
|
871
873
|
template = File.read(template_file) # reads the template file
|
872
874
|
template = Liquid::Template.parse(template) # compiles template
|
@@ -877,17 +879,22 @@ def liquify data_obj, template_file, output
|
|
877
879
|
@logger.error message
|
878
880
|
raise message
|
879
881
|
end
|
880
|
-
unless output
|
882
|
+
unless output == "stdout" || @output_type == "stdout"
|
881
883
|
output_file = output
|
882
884
|
generate_file(rendered, output_file)
|
883
885
|
else # if stdout
|
884
|
-
puts
|
886
|
+
puts rendered
|
885
887
|
end
|
886
888
|
end
|
887
889
|
|
888
890
|
def cli_liquify data_files=nil, template_file=nil, output_file=nil, passed_vars
|
889
891
|
# converts command-line options into liquify or regurgidata inputs
|
890
892
|
data_obj = DataObj.new()
|
893
|
+
if output_file
|
894
|
+
output = output_file
|
895
|
+
else
|
896
|
+
output = "stdout"
|
897
|
+
end
|
891
898
|
if data_files
|
892
899
|
payload = get_payload(data_files)
|
893
900
|
data_obj.add_payload!(payload)
|
@@ -895,16 +902,16 @@ def cli_liquify data_files=nil, template_file=nil, output_file=nil, passed_vars
|
|
895
902
|
if template_file
|
896
903
|
# data_obj.add_data!(ingested, "data") if df
|
897
904
|
data_obj.add_data!(passed_vars, "vars") if passed_vars
|
898
|
-
liquify(data_obj, template_file,
|
905
|
+
liquify(data_obj, template_file, output)
|
899
906
|
else
|
900
907
|
data_obj.remove_scope("vars")
|
901
908
|
data_obj.add_data!(passed_vars) if passed_vars
|
902
|
-
regurgidata(data_obj,
|
909
|
+
regurgidata(data_obj, output)
|
903
910
|
end
|
904
911
|
end
|
905
912
|
|
906
913
|
def regurgidata data_obj, output
|
907
|
-
# converts data
|
914
|
+
# converts data object from one format directly to another
|
908
915
|
raise "UnrecognizedFileExtension" unless File.extname(output).match(/\.yml|\.json|\.xml|\.csv/)
|
909
916
|
case File.extname(output)
|
910
917
|
when ".yml"
|
@@ -919,7 +926,6 @@ def regurgidata data_obj, output
|
|
919
926
|
if new_data
|
920
927
|
begin
|
921
928
|
generate_file(new_data, output)
|
922
|
-
# File.open(output, 'w') { |file| file.write(new_data) }
|
923
929
|
@logger.info "Data converted and saved to #{output}."
|
924
930
|
rescue Exception => ex
|
925
931
|
@logger.error "#{ex.class}: #{ex.message}"
|
@@ -1140,7 +1146,7 @@ def jekyll_serve build
|
|
1140
1146
|
@logger.debug "Attempting Jekyll serve operation."
|
1141
1147
|
config_file = build.props['files'][0]
|
1142
1148
|
if build.props['arguments']
|
1143
|
-
opts_args = build.props['arguments'].
|
1149
|
+
opts_args = build.props['arguments'].argify
|
1144
1150
|
end
|
1145
1151
|
command = "bundle exec jekyll serve --config #{config_file} #{opts_args} --no-watch --skip-initial-build"
|
1146
1152
|
system command
|
@@ -1182,7 +1188,7 @@ def execute_command cmd
|
|
1182
1188
|
contents = stdout
|
1183
1189
|
if cmd.options['outfile']
|
1184
1190
|
contents = "#{cmd.options['outfile']['prepend']}\n#{stdout}" if cmd.options['outfile']['prepend']
|
1185
|
-
contents = "#{stdout}
|
1191
|
+
contents = "#{stdout}\n#{cmd.options['outfile']['append']}" if cmd.options['outfile']['append']
|
1186
1192
|
generate_file(contents, cmd.options['outfile']['path'])
|
1187
1193
|
end
|
1188
1194
|
if cmd.options['stdout']
|
@@ -1196,24 +1202,6 @@ end
|
|
1196
1202
|
# Text manipulation Classes, Modules, procs, etc
|
1197
1203
|
# ===
|
1198
1204
|
|
1199
|
-
module HashMash
|
1200
|
-
|
1201
|
-
def to_opts_args
|
1202
|
-
out = ''
|
1203
|
-
if self.is_a? Hash # TODO Should also be testing for flatness
|
1204
|
-
self.each do |opt,arg|
|
1205
|
-
out = out + " --#{opt} #{arg}"
|
1206
|
-
end
|
1207
|
-
end
|
1208
|
-
return out
|
1209
|
-
end
|
1210
|
-
|
1211
|
-
end
|
1212
|
-
|
1213
|
-
class Hash
|
1214
|
-
include HashMash
|
1215
|
-
end
|
1216
|
-
|
1217
1205
|
module ForceArray
|
1218
1206
|
# So we can accept a list string ("item1.yml,item2.yml") or a single item ("item1.yml")
|
1219
1207
|
# and convert to array as needed
|
@@ -1227,77 +1215,375 @@ module ForceArray
|
|
1227
1215
|
obj = Array.new.push(obj)
|
1228
1216
|
end
|
1229
1217
|
else
|
1230
|
-
|
1218
|
+
if obj.class == Hash
|
1219
|
+
obj = obj.to_array
|
1220
|
+
else
|
1221
|
+
raise "ForceArrayFail"
|
1222
|
+
end
|
1231
1223
|
end
|
1232
1224
|
end
|
1233
1225
|
return obj.to_ary
|
1234
1226
|
end
|
1235
1227
|
|
1228
|
+
def force_array!
|
1229
|
+
self.force_array
|
1230
|
+
end
|
1231
|
+
|
1236
1232
|
end
|
1237
1233
|
|
1238
1234
|
class String
|
1239
1235
|
include ForceArray
|
1240
|
-
# Adapted from Nikhil Gupta
|
1241
|
-
# http://nikhgupta.com/code/wrapping-long-lines-in-ruby-for-display-in-source-files/
|
1236
|
+
# Adapted from Nikhil Gupta
|
1237
|
+
# http://nikhgupta.com/code/wrapping-long-lines-in-ruby-for-display-in-source-files/
|
1242
1238
|
def wrap options = {}
|
1243
|
-
width = options.fetch(:width, 76)
|
1244
|
-
|
1239
|
+
width = options.fetch(:width, 76) # length to wrap at
|
1240
|
+
pre = options.fetch(:prepend, '') # text to prepend
|
1241
|
+
app = options.fetch(:append, '') # text to append
|
1242
|
+
chars = pre.size + app.size
|
1245
1243
|
self.strip.split("\n").collect do |line|
|
1246
|
-
line.length > width ? line.gsub(/(.{1,#{width}})(\s+|$)/, "\\1
|
1247
|
-
end.map(&:
|
1244
|
+
line.length + chars.size > width ? line.gsub(/(.{1,#{(width - chars)}})(\s+|$)/, "#{pre}\\1#{app}\n") : "#{pre}#{line}#{app}\n"
|
1245
|
+
end.map(&:rstrip).join("\n")
|
1248
1246
|
end
|
1249
1247
|
|
1250
1248
|
def indent options = {}
|
1251
|
-
|
1252
|
-
|
1249
|
+
# TODO: does not allow tabs; inserts explicit `\t` string
|
1250
|
+
syms = options.fetch(:sym, ' ') * options.fetch(:by, 2)
|
1251
|
+
self.gsub!(/^/m, "#{syms}")
|
1252
|
+
self.sub!("#{syms}", "") unless options.fetch(:line1, false)
|
1253
|
+
end
|
1254
|
+
|
1255
|
+
def contains_liquid?
|
1256
|
+
self.each_line do |row|
|
1257
|
+
if row.match(/.*\{\%.*\%\}.*|.*\{\{.*\}\}.*/)
|
1258
|
+
return true
|
1259
|
+
end
|
1260
|
+
end
|
1261
|
+
return false
|
1253
1262
|
end
|
1254
1263
|
|
1255
|
-
def
|
1256
|
-
|
1257
|
-
|
1258
|
-
|
1259
|
-
|
1264
|
+
def quote_wrap options = {}
|
1265
|
+
# When a string contains a certain pattern, wrap it in certain quotes
|
1266
|
+
# Pass '\s' as pattern to wrap any string that contains 1 or more spaces or tabs
|
1267
|
+
# pass '.' as pattern to always wrap.
|
1268
|
+
|
1269
|
+
pattern = options.fetch(:pattern, '\s').to_s
|
1270
|
+
return self unless self.strip.match(/\s/)
|
1271
|
+
quotes = options.fetch(:quotes, "single")
|
1272
|
+
case quotes
|
1273
|
+
when "single"
|
1274
|
+
wrap = "''"
|
1275
|
+
when "double"
|
1276
|
+
wrap = '""'
|
1277
|
+
when "backtick"
|
1278
|
+
wrap = "``"
|
1279
|
+
when "bracket"
|
1280
|
+
wrap = "[]"
|
1281
|
+
else
|
1282
|
+
wrap = quotes
|
1283
|
+
end
|
1284
|
+
quotes << wrap[0] unless wrap[1]
|
1285
|
+
return wrap[0] + self.strip + wrap[1]
|
1260
1286
|
end
|
1261
1287
|
|
1262
1288
|
end
|
1263
1289
|
|
1264
1290
|
class Array
|
1265
1291
|
include ForceArray
|
1292
|
+
|
1293
|
+
def to_hash
|
1294
|
+
struct = {}
|
1295
|
+
self.each do |p|
|
1296
|
+
struct.merge!p if p.is_a? Hash
|
1297
|
+
end
|
1298
|
+
return struct
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
# Get all unique values for each item in an array, or each unique value of a desigated
|
1302
|
+
# parameter in an array of hashes.
|
1303
|
+
#
|
1304
|
+
# @input : the object array
|
1305
|
+
# @property : (optional) parameter in which to select unique values (for hashes)
|
1306
|
+
def unique_property_values property=nil
|
1307
|
+
return self.uniq unless property
|
1308
|
+
new_ary = self.uniq { |i| i[property] }
|
1309
|
+
out = new_ary.map { |i| i[property] }.compact
|
1310
|
+
out
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
def concatenate_property_instances property=String
|
1314
|
+
# flattens the values of instances of a given property throughout an array of Hashes
|
1315
|
+
all_arrays = []
|
1316
|
+
self.each do |i|
|
1317
|
+
all_arrays << i[property]
|
1318
|
+
end
|
1319
|
+
return all_arrays.flatten
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
def repeated_property_values property=String
|
1323
|
+
# testing for uniqueness globally among all values in subarrays (list-formatted values) of all instances of the property across all nodes in the parent array
|
1324
|
+
# returns an array of duplicate items among all the tested arrays
|
1325
|
+
#
|
1326
|
+
# Example:
|
1327
|
+
# array_of_hashes[0]['cue'] = ['one','two','three']
|
1328
|
+
# array_of_hashes[1]['cue'] = ['three','four','five']
|
1329
|
+
# array_of_hashes.duplicate_property_values('cue')
|
1330
|
+
# #=> ['three']
|
1331
|
+
# Due to the apperance of 'three' in both instances of cue.
|
1332
|
+
firsts = []
|
1333
|
+
dupes = []
|
1334
|
+
self.each do |node|
|
1335
|
+
return ['non-array property value present'] unless node[property].is_a? Array
|
1336
|
+
node[property].each do |i|
|
1337
|
+
dupes << i if firsts.include? i
|
1338
|
+
firsts << i
|
1339
|
+
end
|
1340
|
+
end
|
1341
|
+
return dupes
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
class Hash
|
1347
|
+
include ForceArray
|
1348
|
+
|
1349
|
+
def to_array op=nil
|
1350
|
+
# Converts a hash of key-value pairs to a flat array based on the first tier
|
1351
|
+
out = []
|
1352
|
+
self.each do |k,v|
|
1353
|
+
v = "<RemovedObject>" if v.is_a? Enumerable and op == "flatten"
|
1354
|
+
out << {k => v}
|
1355
|
+
end
|
1356
|
+
return out
|
1357
|
+
end
|
1358
|
+
|
1359
|
+
def argify options = {}
|
1360
|
+
# Converts a hash of key-value pairs to command-line option/argument listings
|
1361
|
+
# Can be called with optional arguments:
|
1362
|
+
# template :: Liquid-formatted parsing template string
|
1363
|
+
# Accepts:
|
1364
|
+
#
|
1365
|
+
# 'hyph' :: -<key> <value>
|
1366
|
+
# 'hyphhyph' :: --<key> <value> (default)
|
1367
|
+
# 'hyphchar' :: -<k> <value>
|
1368
|
+
# 'dump' :: <key> <value>
|
1369
|
+
# 'paramequal' :: <key>=<value>
|
1370
|
+
# 'valonly' :: <value>
|
1371
|
+
# delim :: Delimiter -- any ASCII characters that separate the arguments
|
1372
|
+
#
|
1373
|
+
# For template-based usage, express the variables:
|
1374
|
+
# opt (the keyname) as {{opt}}
|
1375
|
+
# arg (the value) as {{arg}}
|
1376
|
+
# EXAMPLES (my_hash = {"key1"=>"val1", "key2"=>"val2"})
|
1377
|
+
# my_hash.argify #=> key1 val1 key2 val2
|
1378
|
+
# my_hash.argify('hyphhyph') #=> --key1 val1 --key2 val2
|
1379
|
+
# my_hash.argify('paramequal') #=> key1=val1 key2=val2
|
1380
|
+
# my_hash.argify('-a {{opt}}={{arg}}')#=> -a key1=val1 -a key2=val2
|
1381
|
+
# my_hash.argify('valonly', '||') #=> val1||val2
|
1382
|
+
# my_hash.argify("{{opt}} `{{arg}}`") #=> key1 `val1` key2 `val2`
|
1383
|
+
raise "InvalidObject" unless self.is_a? Hash
|
1384
|
+
template = options.fetch(:template, 'hyphhyph')
|
1385
|
+
if template.contains_liquid?
|
1386
|
+
tp = template # use the passed Liquid template
|
1387
|
+
else
|
1388
|
+
case template # use a preset Liquid template by name
|
1389
|
+
when "dump"
|
1390
|
+
tp = "{{opt}} {{arg | quote_wrap: 'single', '\s|,' }}"
|
1391
|
+
when "hyph"
|
1392
|
+
tp = "-{{opt}} {{arg | quote_wrap: 'single', '\s|,' }}"
|
1393
|
+
when "hyphhyph"
|
1394
|
+
tp = "--{{opt}} {{arg | quote_wrap: 'single', '\s|,' }}"
|
1395
|
+
when "paramequal"
|
1396
|
+
tp = "{{opt}}={{arg | quote_wrap: 'single', '\s|,' }}"
|
1397
|
+
when "valonly"
|
1398
|
+
tp = "{{arg | quote_wrap: 'single', '\s|,' }}"
|
1399
|
+
else
|
1400
|
+
return "Liquid: Unrecognized argify template name: #{template}"
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
begin
|
1404
|
+
tpl = Liquid::Template.parse(tp)
|
1405
|
+
first = true
|
1406
|
+
out = ''
|
1407
|
+
self.each do |k,v|
|
1408
|
+
# establish datasource
|
1409
|
+
v = "<Object>" if v.is_a? Hash
|
1410
|
+
v = v.join(',') if v.is_a? Array
|
1411
|
+
input = {"opt" => k.to_s, "arg" => v.to_s }
|
1412
|
+
if first
|
1413
|
+
dlm = ""
|
1414
|
+
first = false
|
1415
|
+
else
|
1416
|
+
dlm = options.fetch(:delim, ' ')
|
1417
|
+
end
|
1418
|
+
out += dlm + tpl.render(input)
|
1419
|
+
end
|
1420
|
+
rescue
|
1421
|
+
raise "Argify template processing failed"
|
1422
|
+
end
|
1423
|
+
return out
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
|
1266
1427
|
end
|
1267
1428
|
|
1268
1429
|
# Extending Liquid filters/text manipulation
|
1269
|
-
module
|
1430
|
+
module LiquiDocFilters
|
1270
1431
|
include Jekyll::Filters
|
1432
|
+
#
|
1433
|
+
# sterile-based filters
|
1434
|
+
#
|
1435
|
+
|
1436
|
+
def to_slug input, delim='-'
|
1437
|
+
o = input.dup
|
1438
|
+
opts = {:delimiter=>delim}
|
1439
|
+
o.to_slug(opts)
|
1440
|
+
end
|
1441
|
+
|
1442
|
+
def transliterate input
|
1443
|
+
o = input.dup
|
1444
|
+
o.transliterate
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
def smart_format input
|
1448
|
+
o = input.dup
|
1449
|
+
o.smart_format
|
1450
|
+
end
|
1451
|
+
|
1452
|
+
def encode_entities input
|
1453
|
+
o = input.dup
|
1454
|
+
o.encode_entities
|
1455
|
+
end
|
1271
1456
|
|
1272
|
-
def
|
1273
|
-
input.
|
1457
|
+
def titlecase input
|
1458
|
+
o = input.dup
|
1459
|
+
o.titlecase
|
1274
1460
|
end
|
1275
|
-
|
1276
|
-
|
1461
|
+
|
1462
|
+
def strip_tags input
|
1463
|
+
o = input.dup
|
1464
|
+
o.strip_tags
|
1465
|
+
end
|
1466
|
+
|
1467
|
+
def sterilize input
|
1468
|
+
o = input.dup
|
1469
|
+
o.sterilize
|
1277
1470
|
end
|
1278
|
-
|
1471
|
+
|
1472
|
+
#
|
1473
|
+
# Custom Filters
|
1474
|
+
#
|
1475
|
+
|
1476
|
+
def where_uniq input, property, value
|
1477
|
+
o = input.where(input, property, value)
|
1478
|
+
o[0] if o.size == 1
|
1479
|
+
"No result" unless o.size
|
1480
|
+
"Multiple results" if o.size > 1
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
def wrap input, width=80, prepend='', append='', vent=false
|
1484
|
+
input.wrap(:width => width, :prepend => prepend, :append => append)
|
1485
|
+
end
|
1486
|
+
|
1487
|
+
def plainwrap input, width=80
|
1488
|
+
input.wrap(:width => width)
|
1489
|
+
end
|
1490
|
+
|
1491
|
+
def commentwrap input, width=80, prepend='# '
|
1492
|
+
input.wrap(:width => width, :pre => prepend)
|
1493
|
+
end
|
1494
|
+
|
1495
|
+
def unwrap input, token1='&g59h%j1k;', token2='&ru8sf%df;'
|
1279
1496
|
if input
|
1280
|
-
|
1281
|
-
input.gsub(/\n\n/, token).gsub(/\n/, ' ').gsub(token, "\n\n")
|
1497
|
+
input.gsub(/(.)\n\n/, "\\1#{token1}").gsub(/([\."'])$\n([A-Z\(\_"'])/,"\\1#{token2}\\2").gsub(/\n/, '').gsub(token2,"\n").gsub(token1, "\n\n")
|
1282
1498
|
end
|
1283
1499
|
end
|
1284
1500
|
|
1285
|
-
def
|
1286
|
-
#
|
1287
|
-
|
1501
|
+
def indent_lines input, by=2, sym=' ', line1=false
|
1502
|
+
input.indent(:by => by, :sym => "#{sym}", :line1 => line1)
|
1503
|
+
end
|
1504
|
+
|
1505
|
+
def slugify input, delim='-', snip=false
|
1288
1506
|
s = input.to_s.downcase
|
1289
|
-
s.gsub!(/[^a-
|
1507
|
+
s.gsub!(/[^a-z0-9]/, delim)
|
1508
|
+
if snip
|
1509
|
+
while s.match("#{delim}#{delim}")
|
1510
|
+
s.gsub!("#{delim}#{delim}", "#{delim}")
|
1511
|
+
end
|
1512
|
+
s.gsub!(/^#{delim}+(.*)$/, "\\1")
|
1513
|
+
s.gsub!(/^(.*)#{delim}+$/, "\\1")
|
1514
|
+
end
|
1290
1515
|
s
|
1291
1516
|
end
|
1292
1517
|
|
1518
|
+
def asciidocify input
|
1519
|
+
Asciidoctor.convert(input, doctype: "inline")
|
1520
|
+
end
|
1521
|
+
|
1522
|
+
def quote_wrap input, quotes="''", pattern="\s"
|
1523
|
+
input.quote_wrap(:quotes => quotes, :pattern => pattern)
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
def to_cli_args input, template="paramequal", delim=" "
|
1527
|
+
input.argify(:template => template, :delim => delim)
|
1528
|
+
end
|
1529
|
+
|
1530
|
+
def hash_to_array input, op=nil
|
1531
|
+
o = input.dup
|
1532
|
+
o.to_array(op)
|
1533
|
+
end
|
1534
|
+
|
1535
|
+
def holds_liquid input
|
1536
|
+
o = false
|
1537
|
+
o = true if input.contains_liquid?
|
1538
|
+
o
|
1539
|
+
end
|
1540
|
+
|
1541
|
+
def store_list_uniq input, property=nil
|
1542
|
+
input.unique_property_values(property)
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
def store_list_concat input, property=String
|
1546
|
+
input.concatenate_property_instances(property)
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
def store_list_dupes input, property=String
|
1550
|
+
input.repeated_property_values(property)
|
1551
|
+
end
|
1552
|
+
|
1293
1553
|
def regexreplace input, regex, replacement=''
|
1554
|
+
# deprecated in favor of re_replace as of 0.12.0
|
1294
1555
|
input.to_s.gsub(Regexp.new(regex), replacement.to_s)
|
1295
1556
|
end
|
1296
1557
|
|
1558
|
+
def replace_regex input, regex, replacement='', multiline=true, global=true
|
1559
|
+
pattern = Regexp.new(regex, Regexp::MULTILINE) if multiline
|
1560
|
+
pattern = Regexp.new(regex) unless multiline
|
1561
|
+
o = input.to_s.gsub(pattern, replacement.to_s) if global
|
1562
|
+
o = input.to_s.sub(pattern, replacement.to_s) unless global
|
1563
|
+
o
|
1564
|
+
end
|
1565
|
+
|
1566
|
+
def match input, regex, multiline=true, global=true
|
1567
|
+
pattern = Regexp.new(regex, Regexp::MULTILINE) if multiline
|
1568
|
+
pattern = Regexp.new(regex) unless multiline
|
1569
|
+
return true if input.to_s.match(pattern)
|
1570
|
+
return false
|
1571
|
+
end
|
1572
|
+
|
1573
|
+
def to_yaml input
|
1574
|
+
o = input.to_yaml
|
1575
|
+
o = o.gsub(/^\-\-\-$\n/, "")
|
1576
|
+
o
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
def to_json input
|
1580
|
+
o = input.to_json
|
1581
|
+
o
|
1582
|
+
end
|
1297
1583
|
end
|
1298
1584
|
|
1299
|
-
#
|
1300
|
-
Liquid::Template.register_filter(
|
1585
|
+
# Register custom Liquid filters
|
1586
|
+
Liquid::Template.register_filter(LiquiDocFilters)
|
1301
1587
|
|
1302
1588
|
# ===
|
1303
1589
|
# Command/options parser
|
@@ -1317,15 +1603,15 @@ command_parser = OptionParser.new do|opts|
|
|
1317
1603
|
end
|
1318
1604
|
|
1319
1605
|
# Global Options
|
1320
|
-
opts.on("-b PATH", "--base
|
1606
|
+
opts.on("-b PATH", "--base PATH", "The base directory, relative to this script. Defaults to `.`, or pwd." ) do |n|
|
1321
1607
|
@base_dir = n
|
1322
1608
|
end
|
1323
1609
|
|
1324
|
-
opts.on("-B PATH", "--build
|
1610
|
+
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|
|
1325
1611
|
@build_dir = n
|
1326
1612
|
end
|
1327
1613
|
|
1328
|
-
opts.on("-c", "--config
|
1614
|
+
opts.on("-c", "--config PATH", "Configuration file, enables preset source, template, and output.") do |n|
|
1329
1615
|
@config_file = @base_dir + n
|
1330
1616
|
end
|
1331
1617
|
|
@@ -1335,22 +1621,28 @@ command_parser = OptionParser.new do|opts|
|
|
1335
1621
|
@data_files = DataFiles.new(data_files)
|
1336
1622
|
end
|
1337
1623
|
|
1338
|
-
opts.on("-f PATH", "--from
|
1624
|
+
opts.on("-f PATH", "--from PATH", "Directory to copy assets from." ) do |n|
|
1339
1625
|
@attributes_file = n
|
1340
1626
|
end
|
1341
1627
|
|
1342
|
-
opts.on("-i PATH", "--index
|
1628
|
+
opts.on("-i PATH", "--index PATH", "An AsciiDoc index file for mapping an Asciidoctor build." ) do |n|
|
1343
1629
|
@index_file = n
|
1344
1630
|
end
|
1345
1631
|
|
1346
1632
|
opts.on("-o PATH", "--output=PATH", "Output file path for generated content. Ex. path/to/file.adoc. Required unless --config is called.") do |n|
|
1347
|
-
@
|
1633
|
+
@output = @base_dir + n
|
1348
1634
|
end
|
1349
1635
|
|
1350
|
-
opts.on("-t PATH", "--template
|
1636
|
+
opts.on("-t PATH", "--template PATH", "Path to liquid template. Required unless --configuration is called." ) do |n|
|
1351
1637
|
@template_file = @base_dir + n
|
1352
1638
|
end
|
1353
1639
|
|
1640
|
+
opts.on("--includes_dirs PATH[,PATH]", "Paths to directories where includes (partials) can be found. Overrides default directories." ) do |n|
|
1641
|
+
n = n.force_array
|
1642
|
+
# n.map { |p| @base_dir + p }
|
1643
|
+
@includes_dirs = @includes_dirs.concat n
|
1644
|
+
end
|
1645
|
+
|
1354
1646
|
opts.on("--verbose", "Run verbose debug logging.") do |n|
|
1355
1647
|
@logger.level = Logger::DEBUG
|
1356
1648
|
@verbose = true
|
@@ -1369,7 +1661,7 @@ command_parser = OptionParser.new do|opts|
|
|
1369
1661
|
end
|
1370
1662
|
|
1371
1663
|
opts.on("--stdout", "Puts the output in STDOUT instead of writing to a file.") do
|
1372
|
-
@
|
1664
|
+
@output = "stdout"
|
1373
1665
|
end
|
1374
1666
|
|
1375
1667
|
opts.on("--deploy", "EXPERIMENTAL: Trigger a jekyll serve operation against the destination dir of a Jekyll render step.") do
|
@@ -1425,7 +1717,7 @@ explainer_init
|
|
1425
1717
|
unless @config_file
|
1426
1718
|
@logger.debug "Executing config-free build based on API/CLI arguments alone."
|
1427
1719
|
if @data_files
|
1428
|
-
cli_liquify(@data_files, @template_file, @
|
1720
|
+
cli_liquify(@data_files, @template_file, @output, @passed_vars)
|
1429
1721
|
end
|
1430
1722
|
if @index_file
|
1431
1723
|
@logger.warn "Rendering via command line arguments is not yet implemented. Use a config file."
|