liquidoc 0.12.0.pre.rc2 → 0.12.0.pre.rc6

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
@@ -588,7 +582,7 @@ class Build
588
582
  when "render"
589
583
  reqs = ["output"]
590
584
  end
591
- for req in required
585
+ for req in reqs
592
586
  if (defined?(req)).nil?
593
587
  raise "ActionSettingMissing"
594
588
  end
@@ -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)
@@ -1062,6 +1071,7 @@ def asciidocify doc, build
1062
1071
  # Perform the aciidoctor convert
1063
1072
  if build.backend == "pdf"
1064
1073
  @logger.info "Generating PDF. This can take some time..."
1074
+ attrs.merge!({"pdf-theme"=>build.style}) if build.style
1065
1075
  end
1066
1076
  Asciidoctor.convert_file(
1067
1077
  doc.index,
@@ -1073,7 +1083,7 @@ def asciidocify doc, build
1073
1083
  safe: "unsafe",
1074
1084
  sourcemap: true,
1075
1085
  verbose: @verbose,
1076
- mkdirs: true
1086
+ mkdirs: true,
1077
1087
  )
1078
1088
  @logger.info "Rendered file #{to_file}."
1079
1089
  end
@@ -1089,18 +1099,19 @@ def generate_site doc, build
1089
1099
  attrs.merge!(build.attributes) if build.attributes
1090
1100
  attrs = {"asciidoctor" => {"attributes" => attrs} }
1091
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
1092
- File.open("#{@build_dir}/pre/_attributes.yml", 'w') { |file| file.write(attrs_yaml) }
1093
- 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")
1094
1104
  config_list = build.prop_files_array.join(',') # flatten the Array back down for the CLI
1095
1105
  quiet = "--quiet" if @quiet || @explicit
1096
1106
  if build.props['arguments']
1097
- opts_args_file = "#{@build_dir}/pre/jekyll_opts_args.yml"
1107
+ opts_args_file = "#{@build_dir}/pre/jekyll_opts_args_#{@render_count}.yml"
1098
1108
  opts_args = build.props['arguments']
1099
1109
  File.open(opts_args_file, 'w') { |file|
1100
1110
  file.write(opts_args.to_yaml)}
1101
1111
  config_list << ",#{opts_args_file}"
1102
1112
  end
1103
1113
  base_args = "--config #{config_list}"
1114
+ base_args += " --trace" if @verbose
1104
1115
  command = "bundle exec jekyll build #{base_args} #{quiet}"
1105
1116
  if @search_index
1106
1117
  # TODO enable config-based admin api key ingest once config is dynamic
@@ -1134,7 +1145,7 @@ def jekyll_serve build
1134
1145
  @logger.debug "Attempting Jekyll serve operation."
1135
1146
  config_file = build.props['files'][0]
1136
1147
  if build.props['arguments']
1137
- opts_args = build.props['arguments'].to_opts_args
1148
+ opts_args = build.props['arguments'].argify
1138
1149
  end
1139
1150
  command = "bundle exec jekyll serve --config #{config_file} #{opts_args} --no-watch --skip-initial-build"
1140
1151
  system command
@@ -1176,7 +1187,7 @@ def execute_command cmd
1176
1187
  contents = stdout
1177
1188
  if cmd.options['outfile']
1178
1189
  contents = "#{cmd.options['outfile']['prepend']}\n#{stdout}" if cmd.options['outfile']['prepend']
1179
- 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']
1180
1191
  generate_file(contents, cmd.options['outfile']['path'])
1181
1192
  end
1182
1193
  if cmd.options['stdout']
@@ -1190,24 +1201,6 @@ end
1190
1201
  # Text manipulation Classes, Modules, procs, etc
1191
1202
  # ===
1192
1203
 
1193
- module HashMash
1194
-
1195
- def to_opts_args
1196
- out = ''
1197
- if self.is_a? Hash # TODO Should also be testing for flatness
1198
- self.each do |opt,arg|
1199
- out = out + " --#{opt} #{arg}"
1200
- end
1201
- end
1202
- return out
1203
- end
1204
-
1205
- end
1206
-
1207
- class Hash
1208
- include HashMash
1209
- end
1210
-
1211
1204
  module ForceArray
1212
1205
  # So we can accept a list string ("item1.yml,item2.yml") or a single item ("item1.yml")
1213
1206
  # and convert to array as needed
@@ -1221,77 +1214,375 @@ module ForceArray
1221
1214
  obj = Array.new.push(obj)
1222
1215
  end
1223
1216
  else
1224
- raise "ForceArrayFail"
1217
+ if obj.class == Hash
1218
+ obj = obj.to_array
1219
+ else
1220
+ raise "ForceArrayFail"
1221
+ end
1225
1222
  end
1226
1223
  end
1227
1224
  return obj.to_ary
1228
1225
  end
1229
1226
 
1227
+ def force_array!
1228
+ self.force_array
1229
+ end
1230
+
1230
1231
  end
1231
1232
 
1232
1233
  class String
1233
1234
  include ForceArray
