summaryrefslogtreecommitdiff
path: root/ruby/luigi-template.rb
diff options
context:
space:
mode:
authorPaul Duncan <pabs@pablotron.org>2018-09-05 00:53:51 -0400
committerPaul Duncan <pabs@pablotron.org>2018-09-05 00:53:51 -0400
commit7fd6c6611252dbe1aae0ae61afab78f936b47c1f (patch)
treef4643a9b1465b329e0c7c570fcca19fe35b1de7a /ruby/luigi-template.rb
parent4f92438ba4cbc04016e2b06996bb7d7e99e01952 (diff)
downloadluigi-template-7fd6c6611252dbe1aae0ae61afab78f936b47c1f.tar.bz2
luigi-template-7fd6c6611252dbe1aae0ae61afab78f936b47c1f.zip
ruby: add gemspec, add Template::run
Diffstat (limited to 'ruby/luigi-template.rb')
-rw-r--r--ruby/luigi-template.rb289
1 files changed, 0 insertions, 289 deletions
diff --git a/ruby/luigi-template.rb b/ruby/luigi-template.rb
deleted file mode 100644
index d737fdc..0000000
--- a/ruby/luigi-template.rb
+++ /dev/null
@@ -1,289 +0,0 @@
-# require 'pp'
-
-module Luigi
- #
- # library version
- #
- VERSION = '0.4.0'
-
- #
- # built-in filters
- #
- FILTERS = {
- # upper-case string
- uc: proc { |v, args, row, t|
- (v || '').to_s.upcase
- },
-
- # lower-case string
- lc: proc { |v, args, row, t|
- (v || '').to_s.downcase
- },
-
- # html-escape string
- h: proc { |v, args, row, t|
- (v || '').to_s.gsub(/&/, '&amp;').gsub(/</, '&lt;').gsub(/>/, '&gt;').gsub(/'/, '&apos').gsub(/"/, '&quot;')
- },
-
- # uri-escape string
- u: proc( { |v, args, row, t|
- require 'uri'
- URI.escape((v || '').to_s)
- },
-
- # json-encode value
- json: proc { |v, args, row, t|
- require 'json'
- v.to_json
- },
-
- # trim leading and trailing whitespace from string
- trim: proc { |v, args, row, t|
- (v || '').to_s.strip
- },
-
- # base64-encode string
- base64: proc { |v, args, row, t|
- [(v || '').to_s].pack('m')
- },
-
- # hash string
- hash: proc { |v, args, row, t|
- require 'openssl'
- OpenSSL::Digest.new(args[0] || 'md5').hexdigest((v || '').to_s)
- },
- }
-
- #
- # Template parser.
- #
- module Parser
- RES = {
- action: %r{
- # match opening brace
- %\{
-
- # match optional whitespace
- \s*
-
- # match key
- (?<key>[^\s\|\}]+)
-
- # match filter(s)
- (?<filters>(\s*\|(\s*[^\s\|\}]+)+)*)
-
- # match optional whitespace
- \s*
-
- # match closing brace
- \}
-
- # or match up all non-% chars or a single % char
- | (?<text>[^%]* | %)
- }mx,
-
- filter: %r{
- # match filter name
- (?<name>\S+)
-
- # match filter arguments (optional)
- (?<args>(\s*\S+)*)
-
- # optional trailing whitespace
- \s*
- }mx,
-
- delim_filters: %r{
- \s*\|\s*
- }mx,
-
- delim_args: %r{
- \s+
- },
- }
-
- #
- # Parse a (possibly empty) string into an array of actions.
- #
- def self.parse_template(str)
- str.scan(RES[:action]).map { |m|
- if m[0] && m[0].length > 0
- r = {
- type: :action,
- key: m[0].intern,
- filters: parse_filters(m[1]),
- }
- else
- # literal text
- r = { type: :text, text: m[2] }
- end
-
- # pp r
-
- # return result
- r
- }
- end
-
- #
- # Parse a (possibly empty) string into an array of filters.
- #
- def self.parse_filters(str)
- # strip leading and trailing whitespace
- str = (str || '').strip
-
- if str.length > 0
- str.strip.split(RES[:delim_filters]).inject([]) do |r, f|
- # strip whitespace
- f = f.strip
-
- if f.length > 0
- md = f.match(RES[:filter])
- raise "invalid filter: #{f}" unless md
- # pp md
-
- # get args
- args = md[:args].strip
-
- # add to result
- r << {
- name: md[:name].intern,
- args: args.length > 0 ? args.split(RES[:delim_args]) : [],
- }
- end
-
- # return result
- r
- end
- else
- # return empty filter set
- []
- end
- end
- end
-
- #
- # Template class.
- #
- class Template
- #
- # Create a new Template from the given string.
- #
- def initialize(str, filters = FILTERS)
- @str, @filters = str, filters
- @actions = Parser.parse_template(str)
- end
-
- #
- # Run template with given arguments
- #
- def run(args)
- @actions.map { |a|
- # pp a
-
- case a[:type]
- when :action
- # check key and get value
- val = if args.key?(a[:key])
- args[a[:key]]
- elsif args.key?(a[:key].to_s)
- args[a[:key].to_s]
- else
- # invalid key
- raise "unknown argument: #{a[:key]}"
- end
-
- # filter value
- a[:filters].inject(val) do |r, f|
- # check filter name
- raise "unknown filter: #{f[:name]}" unless @filters.key?(f[:name])
-
- # call filter, return result
- @filters[f[:name]].call(r, f[:args], args, self)
- end
- when :text
- # literal text
- a[:text]
- else
- # never reached
- raise "unknown action type: #{a[:type]}"
- end
- }.join
- end
- end
-
- #
- # Simple template cache.
- #
- class Cache
- #
- # Create a new template cache with the given templates
- #
- def initialize(strings, filters = FILTERS)
- @templates = Hash.new do |h, k|
- # always deal with symbols
- k = k.intern
-
- # make sure template exists
- raise "unknown template: #{k}" unless strings.key?(k)
-
- # create template
- h[k] = Template.new(strings[k], filters)
- end
- end
-
- #
- # Run specified template with given arguments.
- #
- def run(key, args)
- # run template with args and return result
- @templates[key].run(args)
- end
- end
-
- #
- # test module
- #
- module Test
- # test template
- TEMPLATE = '
- basic test: hello %{name}
- test filters: hello %{name | uc | base64 | hash sha1}
- test custom: hello %{name|custom}
- test custom_with_arg: %{name|custom_with_arg asdf}
- '
-
- CUSTOM_FILTERS = {
- custom: proc {
- 'custom'
- },
-
- custom_with_arg: proc { |v, args|
- args.first || 'custom'
- },
- }
-
- # test template cache
- CACHE = {
- test: TEMPLATE
- }
-
- # test arguments
- ARGS = {
- name: 'paul',
- }
-
- def self.run
- # add custom filters
- Luigi::FILTERS.update(CUSTOM_FILTERS)
-
- # test individual template
- puts Template.new(TEMPLATE).run(ARGS)
-
- # test template cache
- puts Cache.new(CACHE).run(:test, ARGS)
- end
- end
-end
-
-Luigi::Test.run if __FILE__ == $0