liquidoc 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6d540613bd9ce63cedb817ae5faec345f8f1ef6
4
+ data.tar.gz: b8d3ebaae9154e2c06632288226f0d0d790cba60
5
+ SHA512:
6
+ metadata.gz: 520b0885644503d9ef90e0283423c5eedfa3a53b2c3b6d920dcc60776e8173d770b64a80994e463cdb7a1c5411f1fc11e4d0b7ba43ec9687b349a82940067955
7
+ data.tar.gz: 760c4c9a0c6315f0c3488db65f839441a91e09e8e596c0c73ec9756443b2de21e21561154fe7db0511e81fdb598edf527567098f5cd6d62e184442d9241c256c
data/bin/liquidoc ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'liquidoc'
@@ -0,0 +1,3 @@
1
+ module Liquidoc
2
+ VERSION = "0.1.0"
3
+ end
data/lib/liquidoc.rb ADDED
@@ -0,0 +1,405 @@
1
+ require "liquidoc"
2
+ require 'yaml'
3
+ require 'json'
4
+ require 'optparse'
5
+ require 'liquid'
6
+ require 'asciidoctor'
7
+ require 'logger'
8
+ require 'csv'
9
+ require 'crack/xml'
10
+
11
+ # Default settings
12
+ @base_dir_def = Dir.pwd + '/'
13
+ @base_dir = @base_dir_def
14
+ @configs_dir = @base_dir + '_configs'
15
+ @templates_dir = @base_dir + '_templates/'
16
+ @data_dir = @base_dir + '_data/'
17
+ @output_dir = @base_dir + '_output/'
18
+ @config_file_def = @base_dir + '_configs/cfg-sample.yml'
19
+ @config_file = @config_file_def
20
+ @attributes_file_def = '_data/asciidoctor.yml'
21
+ @attributes_file = @attributes_file_def
22
+ @pdf_theme_file = 'theme/pdf-theme.yml'
23
+ @fonts_dir = 'theme/fonts/'
24
+ @output_filename = 'index'
25
+ @attributes = {}
26
+
27
+ @logger = Logger.new(STDOUT)
28
+ @logger.level = Logger::INFO
29
+ @logger.formatter = proc do |severity, datetime, progname, msg|
30
+ "#{severity}: #{msg}\n"
31
+ end
32
+
33
+ # ===
34
+ # General methods
35
+ # ===
36
+
37
+ # Pull in a semi-structured data file, converting contents to a Ruby hash
38
+ def get_data data_file
39
+ case File.extname(data_file)
40
+ when ".yml"
41
+ begin
42
+ return YAML.load_file(data_file)
43
+ rescue Exception => ex
44
+ @logger.error "There was a problem with the data file. #{ex.message}"
45
+ end
46
+ when ".json"
47
+ begin
48
+ return JSON.parse(File.read(data_file))
49
+ rescue Exception => ex
50
+ @logger.error "There was a problem with the data file. #{ex.message}"
51
+ end
52
+ when ".xml"
53
+ begin
54
+ data = Crack::XML.parse(File.read(data_file))
55
+ return data['root']
56
+ rescue Exception => ex
57
+ @logger.error "There was a problem with the data file. #{ex.message}"
58
+ end
59
+ when ".csv"
60
+ output = []
61
+ i = 0
62
+ begin
63
+ CSV.foreach(data_file, headers: true, skip_blanks: true) do |row|
64
+ output[i] = row.to_hash
65
+ i = i+1
66
+ end
67
+ output = {"data" => output}
68
+ return output
69
+ rescue
70
+ @logger.error "The CSV format is invalid."
71
+ end
72
+ else
73
+ @logger.error "The data file is an invalid type. Allowed: .yml, .json, .xml, and .csv."
74
+ end
75
+ end
76
+
77
+ # Establish source, template, index, etc details for build jobs from a config file
78
+ # TODO This needs to be turned into a Class?
79
+ def config_build config_file
80
+ @logger.debug "Using config file #{config_file}."
81
+ validate_file_input(config_file, "config")
82
+ begin
83
+ config = YAML.load_file(config_file)
84
+ rescue
85
+ unless File.exists?(config_file)
86
+ @logger.error "Config file not found."
87
+ else
88
+ @logger.error "Problem loading config file. Exiting."
89
+ end
90
+ raise "Could not load #{config_file}"
91
+ end
92
+ validate_config_structure(config)
93
+ if config['compile']
94
+ for src in config['compile']
95
+ data = @base_dir + src['data']
96
+ for cfgn in src['builds']
97
+ template = @base_dir + cfgn['template']
98
+ unless cfgn['output'] == "STDOUT" or @output_type == "STDOUT"
99
+ output = @base_dir + cfgn['output']
100
+ else
101
+ output = "STDOUT"
102
+ end
103
+ liquify(data, template, output)
104
+ end
105
+ end
106
+ end
107
+ if config['publish']
108
+ begin
109
+ for pub in config['publish']
110
+ for bld in pub['builds']
111
+ if bld['publish']
112
+ publish(pub, bld)
113
+ else
114
+ @logger.warn "Publish build for '#{index}' backend '#{backend}' disabled."
115
+ end
116
+ end
117
+ end
118
+ rescue Exception => ex
119
+ @logger.error "Error during publish action. #{ex}"
120
+ end
121
+ end
122
+ end
123
+
124
+ # Verify files exist
125
+ def validate_file_input file, type
126
+ @logger.debug "Validating input file for #{type} file #{file}"
127
+ error = false
128
+ unless file.is_a?(String) and !file.nil?
129
+ error = "The #{type} file (#{file}) is not valid."
130
+ else
131
+ unless File.exists?(file)
132
+ error = "The #{type} file (#{file}) was not found."
133
+ end
134
+ end
135
+ unless error
136
+ @logger.debug "Input file validated for #{type} file #{file}."
137
+ else
138
+ @logger.error
139
+ raise "Could not validate file input: #{error}"
140
+ end
141
+ end
142
+
143
+ def validate_config_structure config
144
+ unless config.is_a? Hash
145
+ message = "The configuration file is not properly structured; it is not a Hash"
146
+ @logger.error message
147
+ raise message
148
+ else
149
+ unless config['publish'] or config['compile']
150
+ raise "Config file must have at least one top-level section named 'publish:' or 'compile:'."
151
+ end
152
+ end
153
+ # TODO More validation needed
154
+ end
155
+
156
+ # ===
157
+ # Liquify BUILD methods
158
+ # ===
159
+
160
+ # Parse given data using given template, saving to given filename
161
+ def liquify data_file, template_file, output_file
162
+ @logger.debug "Executing... liquify parsing operation on data file: #{data_file}, template #{template_file}, to #{output_file}."
163
+ validate_file_input(data_file, "data")
164
+ validate_file_input(template_file, "template")
165
+ data = get_data(data_file) # gathers the data
166
+ begin
167
+ template = File.read(template_file) # reads the template file
168
+ template = Liquid::Template.parse(template) # compiles template
169
+ rendered = template.render(data) # renders the output
170
+ rescue Exception => ex
171
+ message = "Problem rendering Liquid template. #{template_file}\n" \
172
+ "#{ex.class} thrown. #{ex.message}"
173
+ @logger.error message
174
+ raise message
175
+ end
176
+ unless @output_type == "STDOUT"
177
+ begin
178
+ Dir.mkdir(@output_dir) unless File.exists?(@output_dir)
179
+ File.open(output_file, 'w') { |file| file.write(rendered) } # saves file
180
+ rescue Exception => ex
181
+ @logger.error "Failed to save output.\n#{ex.class} #{ex.message}"
182
+ end
183
+ if File.exists?(output_file)
184
+ @logger.info "File built: #{File.basename(output_file)}"
185
+ else
186
+ @logger.error "Hrmp! File not built."
187
+ end
188
+ else # if stdout
189
+ puts "========\nOUTPUT: Rendered with template #{template_file}:\n\n#{rendered}\n"
190
+ end
191
+ end
192
+
193
+ # Copy images and other assets into output dir for HTML operations
194
+ def copy_assets src, dest
195
+ if @recursive
196
+ dest = "#{dest}/#{src}"
197
+ recursively = "Recursively c"
198
+ else
199
+ recursively = "C"
200
+ end
201
+ @logger.debug "#{recursively}opying image assets to #{dest}"
202
+ begin
203
+ FileUtils.mkdir_p(dest) unless File.exists?(dest)
204
+ FileUtils.cp_r(src, dest)
205
+ rescue Exception => ex
206
+ @logger.warn "Problem while copying assets. #{ex.message}"
207
+ return
208
+ end
209
+ @logger.debug "\s\s#{recursively}opied: #{src} --> #{dest}/#{src}"
210
+ end
211
+
212
+ # ===
213
+ # PUBLISH methods
214
+ # ===
215
+
216
+ # Gather attributes from a fixed attributes file
217
+ # Use _data/attributes.yml or designate as -a path/to/filename.yml
218
+ def get_attributes attributes_file
219
+ if attributes_file == nil
220
+ attributes_file = @attributes_file_def
221
+ end
222
+ validate_file_input(attributes_file, "attributes")
223
+ begin
224
+ attributes = YAML.load_file(attributes_file)
225
+ return attributes
226
+ rescue
227
+ @logger.warn "Attributes file invalid."
228
+ end
229
+ end
230
+
231
+ # Set attributes for direct Asciidoctor operations
232
+ def set_attributes attributes
233
+ unless attributes.is_a?(Enumerable)
234
+ attributes = { }
235
+ end
236
+ attributes["basedir"] = @base_path
237
+ attributes.merge!get_attributes(@attributes_file)
238
+ attributes = '-a ' + attributes.map{|k,v| "#{k}='#{v}'"}.join(' -a ')
239
+ return attributes
240
+ end
241
+
242
+ # To be replaced with a gem call
243
+ def publish pub, bld
244
+ @logger.warn "Publish actions not yet implemented."
245
+ end
246
+
247
+ # ===
248
+ # Misc Classes, Modules, filters, etc
249
+ # ===
250
+
251
+ class String
252
+ # Adapted from Nikhil Gupta
253
+ # http://nikhgupta.com/code/wrapping-long-lines-in-ruby-for-display-in-source-files/
254
+ def wrap options = {}
255
+ width = options.fetch(:width, 76)
256
+ commentchar = options.fetch(:commentchar, '')
257
+ self.strip.split("\n").collect do |line|
258
+ line.length > width ? line.gsub(/(.{1,#{width}})(\s+|$)/, "\\1\n#{commentchar}") : line
259
+ end.map(&:strip).join("\n#{commentchar}")
260
+ end
261
+
262
+ def indent options = {}
263
+ spaces = " " * options.fetch(:spaces, 4)
264
+ self.gsub(/^/, spaces).gsub(/^\s*$/, '')
265
+ end
266
+
267
+ def indent_with_wrap options = {}
268
+ spaces = options.fetch(:spaces, 4)
269
+ width = options.fetch(:width, 80)
270
+ width = width > spaces ? width - spaces : 1
271
+ self.wrap(width: width).indent(spaces: spaces)
272
+ end
273
+
274
+ end
275
+
276
+ # Liquid modules for text manipulation
277
+ module CustomFilters
278
+ def plainwrap input
279
+ input.wrap
280
+ end
281
+ def commentwrap input
282
+ input.wrap commentchar: "# "
283
+ end
284
+ def unwrap input # Not fully functional; inserts explicit '\n'
285
+ if input
286
+ token = "[g59hj1k]"
287
+ input.gsub(/\n\n/, token).gsub(/\n/, ' ').gsub(token, "\n\n")
288
+ end
289
+ end
290
+
291
+ # From Slate Studio's fork of Locomotive CMS engine
292
+ # https://github.com/slate-studio/engine/blob/master/lib/locomotive/core_ext.rb
293
+ def slugify(options = {})
294
+ options = { :sep => '_', :without_extension => false, :downcase => false, :underscore => false }.merge(options)
295
+ # replace accented chars with ther ascii equivalents
296
+ s = ActiveSupport::Inflector.transliterate(self).to_s
297
+ # No more than one slash in a row
298
+ s.gsub!(/(\/[\/]+)/, '/')
299
+ # Remove leading or trailing space
300
+ s.strip!
301
+ # Remove leading or trailing slash
302
+ s.gsub!(/(^[\/]+)|([\/]+$)/, '')
303
+ # Remove extensions
304
+ s.gsub!(/(\.[a-zA-Z]{2,})/, '') if options[:without_extension]
305
+ # Downcase
306
+ s.downcase! if options[:downcase]
307
+ # Turn unwanted chars into the seperator
308
+ s.gsub!(/[^a-zA-Z0-9\-_\+\/]+/i, options[:sep])
309
+ # Underscore
310
+ s.gsub!(/[\-]/i, '_') if options[:underscore]
311
+ s
312
+ end
313
+ def slugify!(options = {})
314
+ replace(self.slugify(options))
315
+ end
316
+ def parameterize!(sep = '_')
317
+ replace(self.parameterize(sep))
318
+ end
319
+
320
+ end
321
+
322
+ Liquid::Template.register_filter(CustomFilters)
323
+
324
+ # Define command-line option/argument parameters
325
+ # From the root directory of your project:
326
+ # $ ./parse.rb --help
327
+ command_parser = OptionParser.new do|opts|
328
+ opts.banner = "Usage: liquidoc [options]"
329
+
330
+ opts.on("-a PATH", "--attributes-file=PATH", "For passing in a standard YAML AsciiDoc attributes file. Default: #{@attributes_file_def}") do |n|
331
+ @assets_path = n
332
+ end
333
+
334
+ opts.on("--attr=STRING", "For passing an AsciiDoc attribute parameter to Asciidoctor. Ex: --attr basedir=some/path --attr imagesdir=some/path/images") do |n|
335
+ @passed_attrs = @passed_attrs.merge!n
336
+ end
337
+
338
+ # Global Options
339
+ opts.on("-b PATH", "--base=PATH", "The base directory, relative to this script. Defaults to `.`, or pwd." ) do |n|
340
+ @data_file = @base_dir + n
341
+ end
342
+
343
+ opts.on("-c", "--config=PATH", "Configuration file, enables preset source, template, and output.") do |n|
344
+ @config_file = @base_dir + n
345
+ end
346
+
347
+ opts.on("-d PATH", "--data=PATH", "Semi-structured data source (input) path. Ex. path/to/data.yml. Required unless --config is called." ) do |n|
348
+ @data_file = @base_dir + n
349
+ end
350
+
351
+ opts.on("-f PATH", "--from=PATH", "Directory to copy assets from." ) do |n|
352
+ @attributes_file = n
353
+ end
354
+
355
+ opts.on("-i PATH", "--index=PATH", "An AsciiDoc index file for mapping an Asciidoctor build." ) do |n|
356
+ @index_file = n
357
+ end
358
+
359
+ opts.on("-o PATH", "--output=PATH", "Output file path for generated content. Ex. path/to/file.adoc. Required unless --config is called.") do |n|
360
+ @output_file = @base_dir + n
361
+ end
362
+
363
+ opts.on("-t PATH", "--template=PATH", "Path to liquid template. Required unless --configuration is called." ) do |n|
364
+ @template_file = @base_dir + n
365
+ end
366
+
367
+ opts.on("--verbose", "Run verbose") do |n|
368
+ @logger.level = Logger::DEBUG
369
+ end
370
+
371
+ opts.on("--stdout", "Puts the output in STDOUT instead of writing to a file.") do
372
+ @output_type = "STDOUT"
373
+ end
374
+
375
+ opts.on("-h", "--help", "Returns help.") do
376
+ puts opts
377
+ exit
378
+ end
379
+
380
+ end
381
+
382
+ # Parse options.
383
+ command_parser.parse!
384
+
385
+ # Upfront debug output
386
+ @logger.debug "Base dir: #{@base_dir}"
387
+ @logger.debug "Config file: #{@config_file}"
388
+ @logger.debug "Index file: #{@index_file}"
389
+
390
+ # Parse data into docs!
391
+ # liquify() takes the names of a Liquid template, a data file, and an output doc.
392
+ # Input and output file extensions are non-determinant; your template
393
+ # file establishes the structure.
394
+
395
+ unless @config_file
396
+ if @data_file
397
+ liquify(@data_file, @template_file, @output_file)
398
+ end
399
+ if @index_file
400
+ @logger.warn "Publishing via command line arguments not yet implemented. Use a config file."
401
+ end
402
+ else
403
+ @logger.debug "Executing... config_build"
404
+ config_build(@config_file)
405
+ end
metadata ADDED
@@ -0,0 +1,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: liquidoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Dominick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ description: LiquiDoc conveniently harnesses the power of Liquid templates, flat-file
42
+ data formats such as YAML, JSON, XML, and CSV, as well as AsciiDoc markup and powerful
43
+ Asciidoctor output capabilities -- all in a single command-line tool.
44
+ email:
45
+ - badominick@gmail.com
46
+ executables:
47
+ - liquidoc
48
+ extensions: []
49
+ extra_rdoc_files: []
50
+ files:
51
+ - bin/liquidoc
52
+ - lib/liquidoc.rb
53
+ - lib/liquidoc/version.rb
54
+ homepage: https://github.com/scalingdata/liquidoc
55
+ licenses:
56
+ - MIT
57
+ metadata:
58
+ allowed_push_host: https://rubygems.org
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubyforge_project:
75
+ rubygems_version: 2.4.8
76
+ signing_key:
77
+ specification_version: 4
78
+ summary: A highly configurable command-line tool for parsing data and content in common
79
+ flat-file formats.
80
+ test_files: []