1234
- # Adapted from Nikhil Gupta
1235
- # 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/
1236
1237
  def wrap options = {}
1237
- width = options.fetch(:width, 76)
1238
- 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
1239
1242
  self.strip.split("\n").collect do |line|
1240
- line.length > width ? line.gsub(/(.{1,#{width}})(\s+|$)/, "\\1\n#{commentchar}") : line
1241
- 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")
1242
1245
  end
1243
1246
 
1244
1247
  def indent options = {}
1245
- spaces = " " * options.fetch(:spaces, 4)
1246
- 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)
1247
1252
  end
1248
1253
 
1249
- def indent_with_wrap options = {}
1250
- spaces = options.fetch(:spaces, 4)
1251
- width = options.fetch(:width, 80)
1252
- width = width > spaces ? width - spaces : 1
1253
- self.wrap(width: width).indent(spaces: spaces)
1254
+ def contains_liquid?
1255
+ self.each_line do |row|
1256
+ if row.match(/.*\{\%.*\%\}.*|.*\{\{.*\}\}.*/)
1257
+ return true
1258
+ end
1259
+ end
1260
+ return false
1261
+ end
1262
+
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]
1254
1285
  end
1255
1286
 
1256
1287
  end
1257
1288
 
1258
1289
  class Array
1259
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
+
1260
1426
  end
1261
1427
 
1262
1428
  # Extending Liquid filters/text manipulation
1263
- module CustomFilters
1429
+ module LiquiDocFilters
1264
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
1265
1455
 
1266
- def plainwrap input
1267
- input.wrap
1456
+ def titlecase input
1457
+ o = input.dup
1458
+ o.titlecase
1268
1459
  end
1269
- def commentwrap input
1270
- 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
1271
1469
  end
1272
- 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;'
1273
1495
  if input
1274
- token = "[g59hj1k]"
1275
- 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")
1276
1497
  end
1277
1498
  end
1278
1499
 
1279
- def slugify input
1280
- # Downcase
1281
- # 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
1282
1505
  s = input.to_s.downcase
1283
- 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
1284
1514
  s
1285
1515
  end
1286
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
+
1287
1552
  def regexreplace input, regex, replacement=''
1553
+ # deprecated in favor of re_replace as of 0.12.0
1288
1554
  input.to_s.gsub(Regexp.new(regex), replacement.to_s)
1289
1555
  end
1290
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
1291
1582
  end
1292
1583
 
1293
- # register custom Liquid filters
1294
- Liquid::Template.register_filter(CustomFilters)
1584
+ # Register custom Liquid filters
1585
+ Liquid::Template.register_filter(LiquiDocFilters)
1295
1586
 
1296
1587
  # ===
1297
1588
  # Command/options parser
@@ -1311,15 +1602,15 @@ command_parser = OptionParser.new do|opts|
1311
1602
  end
1312
1603
 
1313
1604
  # Global Options
1314
- 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|
1315
1606
  @base_dir = n
1316
1607
  end
1317
1608
 
1318
- 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|
1319
1610
  @build_dir = n
1320
1611
  end
1321
1612
 
1322
- 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|
1323
1614
  @config_file = @base_dir + n
1324
1615
  end
1325
1616
 
@@ -1329,22 +1620,28 @@ command_parser = OptionParser.new do|opts|
1329
1620
  @data_files = DataFiles.new(data_files)
1330
1621
  end
1331
1622
 
1332
- 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|
1333
1624
  @attributes_file = n
1334
1625
  end
1335
1626
 
1336
- 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|
1337
1628
  @index_file = n
1338
1629
  end
1339
1630
 
1340
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|
1341
- @output_file = @base_dir + n
1632
+ @output = @base_dir + n
1342
1633
  end
1343
1634
 
1344
- 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|
1345
1636
  @template_file = @base_dir + n
1346
1637
  end
1347
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
+
1348
1645
  opts.on("--verbose", "Run verbose debug logging.") do |n|
1349
1646
  @logger.level = Logger::DEBUG
1350
1647
  @verbose = true
@@ -1363,7 +1660,7 @@ command_parser = OptionParser.new do|opts|
1363
1660
  end
1364
1661
 
1365
1662
  opts.on("--stdout", "Puts the output in STDOUT instead of writing to a file.") do
1366
- @output_type = "stdout"
1663
+ @output = "stdout"
1367
1664
  end
1368
1665
 
1369
1666
  opts.on("--deploy", "EXPERIMENTAL: Trigger a jekyll serve operation against the destination dir of a Jekyll render step.") do
@@ -1419,7 +1716,7 @@ explainer_init
1419
1716
  unless @config_file
1420
1717
  @logger.debug "Executing config-free build based on API/CLI arguments alone."
1421
1718
  if @data_files
1422
- cli_liquify(@data_files, @template_file, @output_file, @passed_vars)
1719
+ cli_liquify(@data_files, @template_file, @output, @passed_vars)
1423
1720
  end
1424
1721
  if @index_file
1425
1722
  @logger.warn "Rendering via command line arguments is not yet implemented. Use a config file."