liquidoc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []