liquidoc 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/liquidoc.rb +311 -133
  3. data/lib/liquidoc/version.rb +1 -1
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 84d0dce2c5baddc6b4b2c9d7a9dffe763ce32a73
4
- data.tar.gz: 65d945dcfdb8480a72aa72650adf4e509e7834ed
3
+ metadata.gz: 9adb50baa6b3a7c4f31b2de0cc7888bdb69e798f
4
+ data.tar.gz: '0392150cfd9c8a5b2ea35ac16116e1ae519bd874'
5
5
  SHA512:
6
- metadata.gz: d8e16dd3ae8c7851c1e7f24d0b5a684019d9b7f8e3b93efb8b8713b243e7692ef53150db619e34dc3247f4861e9dae335152aad51f1b754795cb95e59a1cfde8
7
- data.tar.gz: a72ffb5946651195885fe70e25739c2476939fd9ebdfd637d96ef56896332fcde81b6cce7555ff311ef79c05890580f1940bfd2c264067fe5a6ae304e9b60d0a
6
+ metadata.gz: 2762bc4215573e07f60bcc88d91c2fd2b62dc9995be94ac29b89fd96ec9f214d4135f6f7d66239b99acf3ee4fff57b21d3a599e54d14298073cd287ac4b96a95
7
+ data.tar.gz: b0c252cbc74af8f56ffdd03718713a7a193d9da94caa110cc68abd5a7f58e917ea611031b939aaca8a456a370f8da3a3fc13e770605279a60dbad22fff681c84
@@ -8,15 +8,32 @@ require 'logger'
8
8
  require 'csv'
9
9
  require 'crack/xml'
10
10
 
11
+ # ===
12
+ # Table of Contents
13
+ # ===
14
+ #
15
+ # 1. dependencies stack
16
+ # 2. default settings
17
+ # 3. general methods
18
+ # 4. object classes
19
+ # 5. action-specific methods
20
+ # 5a. parse methods
21
+ # 5b. migrate methods
22
+ # 5c. render methods
23
+ # 6. text manipulation
24
+ # 7. command/option parser
25
+ # 8. executive method calls
26
+
27
+ # ===
11
28
  # Default settings
29
+ # ===
30
+
12
31
  @base_dir_def = Dir.pwd + '/'
13
32
  @base_dir = @base_dir_def
14
33
  @configs_dir = @base_dir + '_configs'
15
34
  @templates_dir = @base_dir + '_templates/'
16
35
  @data_dir = @base_dir + '_data/'
17
36
  @output_dir = @base_dir + '_output/'
18
- @config_file_def = @base_dir + '_configs/cfg-sample.yml'
19
- @config_file = @config_file_def
20
37
  @attributes_file_def = '_data/asciidoctor.yml'
21
38
  @attributes_file = @attributes_file_def
22
39
  @pdf_theme_file = 'theme/pdf-theme.yml'
@@ -31,73 +48,10 @@ require 'crack/xml'
31
48
  end
32
49
 
33
50
  # ===
34
- # General methods
51
+ # Executive methods
35
52
  # ===
36
53
 
37
- # Pull in a semi-structured data file, converting contents to a Ruby hash
38
- def get_data data
39
- # data must be a hash produced by data_hashify()
40
- if data['type']
41
- if data['type'].downcase == "yaml"
42
- data['type'] = "yml"
43
- end
44
- unless data['type'].downcase.match(/yml|json|xml|csv|regex/)
45
- @logger.error "Declared data type must be one of: yaml, json, xml, csv, or regex."
46
- raise "DataTypeUnrecognized"
47
- end
48
- else
49
- unless data['ext'].match(/\.yml|\.json|\.xml|\.csv/)
50
- @logger.error "Data file extension must be one of: .yml, .json, .xml, or .csv or else declared in config file."
51
- raise "FileExtensionUnknown (#{data[ext]})"
52
- end
53
- data['type'] = data['ext']
54
- data['type'].slice!(0) # removes leading dot char
55
- end
56
- case data['type']
57
- when "yml"
58
- begin
59
- return YAML.load_file(data['file'])
60
- rescue Exception => ex
61
- @logger.error "There was a problem with the data file. #{ex.message}"
62
- end
63
- when "json"
64
- begin
65
- return JSON.parse(File.read(data['file']))
66
- rescue Exception => ex
67
- @logger.error "There was a problem with the data file. #{ex.message}"
68
- end
69
- when "xml"
70
- begin
71
- data = Crack::XML.parse(File.read(data['file']))
72
- return data['root']
73
- rescue Exception => ex
74
- @logger.error "There was a problem with the data file. #{ex.message}"
75
- end
76
- when "csv"
77
- output = []
78
- i = 0
79
- begin
80
- CSV.foreach(data['file'], headers: true, skip_blanks: true) do |row|
81
- output[i] = row.to_hash
82
- i = i+1
83
- end
84
- output = {"data" => output}
85
- return output
86
- rescue
87
- @logger.error "The CSV format is invalid."
88
- end
89
- when "regex"
90
- if data['pattern']
91
- return parse_regex(data['file'], data['pattern'])
92
- else
93
- @logger.error "You must supply a regex pattern with your free-form data file."
94
- raise "MissingRegexPattern"
95
- end
96
- end
97
- end
98
-
99
54
  # Establish source, template, index, etc details for build jobs from a config file
