liquidoc 0.12.0.pre.rc3 → 0.12.0

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.
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."