liquidoc 0.12.0.pre.rc3 → 0.12.0

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