100
- # TODO This needs to be turned into a Class?
101
55
  def config_build config_file
102
56
  @logger.debug "Using config file #{config_file}."
103
57
  validate_file_input(config_file, "config")
@@ -105,40 +59,38 @@ def config_build config_file
105
59
  config = YAML.load_file(config_file)
106
60
  rescue
107
61
  unless File.exists?(config_file)
108
- @logger.error "Config file not found."
62
+ @logger.error "Config file #{config_file} not found."
109
63
  else
110
- @logger.error "Problem loading config file. Exiting."
111
- end
112
- raise "Could not load #{config_file}"
113
- end
114
- validate_config_structure(config)
115
- if config['compile']
116
- for src in config['compile']
117
- data = src['data']
118
- for cfgn in src['builds']
119
- template = @base_dir + cfgn['template']
120
- unless cfgn['output'].downcase == "stdout"
121
- output = @base_dir + cfgn['output']
122
- else
123
- output = "stdout"
124
- end
125
- liquify(data, template, output)
126
- end
64
+ @logger.error "Problem loading config file #{config_file}. Exiting."
127
65
  end
66
+ raise "ConfigFileError"
128
67
  end
129
- if config['publish']
130
- begin
131
- for pub in config['publish']
132
- for bld in pub['builds']
133
- if bld['publish']
134
- publish(pub, bld)
135
- else
136
- @logger.warn "Publish build for '#{index}' backend '#{backend}' disabled."
137
- end
138
- end
68
+ cfg = BuildConfig.new(config) # convert the config file to a new object called 'cfg'
69
+ iterate_build(cfg)
70
+ end
71
+
72
+ def iterate_build cfg
73
+ stepcount = 0
74
+ for step in cfg.steps # iterate through each node in the 'config' object, which should start with an 'action' parameter
75
+ stepcount = stepcount + 1
76
+ step = BuildConfigStep.new(step) # create an instance of the Action class, validating the top-level step hash (now called 'step') in the process
77
+ type = step.type
78
+ case type # a switch to evaluate the 'action' parameter for each step in the iteration...
79
+ when "parse"
80
+ data = DataSrc.new(step.data)
81
+ builds = step.builds
82
+ for bld in builds
83
+ build = Build.new(bld, type) # create an instance of the Build class; Build.new accepts a 'bld' hash & action 'type'
84
+ liquify(data, build.template, build.output) # perform the liquify operation
139
85
  end
140
- rescue Exception => ex
141
- @logger.error "Error during publish action. #{ex}"
86
+ when "migrate"
87
+ @logger.warn "Migrate actions not yet implemented."
88
+ when "render"
89
+ @logger.warn "Render actions not yet implemented."
90
+ when "deploy"
91
+ @logger.warn "Deploy actions not yet implemented."
92
+ else
93
+ @logger.warn "The action `#{type}` is not valid."
142
94
  end
143
95
  end
144
96
  end
@@ -154,38 +106,258 @@ def validate_file_input file, type
154
106
  error = "The #{type} file (#{file}) was not found."
155
107
  end
156
108
  end
157
- unless error
158
- @logger.debug "Input file validated for #{type} file #{file}."
159
- else
109
+ if error
160
110
  @logger.error "Could not validate input file: #{error}"
161
111
  raise "InvalidInput"
162
112
  end
163
113
  end
164
114
 
165
115
  def validate_config_structure config
166
- unless config.is_a? Hash
167
- message = "The configuration file is not properly structured; it is not a hash"
116
+ unless config.is_a? Array
117
+ message = "The configuration file is not properly structured."
168
118
  @logger.error message
169
- raise message
119
+ raise "ConfigStructError"
170
120
  else
171
- unless config['publish'] or config['compile']
172
- raise "Config file must have at least one top-level section named 'publish:' or 'compile:'."
121
+ if (defined?(config['action'])).nil?
122
+ message = "Every listing in the configuration file needs an action type declaration."
123
+ @logger.error message
124
+ raise "ConfigStructError"
173
125
  end
174
126
  end
175
127
  # TODO More validation needed
176
128
  end
177
129
 
178
- def data_hashify data_var
179
- # TODO make datasource config a class
180
- if data_var.is_a?(String)
181
- data = {}
182
- data['file'] = data_var
183
- data['ext'] = File.extname(data_var)
184
- else # add ext to the hash
185
- data = data_var
186
- data['ext'] = File.extname(data['file'])
187
- end
188
- return data
130
+ # ===
131
+ # Core classes
132
+ # ===
133
+
134
+ # For now BuildConfig is mostly to objectify the primary build 'action' steps
135
+ class BuildConfig
136
+
137
+ def initialize config
138
+
139
+ if (defined?(config['compile'][0])) # The config is formatted for vesions < 0.3.0; convert it
140
+ config = deprecated_format(config)
141
+ end
142
+
143
+ # validations
144
+ unless config.is_a? Array
145
+ raise "ConfigStructError"
146
+ end
147
+
148
+ @@cfg = config
149
+ end
150
+
151
+ def steps
152
+ @@cfg
153
+ end
154
+
155
+ def deprecated_format config # for backward compatibility with 0.1.0 and 0.2.0
156
+ puts "You are using a deprecated configuration file structure. Update your config files; support for this structure will be dropped in version 1.0.0."
157
+ # There's only ever one item in the 'compile' array, and only one action type ("parse")
158
+ config['compile'].each do |n|
159
+ n.merge!("action" => "parse") # the action type was not previously declared
160
+ end
161
+ return config['compile']
162
+ end
163
+
164
+ end #class BuildConfig
165
+
166
+ class BuildConfigStep
167
+
168
+ def initialize step
169
+ @@step = step
170
+ @@logger = Logger.new(STDOUT)
171
+ if (defined?(@@step['action'])).nil?
172
+ @logger.error "Every step in the configuration file needs an 'action' type declared."
173
+ raise "ConfigStructError"
174
+ end
175
+ end
176
+
177
+ def type
178
+ return @@step['action']
179
+ end
180
+
181
+ def data
182
+ return @@step['data']
183
+ end
184
+
185
+ def builds
186
+ return @@step['builds']
187
+ end
188
+
189
+ def self.validate reqs
190
+ for req in reqs
191
+ if (defined?(@@step[req])).nil?
192
+ @@logger.error "Every #{@@step['action']}-type in the configuration file needs a '#{req}' declaration."
193
+ raise "ConfigStructError"
194
+ end
195
+ end
196
+ end
197
+
198
+ end #class Action
199
+
200
+ class Build
201
+
202
+ def initialize build, type
203
+ @@build = build
204
+ @@type = type
205
+ @@logger = Logger.new(STDOUT)
206
+ required = []
207
+ case type
208
+ when "parse"
209
+ required = ["template,output"]
210
+ when "render"
211
+ required = ["index,output"]
212
+ when "migrate"
213
+ required = ["source,target"]
214
+ end
215
+ for req in required
216
+ if (defined?(req)).nil?
217
+ raise ActionSettingMissing
218
+ end
219
+ end
220
+ end
221
+
222
+ def template
223
+ @@build['template']
224
+ end
225
+
226
+ def output
227
+ @@build['output']
228
+ end
229
+
230
+ def index
231
+ @@build['index']
232
+ end
233
+
234
+ def source
235
+ @@build['source']
236
+ end
237
+
238
+ def target
239
+ @@build['target']
240
+ end
241
+
242
+ end #class Build
243
+
244
+ class DataSrc
245
+ # initialization means establishing a proper hash for the 'data' param
246
+ def initialize datasrc
247
+ @@datasrc = {}
248
+ if datasrc.is_a? String # create a hash out of the filename
249
+ begin
250
+ @@datasrc['file'] = datasrc
251
+ @@datasrc['ext'] = File.extname(datasrc)
252
+ @@datasrc['type'] = false
253
+ @@datasrc['pattern'] = false
254
+ rescue
255
+ raise "InvalidDataFilename"
256
+ end
257
+ else
258
+ if datasrc.is_a? Hash # data var is a hash, so add 'ext' to it by extracting it from filename
259
+ @@datasrc['file'] = datasrc['file']
260
+ @@datasrc['ext'] = File.extname(datasrc['file'])
261
+ if (defined?(datasrc['pattern']))
262
+ @@datasrc['pattern'] = datasrc['pattern']
263
+ end
264
+ if (defined?(datasrc['type']))
265
+ @@datasrc['type'] = datasrc['type']
266
+ end
267
+ else # datasrc is neither String nor Hash
268
+ raise "InvalidDataSource"
269
+ end
270
+ end
271
+ end
272
+
273
+ def file
274
+ @@datasrc['file']
275
+ end
276
+
277
+ def ext
278
+ @@datasrc['ext']
279
+ end
280
+
281
+ def type
282
+ if @@datasrc['type'] # if we're carrying a 'type' setting for data, pass it along
283
+ datatype = @@datasrc['type']
284
+ if datatype.downcase == "yaml" # This is an expected common error, so let's do the user a solid
285
+ datatype = "yml"
286
+ end
287
+ else # If there's no 'type' defined, extract it from the filename and validate it
288
+ unless @@datasrc['ext'].downcase.match(/\.yml|\.json|\.xml|\.csv/)
289
+ # @logger.error "Data file extension must be one of: .yml, .json, .xml, or .csv or else declared in config file."
290
+ raise "FileExtensionUnknown"
291
+ end
292
+ datatype = @@datasrc['ext']
293
+ datatype = datatype[1..-1] # removes leading dot char
294
+ end
295
+ unless datatype.downcase.match(/yml|json|xml|csv|regex/) # 'type' must be one of these permitted vals
296
+ # @logger.error "Declared data type must be one of: yaml, json, xml, csv, or regex."
297
+ raise "DataTypeUnrecognized"
298
+ end
299
+ datatype
300
+ end
301
+
302
+ def pattern
303
+ @@datasrc['pattern']
304
+ end
305
+ end
306
+
307
+ # ===
308
+ # Action-specific methods
309
+ #
310
+ # PARSE-type build methods
311
+ # ===
312
+
313
+ # Pull in a semi-structured data file, converting contents to a Ruby hash
314
+ def ingest_data datasrc
315
+ # Must be passed a proper data object (there must be a better way to validate arg datatypes)
316
+ unless datasrc.is_a? Object
317
+ raise "InvalidDataObject"
318
+ end
319
+ # This method should really begin here, once the data object is in order
320
+ case datasrc.type
321
+ when "yml"
322
+ begin
323
+ return YAML.load_file(datasrc.file)
324
+ rescue Exception => ex
325
+ @logger.error "There was a problem with the data file. #{ex.message}"
326
+ end
327
+ when "json"
328
+ begin
329
+ return JSON.parse(File.read(datasrc.file))
330
+ rescue Exception => ex
331
+ @logger.error "There was a problem with the data file. #{ex.message}"
332
+ end
333
+ when "xml"
334
+ begin
335
+ data = Crack::XML.parse(File.read(datasrc.file))
336
+ return data['root']
337
+ rescue Exception => ex
338
+ @logger.error "There was a problem with the data file. #{ex.message}"
339
+ end
340
+ when "csv"
341
+ output = []
342
+ i = 0
343
+ begin
344
+ CSV.foreach(datasrc.file, headers: true, skip_blanks: true) do |row|
345
+ output[i] = row.to_hash
346
+ i = i+1
347
+ end
348
+ output = {"data" => output}
349
+ return output
350
+ rescue
351
+ @logger.error "The CSV format is invalid."
352
+ end
353
+ when "regex"
354
+ if datasrc.pattern
355
+ return parse_regex(datasrc.file, datasrc.pattern)
356
+ else
357
+ @logger.error "You must supply a regex pattern with your free-form data file."
358
+ raise "MissingRegexPattern"
359
+ end
360
+ end
189
361
  end
