liquidum 1.0.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +39 -0
  4. data/Rakefile +37 -0
  5. data/app/controllers/liquidum/application_controller.rb +5 -0
  6. data/app/controllers/liquidum/completions_controller.rb +15 -0
  7. data/app/controllers/liquidum/sandbox_controller.rb +28 -0
  8. data/app/helpers/liquidum/application_helper.rb +4 -0
  9. data/app/jobs/liquidum/application_job.rb +4 -0
  10. data/app/models/concerns/liquidum/to_liquid.rb +17 -0
  11. data/app/views/liquidum/sandbox/index.html.slim +13 -0
  12. data/config/routes.rb +4 -0
  13. data/lib/liquidum/configuration.rb +23 -0
  14. data/lib/liquidum/drop.rb +69 -0
  15. data/lib/liquidum/engine.rb +15 -0
  16. data/lib/liquidum/liquid/drops/active_storage_attached_many_drop.rb +14 -0
  17. data/lib/liquidum/liquid/drops/active_storage_attached_one_drop.rb +21 -0
  18. data/lib/liquidum/liquid/drops/active_storage_attachment_drop.rb +19 -0
  19. data/lib/liquidum/liquid/drops/enumerator_lazy_drop.rb +7 -0
  20. data/lib/liquidum/liquid/filters/array_wrap_filter.rb +14 -0
  21. data/lib/liquidum/liquid/filters/encode_filter.rb +14 -0
  22. data/lib/liquidum/liquid/filters/format_filter.rb +14 -0
  23. data/lib/liquidum/liquid/filters/localize_filter.rb +15 -0
  24. data/lib/liquidum/liquid/filters/strptime_filter.rb +14 -0
  25. data/lib/liquidum/liquid/filters/sum_filter.rb +16 -0
  26. data/lib/liquidum/liquid/filters/timezone_filter.rb +18 -0
  27. data/lib/liquidum/liquid/filters/to_filter.rb +46 -0
  28. data/lib/liquidum/liquid/filters/translate_filter.rb +45 -0
  29. data/lib/liquidum/liquid/filters/where_exp_filter.rb +70 -0
  30. data/lib/liquidum/liquid/liquid_helpers.rb +163 -0
  31. data/lib/liquidum/liquid/liquid_template_extensions.rb +34 -0
  32. data/lib/liquidum/liquid/liquidum_block.rb +5 -0
  33. data/lib/liquidum/liquid/liquidum_tag.rb +5 -0
  34. data/lib/liquidum/liquid/parser.rb +53 -0
  35. data/lib/liquidum/liquid/tags/helper_tag.rb +30 -0
  36. data/lib/liquidum/liquid/tags/render_tag.rb +19 -0
  37. data/lib/liquidum/version.rb +5 -0
  38. data/lib/liquidum.rb +77 -0
  39. metadata +318 -0
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquidum
4
+ module LiquidHelpers
5
+ private
6
+
7
+ def initialize(tag, args, tokens)
8
+ super
9
+ @blank = false
10
+ begin
11
+ @args = Liquidum::Tag::Parser.new(args).args
12
+ rescue Parslet::ParseFailed => e
13
+ raise Parslet::ParseFailed, e.message + " (#{tag} #{args})"
14
+ end
15
+ end
16
+
17
+ def render(context)
18
+ @context = context
19
+ end
20
+
21
+ def render_body
22
+ @body.render(@context)
23
+ end
24
+
25
+ # Return named argument
26
+ def arg(name)
27
+ raise 'No @context set' unless @context
28
+
29
+ attr = @args.find { |a| a[:attr] == name.to_s }
30
+ return unless attr
31
+
32
+ if attr.key? :value
33
+ attr[:value].to_s
34
+ elsif attr.key? :lvalue
35
+ lookup(@context, attr[:lvalue].to_s)
36
+ end
37
+ end
38
+
39
+ # Returns the first argument - usually reserved for literal or quoted values, not for attribute value pairs
40
+ # When the first attribute is a pair, it will return nil
41
+ def argv1
42
+ raise 'No @context set' unless @context
43
+
44
+ argv1 = @args[0]
45
+ return unless argv1
46
+
47
+ if argv1.key? :quoted
48
+ argv1[:quoted].to_s
49
+ elsif argv1.key? :literal
50
+ @context.key?(argv1[:literal].to_s) ? @context[argv1[:literal].to_s] : argv1[:literal].to_s
51
+ end
52
+ end
53
+
54
+ # Returns the standalone arguments
55
+ def sargs
56
+ raise 'No @context set' unless @context
57
+
58
+ @args.slice(1..-1).select { |a| a.key?(:quoted) || a.key?(:literal) }.map do |a|
59
+ if a.key? :quoted
60
+ a[:quoted].to_s
61
+ elsif a.key? :literal
62
+ lookup(@context, a[:literal].to_s) || a[:literal]
63
+ end
64
+ end
65
+ end
66
+
67
+ # Returns the attribute-value-pair arguments as a hash
68
+ def attr_args
69
+ raise 'No @context set' unless @context
70
+
71
+ result = {}
72
+ @args.select { |a| a.key?(:value) || a.key?(:lvalue) || a.key?(:svalue) }.map do |a|
73
+ if a.key? :value
74
+ result[a[:attr].to_sym] = a[:value].to_s
75
+ elsif a.key? :lvalue
76
+ result[a[:attr].to_sym] = lookup(@context, a[:lvalue].to_s) || a[:lvalue]
77
+ elsif a.key? :svalue
78
+ result[a[:attr].to_sym] = a[:svalue].to_sym
79
+ end
80
+ end
81
+ result
82
+ end
83
+
84
+ # Returns an attribute string if the attribute has a value, for use in making HTML
85
+ #
86
+ # @param [Symbol] attr
87
+ # @param [Object] value
88
+ # @param [Object] default
89
+ # @return [String]
90
+ def attr_str(attr, value, default = nil)
91
+ v = value || default
92
+ v.present? ? " #{attr}=\"#{v}\"" : ''
93
+ end
94
+
95
+ def attrs_str(*attrs, reject: [])
96
+ attrs = @args.map { |a| a[:attr] }.reject { |a| a.nil? || reject.include?(a) } if attrs.empty?
97
+
98
+ result = []
99
+ attrs.each do |attr|
100
+ result << attr_str(attr, arg(attr))
101
+ end
102
+ result.join
103
+ end
104
+
105
+ # Lookup allows access to the assigned variables through the context
106
+ def lookup(context, name)
107
+ return unless name
108
+
109
+ context[name]
110
+ end
111
+
112
+ # For use with forms and inputs
113
+ def input(purpose, name)
114
+ form_model = lookup(@context, 'form.model')
115
+ form_class_name = lookup(@context, 'form.class_name')
116
+
117
+ # binding.pry if name == 'postal_code'
118
+
119
+ # order[order_lines_attributes][xxxx][product_name]
120
+ #
121
+ # order[order_attributes]_[lines_attributes][][product_name]
122
+
123
+ parts = @context.scopes.select { |scope| scope.key? 'form' }.map do |scope|
124
+ if scope['form'].attribute
125
+ scope['form'].attribute.to_s.gsub(/([a-z\-\_]+)/) { "[#{Regexp.last_match(1)}_attributes]" }.gsub(/\[\]$/, "[#{SecureRandom.uuid}]")
126
+ else
127
+ scope['form'].class_name.underscore.gsub('/', '_')
128
+ end
129
+ end
130
+ parts = parts.unshift("[#{name}]").reverse
131
+
132
+ return unless form_model && form_class_name && name
133
+
134
+ case purpose
135
+ when :id
136
+ parts.join('_').gsub('][', '_').tr('][', '')
137
+ when :value
138
+ # This is executed on the drop, drops provide the values for the form
139
+ form_model.send(name.to_sym)
140
+ when :name
141
+ # The original class's name dictates the name of the fields
142
+ parts.first + parts.slice(1..-1).join
143
+ when :error
144
+ parts.slice(1..-1).join('.')
145
+ when :checked
146
+ 'checked' if (input(:value, name) ? 1 : 0) == 1
147
+ end
148
+ end
149
+
150
+ # Returns the path for the input from the main form, say: origin.contact_name
151
+ def path_for_input(name)
152
+ parts = @context.scopes.select { |scope| scope.key? 'form' }.select { |scope| scope['form'].attribute }.map do |scope|
153
+ scope['form'].attribute
154
+ end
155
+ parts = parts.unshift(name.to_s).reverse
156
+ parts.join('.')
157
+ end
158
+
159
+ def real_object_from_drop(drop)
160
+ drop.instance_variable_get('@object')
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ #
4
+ # This is here to allow you to block certain tags for certain templates.
5
+ # Say in emails or exports it doesn't make sense to allow forms, you could exclude it as follows:
6
+ #
7
+ # template = Liquid::Template.parse(data, block_tags: %w[form])
8
+ #
9
+ module Liquid
10
+ class ParseContext
11
+ def block_tags
12
+ @options[:block_tags]
13
+ end
14
+ end
15
+
16
+ class BlockBody
17
+ alias parse_without_setting_parse_context parse
18
+
19
+ def parse(tokenizer, parse_context, &block)
20
+ @parse_context = parse_context
21
+ parse_without_setting_parse_context(tokenizer, parse_context, &block)
22
+ end
23
+
24
+ def registered_tags
25
+ allowed_tags = Template.tags.dup
26
+ if @parse_context.block_tags.present?
27
+ @parse_context.block_tags.each do |tag|
28
+ allowed_tags.delete(tag)
29
+ end
30
+ end
31
+ allowed_tags
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LiquidumBlock < Liquid::Block
4
+ include Liquidum::LiquidHelpers
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LiquidumTag < Liquid::Tag
4
+ include Liquidum::LiquidHelpers
5
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquidum
4
+ class Tag
5
+ require 'parslet'
6
+
7
+ class MiniP < Parslet::Parser
8
+ # Single character rules
9
+ rule(:squote) { str("'").repeat(1) }
10
+ rule(:dquote) { str('"').repeat(1) }
11
+
12
+ rule(:colon_or_eql) { match[":|="].repeat(1) }
13
+
14
+ rule(:space) { match('\s').repeat(1) }
15
+ rule(:space?) { space.maybe }
16
+
17
+ # Things
18
+ rule(:identifier) { (match('[a-zA-Z]') >> match('[a-zA-Z0-9\.\/\_\-\[\]\'\"]').repeat) }
19
+ rule(:symbol_literal) { colon_or_eql >> identifier.as(:svalue) }
20
+
21
+ rule(:nsqvalue) { match["^'"] }
22
+ rule(:ndqvalue) { match['^"'] }
23
+
24
+ rule(:literal_value) { identifier >> colon_or_eql >> identifier }
25
+
26
+ rule(:squoted_value) { squote >> nsqvalue.repeat.as(:value) >> squote }
27
+ rule(:dquoted_value) { dquote >> ndqvalue.repeat.as(:value) >> dquote }
28
+ rule(:quoted_value) { squoted_value | dquoted_value }
29
+
30
+ rule(:standalone_squoted_value) { squote >> nsqvalue.repeat.as(:quoted) >> squote }
31
+ rule(:standalone_dquoted_value) { dquote >> ndqvalue.repeat.as(:quoted) >> dquote }
32
+ rule(:standalone_quoted_value) { standalone_squoted_value | standalone_dquoted_value }
33
+
34
+ # Grammar parts
35
+ rule(:standalone) { identifier.as(:literal) >> space? }
36
+ rule(:quoted) { standalone_quoted_value >> space? }
37
+ rule(:attr_with_literal) { identifier.as(:attr) >> space? >> colon_or_eql >> space? >> identifier.as(:lvalue) >> space? }
38
+ rule(:attr_with_quoted) { identifier.as(:attr) >> space? >> colon_or_eql >> space? >> quoted_value >> space? }
39
+
40
+ rule(:attribute) { attr_with_quoted | attr_with_literal | quoted | standalone }
41
+ rule(:expression) { attribute.repeat }
42
+ root :expression
43
+ end
44
+
45
+ class Parser
46
+ attr_reader :args
47
+
48
+ def initialize(raw)
49
+ @args = MiniP.new.parse(raw)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Allow you to use helpers
4
+ #
5
+ # == Basic usage:
6
+ # {%helper 'user_index_path'%}
7
+ # {%helper 'check_box_tag' pet_dog%}
8
+ #
9
+ # == Advanced usage:
10
+ # {%helper 'user_index_path' user%}
11
+ #
12
+
13
+ class HelperTag < LiquidumTag
14
+ include Rails.application.routes.url_helpers
15
+
16
+ def render(context)
17
+ super
18
+
19
+ helper_args = sargs
20
+ helper_args = helper_args.concat([attr_args]) if attr_args.present?
21
+
22
+ if respond_to?(argv1.to_sym)
23
+ send(argv1.to_sym, *helper_args)
24
+ else
25
+ context.registers['controller'].helpers.send(argv1.to_sym, *helper_args)
26
+ end
27
+ end
28
+ end
29
+
30
+ Liquid::Template.register_tag('helper', HelperTag)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Render content from variable
4
+ #
5
+ # == Basic usage:
6
+ # {%render product.description}
7
+ #
8
+ class RenderTag < LiquidumTag
9
+ def render(context)
10
+ super
11
+
12
+ return unless argv1
13
+
14
+ template = Liquid::Template.parse(argv1)
15
+ template.render(context, registers: context.registers)
16
+ end
17
+ end
18
+
19
+ Liquid::Template.register_tag('render', RenderTag)
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Liquidum
4
+ VERSION = '1.0.17'
5
+ end
data/lib/liquidum.rb ADDED
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'liquid'
4
+ require 'tilt'
5
+ require 'sassc'
6
+ require "addressable/uri"
7
+
8
+ require 'liquidum/version'
9
+ require 'liquidum/engine'
10
+ require 'liquidum/configuration'
11
+ require 'liquidum/drop'
12
+
13
+ require 'liquidum/liquid/liquid_helpers'
14
+ require 'liquidum/liquid/liquid_template_extensions'
15
+ require 'liquidum/liquid/liquidum_block'
16
+ require 'liquidum/liquid/liquidum_tag'
17
+ require 'liquidum/liquid/parser'
18
+
19
+ module Liquidum
20
+ class Error < StandardError; end
21
+ class UnknownStepTypeError < Error; end
22
+
23
+ class LiquidumFileSystem
24
+ attr_reader :registers
25
+ def initialize(registers)
26
+ @registers = registers
27
+ end
28
+
29
+ def read_template_file(template_path)
30
+ current_content = registers['content']
31
+
32
+ contents = current_content.site.contents.published
33
+
34
+ content = contents.include(template_path).first
35
+ content&.data || ''
36
+ end
37
+ end
38
+
39
+ class << self
40
+ attr_reader :config
41
+
42
+ def setup
43
+ @config = Configuration.new
44
+ yield config
45
+ end
46
+
47
+ def render(content, options = {})
48
+ template = Liquid::Template.parse(content)
49
+ options[:assigns] ||= {}
50
+ options[:registers] ||= {}
51
+ options[:registers]['file_system'] = Liquidum.config.liquidum_file_system.constantize.new(options[:registers])
52
+ result = template.render(options[:context] || options[:assigns].stringify_keys, registers: options[:registers])
53
+
54
+ if template.errors.present?
55
+ Liquidum.config.logger.error '--- Template rendering errors: ' + '-' * 49
56
+ template.errors.map do |error|
57
+ next unless error.cause
58
+
59
+ Liquidum.config.logger.error error
60
+ Liquidum.config.logger.error '=> ' + error.cause.backtrace.first.to_s + ': ' + error.cause.message
61
+ end
62
+ Liquidum.config.logger.error '-' * 80
63
+ end
64
+
65
+ assigns = options[:assigns].deep_stringify_keys
66
+ assigns = assigns.merge(template.assigns.stringify_keys) if template.assigns
67
+ options[:registers].deep_merge!(template.registers.stringify_keys) if template.registers
68
+
69
+ result = Tilt[options[:filter]].new(options[:filter_options]) { result }.render if options[:filter].present? && Tilt[options[:filter]]
70
+ if options[:layout].present?
71
+ result = render(options[:layout], assigns: assigns.merge('content' => result), registers: options[:registers])
72
+ end
73
+
74
+ result
75
+ end
76
+ end
77
+ end