190
362
 
191
363
  def parse_regex data_file, pattern
@@ -214,17 +386,15 @@ def parse_regex data_file, pattern
214
386
  return output
215
387
  end
216
388
 
217
- # ===
218
- # Liquify BUILD methods
219
- # ===
220
-
221
- # Parse given data using given template, saving to given filename
222
- def liquify data, template_file, output
389
+ # Parse given data using given template, generating given output
390
+ def liquify datasrc, template_file, output
223
391
  @logger.debug "Executing liquify parsing operation."
224
- data = data_hashify(data)
225
- validate_file_input(data['file'], "data")
392
+ if datasrc.is_a? String
393
+ datasrc = DataSrc.new(datasrc)
394
+ end
395
+ validate_file_input(datasrc.file, "data")
226
396
  validate_file_input(template_file, "template")
227
- data = get_data(data) # gathers the data
397
+ data = ingest_data(datasrc)
228
398
  begin
229
399
  template = File.read(template_file) # reads the template file
230
400
  template = Liquid::Template.parse(template) # compiles template
@@ -253,6 +423,10 @@ def liquify data, template_file, output
253
423
  end
254
424
  end
255
425
 
426
+ # ===
427
+ # MIGRATE-type methods
428
+ # ===
429
+
256
430
  # Copy images and other assets into output dir for HTML operations
257
431
  def copy_assets src, dest
258
432
  if @recursive
@@ -273,7 +447,7 @@ def copy_assets src, dest
273
447
  end
274
448
 
275
449
  # ===
276
- # PUBLISH methods
450
+ # RENDER-type methods
277
451
  # ===
278
452
 
279
453
  # Gather attributes from a fixed attributes file
@@ -308,7 +482,7 @@ def publish pub, bld
308
482
  end
309
483
 
310
484
  # ===
311
- # Misc Classes, Modules, filters, etc
485
+ # Text manipulation Classes, Modules, filters, etc
312
486
  # ===
313
487
 
314
488
  class String
@@ -336,7 +510,7 @@ class String
336
510
 
337
511
  end
338
512
 
339
- # Liquid modules for text manipulation
513
+ # Extending Liquid filters/text manipulation
340
514
  module CustomFilters
341
515
  def plainwrap input
342
516
  input.wrap
@@ -382,11 +556,17 @@ module CustomFilters
382
556
 
383
557
  end
384
558
 
559
+ # register custom Liquid filters
385
560
  Liquid::Template.register_filter(CustomFilters)
386
561
 
562
+
563
+ # ===
564
+ # Command/options parser
565
+ # ===
566
+
387
567
  # Define command-line option/argument parameters
388
568
  # From the root directory of your project:
389
- # $ ./parse.rb --help
569
+ # $ liquidoc --help
390
570
  command_parser = OptionParser.new do|opts|
391
571
  opts.banner = "Usage: liquidoc [options]"
392
572
 
@@ -442,7 +622,6 @@ command_parser = OptionParser.new do|opts|
442
622
 
443
623
  end
444
624
 
445
- # Parse options.
446
625
  command_parser.parse!
447
626
 
448
627
  # Upfront debug output
@@ -450,10 +629,9 @@ command_parser.parse!
450
629
  @logger.debug "Config file: #{@config_file}"
451
630
  @logger.debug "Index file: #{@index_file}"
452
631
 
453
- # Parse data into docs!
454
- # liquify() takes the names of a Liquid template, a data file, and an output doc.
455
- # Input and output file extensions are non-determinant; your template
456
- # file establishes the structure.
632
+ # ===
633
+ # Execute
634
+ # ===
457
635
 
458
636
  unless @config_file
459
637
  if @data_file
@@ -1,3 +1,3 @@
1
1
  module Liquidoc
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: liquidoc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Dominick
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-18 00:00:00.000000000 Z
11
+ date: 2017-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler