trmnl-liquid 0.4.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f21152bc330bde8f49978e8a4680e4e1ef79e19c8dd63e657ada328567c85e1c
4
- data.tar.gz: 58fc02a7aa0dbbb0e2fcf66d08019e59a76a63cb18f8bacf63e462df4d50f39b
3
+ metadata.gz: f138875593bd56d979eed237763c5e72a4dc5fdc3df60c35d6eeadffcb72bc87
4
+ data.tar.gz: 71bf4b24705129b781c295e3acf17fd70620df8773be3b15a1d4eaee7f734771
5
5
  SHA512:
6
- metadata.gz: bca98684b5fa6857652231dfbf9ce04cdb49d42feeb0025c3f2862f7eb589d92d1bb42d0142728e12c103fbac42d519a65d87e1cdf4a6b03f6855a9128807745
7
- data.tar.gz: 0eaad3ce40e20868d4560eacdd1810b1dc19ee8d9676907e87a588141e23949ab80c9bb7fd9daab07da7fea2b69e002311f6e9c83ec33e9880ddb3e6ea63ac06
6
+ metadata.gz: c0bff6334290e4eb2cbba125672faa0b9a63707bae29112e3d439f95401b259db3f3d0e8525f9c9fb61a77e1d38fe70b6bb4211929358f042f3ce59133a50a39
7
+ data.tar.gz: 909f77f15b1c3cc2027e295f97da11145978e0e80fcb3d46a5ca901b9fb17cc80cc6100eb729747ba21859122cefb10a52cdf23f4e4c75f9a425aae9d6a7e19d
checksums.yaml.gz.sig ADDED
Binary file
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Rockwell Schrock
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # TRMNL Liquid
2
+
3
+ A set of Liquid filters and tags used to render custom plugins for [TRMNL](https://trmnl.com).
4
+
5
+ ## Installation
6
+
7
+ Install the gem and add to the application's Gemfile by executing:
8
+
9
+ ```bash
10
+ bundle add trmnl-liquid
11
+ ```
12
+
13
+ If bundler is not being used to manage dependencies, install the gem by executing:
14
+
15
+ ```bash
16
+ gem install trmnl-liquid
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ For [Rails](https://rubyonrails.org) folks, you'll need to load the Rails feature but you *only need to do this once* (typically via an initializer). Example:
22
+
23
+ ``` ruby
24
+ TRMNL::Liquid.load :rails
25
+ ```
26
+
27
+ The above will load the Rails i18n support and other functionality for you to use this gem within a Rails application. For any application that isn't using Rails, we have fallbacks that should be sufficient but we're working to close that gap further. For instance, here's how you can configure this gem within a [Hanami](https://hanamirb.org) application as a *provider*:
28
+
29
+
30
+ ``` ruby
31
+ Hanami.app.register_provider :liquid, namespace: true do
32
+ prepare { require "trmnl/liquid" }
33
+
34
+ start do
35
+ default = TRMNL::Liquid.new { |environment| environment.error_mode = :strict }
36
+
37
+ renderer = lambda do |template, data, environment: default|
38
+ Liquid::Template.parse(template, environment:).render data
39
+ end
40
+
41
+ register :default, renderer
42
+ end
43
+ end
44
+ ```
45
+
46
+ If you ignore the provider logic and only focus on the body of the provider `start` life cyle, this can provide inspiration on how you can configure this gem further and use within your own application regardless of web stack.
47
+
48
+ ## Usage
49
+
50
+ Functionality is achieved by parsing a template with the option `{ environment: TRMNL::Liquid.new }`. The environment concept was introduced in [v5.6.0](https://github.com/Shopify/liquid/releases/tag/v5.6.0) of the `liquid` gem as a safer alternative to global registration of tags, filters, and so on. See [lib/trmnl/liquid/filters.rb](lib/trmnl/liquid/filters.rb) for the currently-supported filters.
51
+
52
+ ```ruby
53
+ require "trmnl/liquid"
54
+
55
+ markup = "Hello {{ count | number_with_delimiter }} people!"
56
+ environment = TRMNL::Liquid.new # same arguments as Liquid::Environment.build
57
+ template = Liquid::Template.parse(markup, environment: environment)
58
+ rendered = template.render(count: 1337)
59
+ # => "Hello 1,337 people!"
60
+ ```
61
+
62
+
63
+ Additionally, the `{% template %}` tag defines reusable chunks of markup:
64
+
65
+ ```liquid
66
+ {% template say_hello %}
67
+ <h1>Why hello there, {{ name }}!</h1>
68
+ {% endtemplate %}
69
+
70
+ {% render "say_hello", name: "General Kenobi" %}
71
+ ```
72
+
73
+ For more information, check out our help guides:
74
+
75
+ - [Liquid 101](https://help.trmnl.com/en/articles/10671186-liquid-101)
76
+ - [Advanced Liquid](https://help.trmnl.com/en/articles/10693981-advanced-liquid)
77
+ - [Custom Plugin Filters](https://help.trmnl.com/en/articles/10347358-custom-plugin-filters)
78
+
79
+ ### Rails (optional)
80
+
81
+ The following is optional and only works for Rails applications. Once again, you'll want to enable Rails support by adding the following as an initializer:
82
+
83
+ ``` ruby
84
+ TRMNL::Liquid.load :rails
85
+ ```
86
+
87
+ Then you can include internationalization and/or number and text formatting as described below.
88
+
89
+ #### Internationalization
90
+
91
+ Some filter functions (e.g. `number_to_currency`, `l_word`, and `l_date`) require translations provided by the [rails-i18n](https://rubygems.org/gems/rails-i18n) and [trmnl-i18n](https://rubygems.org/gems/trmnl-i18n) gems. These dependencies are optional, and if missing will fall back to default behavior. If you want to internationalize, also include these gems:
92
+
93
+ ```ruby
94
+ # optional peer dependencies
95
+ gem "rails-i18n", "~> 8.0"
96
+ gem "trmnl-i18n", github: "usetrmnl/trmnl-i18n", branch: "main" # recommended for the latest changes
97
+ ```
98
+
99
+ #### Number and Text Formatting
100
+
101
+ This gem does not _require_ ActionView, but it will leverage ActionView helpers if they are available.
102
+
103
+ ```ruby
104
+ # optional peer dependency
105
+ gem "actionview", "~> 8.0"
106
+ ```
107
+
108
+ ## Development
109
+
110
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
111
+
112
+ To publish, run the following:
113
+
114
+ ``` ruby
115
+ # Step 0: You only need to do this once.
116
+ bundle install gemsmith
117
+
118
+ # Step 1: Edit the version number in trmnl-liquid.gemspec and update it to your desired version.
119
+
120
+ # Step 2: Publish the new version.
121
+ gemsmith --publish
122
+ ```
123
+
124
+ ## Contributing
125
+
126
+ Enhancements, bugs, code reviews, and other requests are welcome via [GitHub](https://github.com/usetrmnl/trmnl-liquid).
127
+
128
+ ## License
129
+
130
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,17 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TRMNL
2
4
  module Liquid
3
5
  # library-native formatting functions that don't rely on ActionView helpers
4
6
  module Fallback
5
- extend self
7
+ module_function
6
8
 
7
- def number_with_delimiter(number, delimiter, separator)
8
- str = number.to_s
9
+ # :reek:TooManyStatements
10
+ # rubocop:todo Metrics/MethodLength
11
+ def number_with_delimiter number, delimiter, separator
12
+ value = number.to_s
9
13
 
10
14
  # return early if it's not a simple numeric-like string
11
- return str unless str.match?(/\A-?\d+(\.\d+)?\z/)
15
+ return value unless value.match?(/\A-?\d+(\.\d+)?\z/)
12
16
 
13
- integer, fractional = str.split('.')
14
- negative = integer.start_with?('-')
17
+ integer, fractional = value.split "."
18
+ negative = integer.start_with? "-"
15
19
  integer = integer[1..] if negative
16
20
 
17
21
  integer_with_delimiters = integer.reverse.scan(/\d{1,3}/).join(delimiter).reverse
@@ -23,39 +27,40 @@ module TRMNL
23
27
  integer_with_delimiters
24
28
  end
25
29
  end
30
+ # rubocop:enable Metrics/MethodLength
26
31
 
27
- def number_to_currency(number, unit, delimiter, separator, precision)
28
- result = number_with_delimiter(number, delimiter, separator)
29
- dollars, cents = result.split(separator)
32
+ # rubocop:disable Metrics/ParameterLists
33
+ def number_to_currency number, unit, delimiter, separator, precision
34
+ result = number_with_delimiter number, delimiter, separator
35
+ dollars, cents = result.split separator
30
36
 
31
37
  if precision <= 0
32
38
  "#{unit}#{dollars}"
33
39
  else
34
- cents = cents.to_s[0..(precision - 1)].ljust(precision, '0')
40
+ cents = cents.to_s[0..(precision - 1)].ljust precision, "0"
35
41
  "#{unit}#{dollars}#{separator}#{cents}"
36
42
  end
37
43
  end
44
+ # rubocop:enable Metrics/ParameterLists
45
+
46
+ # :reek:TooManyStatements
47
+ def ordinalize number
48
+ return "#{number}th" if (11..13).cover? number % 100
38
49
 
39
- def ordinalize(number)
40
- suffix =
41
- if (11..13).include?(number % 100)
42
- 'th'
43
- else
44
- case number % 10
45
- when 1 then 'st'
46
- when 2 then 'nd'
47
- when 3 then 'rd'
48
- else 'th'
49
- end
50
- end
50
+ suffix = case number % 10
51
+ when 1 then "st"
52
+ when 2 then "nd"
53
+ when 3 then "rd"
54
+ else "th"
55
+ end
51
56
 
52
57
  "#{number}#{suffix}"
53
58
  end
54
59
 
55
- def pluralize(count, singular, plural)
60
+ def pluralize count, singular, plural
56
61
  plural ||= "#{singular}s"
57
- count == 1 ? "1 #{singular}" : "#{count} #{plural}"
62
+ count == 1 ? "1 #{singular}" : "#{count} #{plural}"
58
63
  end
59
64
  end
60
65
  end
61
- end
66
+ end
@@ -1,225 +1,198 @@
1
- require 'date'
2
- require 'json'
3
- require 'redcarpet'
4
- require 'tzinfo'
5
- require 'rqrcode'
6
- require 'securerandom'
7
-
8
- require_relative 'fallback'
9
-
10
- # optional
11
- %w[
12
- i18n
13
- action_view
14
- active_support/core_ext/integer/inflections
15
- ].each do |lib|
16
- begin
17
- require lib
18
- rescue LoadError
19
- nil
20
- end
21
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "redcarpet"
5
+ require "rqrcode"
6
+ require "securerandom"
7
+ require "tzinfo"
8
+
9
+ require_relative "fallback"
22
10
 
23
11
  module TRMNL
24
12
  module Liquid
13
+ # rubocop:todo Metrics/ModuleLength
25
14
  module Filters
26
- def append_random(var)
27
- "#{var}#{SecureRandom.hex(2)}"
28
- end
15
+ def append_random(value) = "#{value}#{SecureRandom.hex 2}"
29
16
 
30
- def days_ago(num, tz = 'Etc/UTC')
31
- tzinfo = TZInfo::Timezone.get(tz)
32
- tzinfo.now.to_date - num.to_i
17
+ def days_ago value, timezone = "Etc/UTC"
18
+ TZInfo::Timezone.get(timezone).now.to_date - value.to_i
33
19
  end
34
20
 
35
- def group_by(collection, key)
36
- collection.group_by { |obj| obj[key] }
37
- end
21
+ def group_by(collection, key) = collection.group_by { it[key] }
38
22
 
39
- def find_by(collection, key, value, fallback = nil)
23
+ # :reek:ControlParameter
24
+ # rubocop:todo Metrics/ParameterLists
25
+ def find_by collection, key, value, fallback = nil
40
26
  collection.find { |obj| obj[key] == value } || fallback
41
27
  end
28
+ # rubocop:enable Metrics/ParameterLists
42
29
 
43
- def markdown_to_html(markdown)
44
- markdown ||= ''
45
- renderer = Redcarpet::Render::HTML.new(_render_options = {})
46
- service = Redcarpet::Markdown.new(renderer, _extensions = {})
47
- service.render(markdown)
30
+ def markdown_to_html markdown
31
+ markdown ||= ""
32
+ renderer = Redcarpet::Render::HTML.new _render_options = {}
33
+ service = Redcarpet::Markdown.new renderer, _extensions = {}
34
+ service.render markdown
48
35
  end
49
36
 
50
- def number_with_delimiter(number, delimiter = ',', separator = '.')
51
- if helpers.respond_to?(:number_with_delimiter)
52
- helpers.number_with_delimiter(number, delimiter: delimiter, separator: separator)
37
+ def number_with_delimiter number, delimiter = ",", separator = "."
38
+ if RailsHelpers.respond_to? :number_with_delimiter
39
+ RailsHelpers.number_with_delimiter number, delimiter: delimiter, separator: separator
53
40
  else
54
- Fallback.number_with_delimiter(number, delimiter, separator)
41
+ Fallback.number_with_delimiter number, delimiter, separator
55
42
  end
56
43
  end
57
44
 
58
- def number_to_currency(number, unit_or_locale = '$', delimiter = ',', separator = '.', precision = 2)
59
- if helpers.respond_to?(:number_to_currency)
60
- cur_switcher = with_i18n(:unit) do |i18n|
45
+ # :reek:TooManyStatements
46
+ # rubocop:todo Metrics/ParameterLists
47
+ def number_to_currency number,
48
+ unit_or_locale = "$",
49
+ delimiter = ",",
50
+ separator = ".",
51
+ precision = 2
52
+ if RailsHelpers.respond_to? :number_to_currency
53
+ cur_switcher = with_i18n :unit do |i18n|
61
54
  i18n.available_locales.include?(unit_or_locale.to_sym) ? :locale : :unit
62
55
  end
63
- opts = { delimiter:, separator:, precision: }.merge(cur_switcher => unit_or_locale)
64
- helpers.number_to_currency(number, **opts)
56
+ opts = {delimiter:, separator:, precision:}.merge cur_switcher => unit_or_locale
57
+ RailsHelpers.number_to_currency(number, **opts)
65
58
  else
66
- Fallback.number_to_currency(number, unit_or_locale, delimiter, separator, precision)
59
+ Fallback.number_to_currency number, unit_or_locale, delimiter, separator, precision
67
60
  end
68
61
  end
62
+ # rubocop:enable Metrics/ParameterLists
69
63
 
70
- def l_word(word, locale)
71
- with_i18n("custom_plugins.#{word}") do |i18n|
72
- i18n.t("custom_plugins.#{word}", locale: locale)
73
- end
64
+ def l_word word, locale
65
+ with_i18n("custom_plugins.#{word}") { |i18n| i18n.t "custom_plugins.#{word}", locale: }
74
66
  end
75
67
 
76
- def l_date(date, format, locale = 'en')
77
- with_i18n(date.to_s) do |i18n|
78
- format = format.to_sym unless format.include?('%')
79
- i18n.l(to_datetime(date), format: format, locale: locale)
68
+ # :reek:FeatureEnvy
69
+ def l_date value, format, locale = "en"
70
+ with_i18n value.to_s do |i18n|
71
+ format = format.to_sym unless format.include? "%"
72
+ i18n.l to_time(value), format:, locale:
80
73
  end
81
74
  end
82
75
 
83
- def map_to_i(collection)
84
- collection.map(&:to_i)
85
- end
76
+ def map_to_i(collection) = collection.map(&:to_i)
86
77
 
87
- def pluralize(singular, count, opts = {})
88
- plural = opts['plural']
89
- locale = opts['locale'] || with_i18n(nil) { |i18n| i18n.locale } || 'en'
78
+ # :reek:FeatureEnvy
79
+ # rubocop:todo Style/OptionHash
80
+ def pluralize singular, count, options = {}
81
+ plural = options["plural"]
82
+ locale = options["locale"] || with_i18n(nil, &:locale) || "en"
90
83
 
91
- if helpers.respond_to?(:pluralize)
92
- helpers.pluralize(count, singular, plural: plural, locale: locale)
84
+ if RailsHelpers.respond_to? :pluralize
85
+ RailsHelpers.pluralize count, singular, plural: plural, locale: locale
93
86
  else
94
- Fallback.pluralize(count, singular, plural)
87
+ Fallback.pluralize count, singular, plural
95
88
  end
96
89
  end
90
+ # rubocop:enable Style/OptionHash
97
91
 
98
- def json(obj)
99
- JSON.generate(obj)
100
- end
92
+ def json(value) = JSON.generate value
101
93
 
102
- def parse_json(obj)
103
- JSON.parse(obj)
104
- end
94
+ def parse_json(value) = JSON.parse value
105
95
 
106
96
  def sample(array) = array.sample
107
97
 
98
+ # :reek:TooManyStatements
108
99
  # source: https://github.com/jekyll/jekyll/blob/40ac06ed3e95325a07868dd2ac419e409af823b6/lib/jekyll/filters.rb#L209
109
- def where_exp(input, variable, expression)
110
- return input unless input.respond_to?(:select)
100
+ def where_exp input, variable, expression
101
+ return input unless input.respond_to? :select
111
102
 
112
- input = input.values if input.is_a?(Hash)
103
+ input = input.values if input.is_a? Hash
104
+ condition = parse_condition expression
113
105
 
114
- condition = parse_condition(expression)
115
106
  @context.stack do
116
107
  input.select do |object|
117
108
  @context[variable] = object
118
- condition.evaluate(@context)
109
+ condition.evaluate @context
119
110
  end
120
111
  end || []
121
112
  end
122
113
 
123
- def ordinalize(date_str, strftime_exp)
124
- date = Date.parse(date_str)
125
-
126
- ordinal_day = if date.day.respond_to?(:ordinalize)
127
- date.day.ordinalize
128
- else
129
- Fallback.ordinalize(date.day)
130
- end
131
-
132
- date.strftime(strftime_exp.gsub('<<ordinal_day>>', ordinal_day))
114
+ # :reek:FeatureEnvy
115
+ def ordinalize value, strftime_format
116
+ time = to_time value
117
+ day = time.day
118
+ ordinal_day = day.respond_to?(:ordinalize) ? day.ordinalize : Fallback.ordinalize(day)
119
+
120
+ time.strftime strftime_format.gsub("<<ordinal_day>>", ordinal_day)
133
121
  end
134
122
 
135
- def qr_code(data, size = 11, level = '')
136
- level.downcase!
137
- level = 'h' unless %w[l m q h].include?(level)
123
+ # rubocop:todo Metrics/MethodLength
124
+ def qr_code data, size = 11, level = ""
125
+ level = "h" unless %w[l m q h].include? level.downcase
138
126
 
139
127
  qrcode = RQRCode::QRCode.new(data, level:)
140
128
  qrcode.as_svg(
141
- color: '000',
142
- fill: 'fff',
143
- shape_rendering: 'crispEdges',
129
+ color: "000",
130
+ fill: "fff",
131
+ shape_rendering: "crispEdges",
144
132
  module_size: size,
145
133
  standalone: true,
146
134
  use_path: true,
147
135
  svg_attributes: {
148
- class: 'qr-code'
136
+ class: "qr-code"
149
137
  }
150
138
  )
151
139
  end
140
+ # rubocop:enable Metrics/MethodLength
152
141
 
153
142
  private
154
143
 
155
- def with_i18n(fallback, &block)
144
+ def with_i18n fallback
156
145
  if defined?(::I18n)
157
- block.call(::I18n)
146
+ yield ::I18n
158
147
  else
159
148
  fallback
160
149
  end
161
150
  end
162
151
 
163
- def to_datetime(obj)
164
- case obj
165
- when DateTime
166
- obj
167
- when Date
168
- obj.to_datetime
169
- when Time
170
- DateTime.parse(obj.iso8601)
171
- else
172
- DateTime.parse(obj.to_s)
152
+ def to_time value
153
+ case value
154
+ when "now", "today" then Time.now
155
+ when Integer then Time.at(value)
156
+ else Time.parse value
173
157
  end
174
158
  end
175
159
 
176
- def parse_condition(exp)
177
- parser = ::Liquid::Parser.new(exp)
178
- condition = parse_binary_comparison(parser)
160
+ def parse_condition expression
161
+ parser = ::Liquid::Parser.new expression
162
+ condition = parse_binary_comparison parser
179
163
 
180
- parser.consume(:end_of_string)
164
+ parser.consume :end_of_string
181
165
  condition
182
166
  end
183
167
 
184
- def parse_binary_comparison(parser)
185
- condition = parse_comparison(parser)
168
+ # :reek:TooManyStatements
169
+ # :reek:DuplicateMethodCall
170
+ def parse_binary_comparison parser
171
+ condition = parse_comparison parser
186
172
  first_condition = condition
187
- while (binary_operator = parser.id?('and') || parser.id?('or'))
188
- child_condition = parse_comparison(parser)
189
- condition.send(binary_operator, child_condition)
173
+
174
+ while (binary_operator = parser.id?("and") || parser.id?("or"))
175
+ child_condition = parse_comparison parser
176
+ condition.public_send binary_operator, child_condition
190
177
  condition = child_condition
191
178
  end
179
+
192
180
  first_condition
193
181
  end
194
182
 
195
- def parse_comparison(parser)
196
- left_operand = ::Liquid::Expression.parse(parser.expression)
197
- operator = parser.consume?(:comparison)
183
+ # :reek:DuplicateMethodCall
184
+ def parse_comparison parser
185
+ left_operand = ::Liquid::Expression.parse parser.expression
186
+ operator = parser.consume? :comparison
198
187
 
199
188
  # No comparison-operator detected. Initialize a Liquid::Condition using only left operand
200
- return ::Liquid::Condition.new(left_operand) unless operator
189
+ return ::Liquid::Condition.new left_operand unless operator
201
190
 
202
191
  # Parse what remained after extracting the left operand and the `:comparison` operator
203
192
  # and initialize a Liquid::Condition object using the operands and the comparison-operator
204
- ::Liquid::Condition.new(left_operand, operator, ::Liquid::Expression.parse(parser.expression))
205
- end
206
-
207
- class Helpers
208
- %w[
209
- ::ActionView::Helpers::TextHelper
210
- ::ActionView::Helpers::NumberHelper
211
- ].each do |name|
212
- begin
213
- include Object.const_get(name)
214
- rescue NameError
215
- next
216
- end
217
- end
218
- end
219
-
220
- def helpers
221
- @helpers ||= Helpers.new
193
+ ::Liquid::Condition.new left_operand, operator, ::Liquid::Expression.parse(parser.expression)
222
194
  end
223
195
  end
196
+ # rubocop:enable Metrics/ModuleLength
224
197
  end
225
198
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TRMNL
4
+ module Liquid
5
+ # An in-memory file system for storing custom templates defined with {% template [name] %} tags.
6
+ class MemorySystem < ::Liquid::BlankFileSystem
7
+ def initialize
8
+ super
9
+ @templates = {}
10
+ end
11
+
12
+ def register name, body
13
+ templates[name] = body
14
+ end
15
+
16
+ def read_template_file name
17
+ templates[name] || fail(::Liquid::FileSystemError, "Template not found: #{name}.")
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :templates
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_view"
4
+
5
+ module TRMNL
6
+ module Liquid
7
+ # Defines Rails helpers for use in filters.
8
+ module RailsHelpers
9
+ extend ActionView::Helpers::TextHelper
10
+ extend ActionView::Helpers::NumberHelper
11
+ end
12
+ end
13
+ end
@@ -1,33 +1,44 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TRMNL
2
4
  module Liquid
3
- # The {% template [name] %} tag block is used in conjunction with InlineTemplatesFileSystem to allow users to define
4
- # custom templates within the context of the current Liquid template. Generally speaking, they will define their own
5
- # templates in the "shared" markup content, which is prepended to the individual screen templates before rendering.
5
+ # The {% template [name] %} tag block is used in conjunction with InlineTemplatesFileSystem to
6
+ # allow users to define custom templates within the context of the current Liquid template.
7
+ # Generally speaking, they will define their own templates in the "shared" markup content,
8
+ # which is prepended to the individual screen templates before rendering.
6
9
  class TemplateTag < ::Liquid::Block
7
- NAME_REGEX = %r{\A[a-zA-Z0-9_/]+\z}
10
+ NAME_PATTERN = %r(\A[a-zA-Z0-9_/]+\z)
8
11
 
9
- def initialize(tag_name, markup, options)
12
+ def initialize tag_name, markup, options
10
13
  super
14
+
11
15
  @name = markup.strip
16
+ @body = +""
12
17
  end
13
18
 
14
- def parse(tokens)
15
- @body = ""
19
+ def parse tokens
20
+ body.clear
21
+
16
22
  while (token = tokens.shift)
17
23
  break if token.strip == "{% endtemplate %}"
18
24
 
19
- @body << token
25
+ body << token
20
26
  end
21
27
  end
22
28
 
23
- def render(context)
24
- unless @name =~ NAME_REGEX
25
- return "Liquid error: invalid template name #{@name.inspect} - template names must contain only letters, numbers, underscores, and slashes"
29
+ def render context
30
+ unless NAME_PATTERN.match? name
31
+ return "Liquid error: invalid template name #{name.inspect} - template names " \
32
+ "must contain only letters, numbers, underscores, and slashes"
26
33
  end
27
34
 
28
- context.registers[:file_system].register(@name, @body.strip)
29
- ''
35
+ context.registers[:file_system].register name, body.strip
36
+ ""
30
37
  end
38
+
39
+ private
40
+
41
+ attr_reader :name, :body
31
42
  end
32
43
  end
33
- end
44
+ end
data/lib/trmnl/liquid.rb CHANGED
@@ -1,36 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'liquid'
3
+ require "liquid"
4
+ require "trmnl/liquid/filters"
5
+ require "trmnl/liquid/memory_system"
6
+ require "trmnl/liquid/template_tag"
4
7
 
5
- require 'trmnl/liquid/filters'
6
- require 'trmnl/liquid/file_system'
7
- require 'trmnl/liquid/template_tag'
8
- require 'trmnl/liquid/version'
8
+ module TRMNL
9
+ # Main namespace.
10
+ module Liquid
11
+ def self.build_environment(...)
12
+ warn "`#{self.class}##{__method__}` is deprecated, use `new` instead.", category: :deprecated
13
+ new(...)
14
+ end
9
15
 
10
- # optional
11
- begin
12
- require 'trmnl/i18n'
13
- rescue LoadError
14
- nil
15
- end
16
+ # :reek:TooManyStatements
17
+ def self.load key
18
+ case key
19
+ when :rails
20
+ require "trmnl/liquid/rails_helpers"
16
21
 
17
- if defined?(::TRMNL::I18n)
18
- ::TRMNL::I18n.load_locales
19
- end
22
+ require "trmnl/i18n"
20
23
 
21
- if Gem.loaded_specs['rails-i18n']
22
- ::I18n.load_path += Pathname.new(Gem.loaded_specs['rails-i18n'].full_gem_path).join('rails', 'locale').glob('*.yml')
23
- end
24
+ TRMNL::I18n.load_locales
25
+ path = Pathname Gem.loaded_specs["rails-i18n"].full_gem_path
26
+ ::I18n.load_path += path.join("rails/locale").glob("*.yml")
27
+ else fail KeyError, "Unable to load extension due to invalid key: #{key.inspect}."
28
+ end
29
+ end
24
30
 
25
- module TRMNL
26
- module Liquid
27
- def self.build_environment(*args)
28
- ::Liquid::Environment.build(*args) do |env|
29
- env.register_filter(TRMNL::Liquid::Filters)
30
- env.register_tag('template', TRMNL::Liquid::TemplateTag)
31
- env.file_system = TRMNL::Liquid::FileSystem.new
32
- yield(env) if block_given?
31
+ def self.new(file_system: TRMNL::Liquid::MemorySystem.new, **)
32
+ ::Liquid::Environment.build(file_system:, **) do |environment|
33
+ environment.register_filter TRMNL::Liquid::Filters
34
+ environment.register_tag "template", TRMNL::Liquid::TemplateTag
35
+ yield environment if block_given?
33
36
  end
34
37
  end
35
38
  end
36
- end
39
+ end
data/trmnl-liquid.gemspec CHANGED
@@ -1,17 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/trmnl/liquid/version"
4
-
5
3
  Gem::Specification.new do |spec|
6
4
  spec.name = "trmnl-liquid"
7
- spec.version = TRMNL::Liquid::VERSION
5
+ spec.version = "0.5.0"
8
6
  spec.authors = ["TRMNL"]
9
- spec.email = ["engineering@usetrmnl.com"]
7
+ spec.email = ["engineering@trmnl.com"]
10
8
  spec.homepage = "https://github.com/usetrmnl/trmnl-liquid"
11
-
12
9
  spec.summary = "Liquid templating engine for TRMNL plugins"
13
10
  spec.license = "MIT"
14
- spec.required_ruby_version = ">= 3.2.0"
15
11
 
16
12
  spec.metadata = {
17
13
  "bug_tracker_uri" => "https://github.com/usetrmnl/trmnl-liquid/issues",
@@ -22,15 +18,16 @@ Gem::Specification.new do |spec|
22
18
  "source_code_uri" => "https://github.com/usetrmnl/trmnl-liquid"
23
19
  }
24
20
 
25
- # Specify which files should be added to the gem when it is released.
26
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
27
- spec.files = Dir["*.gemspec", "lib/**/*"]
28
- spec.require_paths = ["lib"]
21
+ spec.signing_key = Gem.default_key_path
22
+ spec.cert_chain = [Gem.default_cert_path]
29
23
 
30
- spec.add_dependency "base64"
31
- spec.add_dependency "liquid", "~> 5.6"
24
+ spec.required_ruby_version = ">= 4.0"
25
+
26
+ spec.add_dependency "liquid", "~> 5.11"
32
27
  spec.add_dependency "redcarpet", "~> 3.6"
33
- spec.add_dependency "rqrcode", "~> 3.0"
34
- spec.add_dependency "securerandom", ">= 0.3"
35
- spec.add_dependency "tzinfo"
28
+ spec.add_dependency "rqrcode", "~> 3.2"
29
+ spec.add_dependency "tzinfo", "~> 2.0"
30
+
31
+ spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
32
+ spec.files = Dir["*.gemspec", "lib/**/*"]
36
33
  end
data.tar.gz.sig ADDED
Binary file
metadata CHANGED
@@ -1,42 +1,54 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trmnl-liquid
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TRMNL
8
8
  bindir: bin
9
- cert_chain: []
9
+ cert_chain:
10
+ - |
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIENjCCAp6gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBBMQ8wDQYDVQQDDAZicm9v
13
+ a2UxGjAYBgoJkiaJk/IsZAEZFgphbGNoZW1pc3RzMRIwEAYKCZImiZPyLGQBGRYC
14
+ aW8wHhcNMjUwMzIyMTQ1NDE3WhcNMjYwMzIyMTQ1NDE3WjBBMQ8wDQYDVQQDDAZi
15
+ cm9va2UxGjAYBgoJkiaJk/IsZAEZFgphbGNoZW1pc3RzMRIwEAYKCZImiZPyLGQB
16
+ GRYCaW8wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCro8tj5/E1Hg88
17
+ f4qfiwPVd2zJQHvdYt4GHVvuHRRgx4HGhJuNp+4BId08RBn7V6V1MW6MY3kezRBs
18
+ M+7QOQ4b1xNLTvY7FYQB1wGK5a4x7TTokDrPYQxDB2jmsdDYCzVbIMrAvUfcecRi
19
+ khyGZCdByiiCl4fKv77P12tTT+NfsvXkLt/AYCGwjOUyGKTQ01Z6eC09T27GayPH
20
+ QQvIkakyFgcJtzSyGzs8bzK5q9u7wQ12MNTjJoXzW69lqp0oNvDylu81EiSUb5S6
21
+ QzzPxZBiRB1sgtbt1gUbVI262ZDq1gR+HxPFmp+Cgt7ZLIJZAtesQvtcMzseXpfn
22
+ hpmm0Sw22KGhRAy/mqHBRhDl5HqS1SJp2Ko3lcnpXeFResp0HNlt8NSu13vhC08j
23
+ GUHU9MyIXbFOsnp3K3ADrAVjPWop8EZkmUR3MV/CUm00w2cZHCSGiXl1KMpiVKvk
24
+ Ywr1gd2ZME4QLSo+EXUtLxDUa/W3xnBS8dBOuMMz02FPWYr3PN8CAwEAAaM5MDcw
25
+ CQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0OBBYEFAFgmv0tYMZnItuPycSM
26
+ F5wykJEVMA0GCSqGSIb3DQEBCwUAA4IBgQBlzRfyAYx/fCFjizS0Npxw4+4T3aYL
27
+ hbXoDqQRWjxuhFZcXUymhz3r8/Ltyri9lSof8grzB+8/+mrMVms7Gwt5qolk6zdn
28
+ FkySGy/jmpN12ldOHFbBEnyVBZNBvOBVb8zkkw8PhiHdBdXOUm4Jy39yJvBLfjcC
29
+ iM1aeWPmgPy1GbvZU+leRGZLt6dRIR9oCDXcWLRjha8xLMoz6Yn9fJBYexBA3iEz
30
+ h5S7pn4AX/JhVRiSyl8pAy4jEKydpyQrliH3gHkpNmUS/XDczP+9xX1bAB4BvqL2
31
+ NCxMcQ+hiJNqCKpPgHxaOOHZfIxV33logIuPEQ8NryHAwZ9ZWnwtYDE8kQGGKskI
32
+ Kkm6QT474hZl7MpwiJjWgW313CR7jUEekQahX1QxCxHPI7LSrKpno0plH3uWIOQp
33
+ KUlkb9uyACBgyRO52ZHiDVI8YvtU5O/j9pSes9/3XgvBeC1onx4qWp+uRX7eVsYS
34
+ GiijocTc3enZVrXERetaXj8/9XWs3fB3HWY=
35
+ -----END CERTIFICATE-----
10
36
  date: 1980-01-02 00:00:00.000000000 Z
11
37
  dependencies:
12
- - !ruby/object:Gem::Dependency
13
- name: base64
14
- requirement: !ruby/object:Gem::Requirement
15
- requirements:
16
- - - ">="
17
- - !ruby/object:Gem::Version
18
- version: '0'
19
- type: :runtime
20
- prerelease: false
21
- version_requirements: !ruby/object:Gem::Requirement
22
- requirements:
23
- - - ">="
24
- - !ruby/object:Gem::Version
25
- version: '0'
26
38
  - !ruby/object:Gem::Dependency
27
39
  name: liquid
28
40
  requirement: !ruby/object:Gem::Requirement
29
41
  requirements:
30
42
  - - "~>"
31
43
  - !ruby/object:Gem::Version
32
- version: '5.6'
44
+ version: '5.11'
33
45
  type: :runtime
34
46
  prerelease: false
35
47
  version_requirements: !ruby/object:Gem::Requirement
36
48
  requirements:
37
49
  - - "~>"
38
50
  - !ruby/object:Gem::Version
39
- version: '5.6'
51
+ version: '5.11'
40
52
  - !ruby/object:Gem::Dependency
41
53
  name: redcarpet
42
54
  requirement: !ruby/object:Gem::Requirement
@@ -57,54 +69,44 @@ dependencies:
57
69
  requirements:
58
70
  - - "~>"
59
71
  - !ruby/object:Gem::Version
60
- version: '3.0'
72
+ version: '3.2'
61
73
  type: :runtime
62
74
  prerelease: false
63
75
  version_requirements: !ruby/object:Gem::Requirement
64
76
  requirements:
65
77
  - - "~>"
66
78
  - !ruby/object:Gem::Version
67
- version: '3.0'
68
- - !ruby/object:Gem::Dependency
69
- name: securerandom
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - ">="
73
- - !ruby/object:Gem::Version
74
- version: '0.3'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - ">="
80
- - !ruby/object:Gem::Version
81
- version: '0.3'
79
+ version: '3.2'
82
80
  - !ruby/object:Gem::Dependency
83
81
  name: tzinfo
84
82
  requirement: !ruby/object:Gem::Requirement
85
83
  requirements:
86
- - - ">="
84
+ - - "~>"
87
85
  - !ruby/object:Gem::Version
88
- version: '0'
86
+ version: '2.0'
89
87
  type: :runtime
90
88
  prerelease: false
91
89
  version_requirements: !ruby/object:Gem::Requirement
92
90
  requirements:
93
- - - ">="
91
+ - - "~>"
94
92
  - !ruby/object:Gem::Version
95
- version: '0'
93
+ version: '2.0'
96
94
  email:
97
- - engineering@usetrmnl.com
95
+ - engineering@trmnl.com
98
96
  executables: []
99
97
  extensions: []
100
- extra_rdoc_files: []
98
+ extra_rdoc_files:
99
+ - LICENSE.txt
100
+ - README.md
101
101
  files:
102
+ - LICENSE.txt
103
+ - README.md
102
104
  - lib/trmnl/liquid.rb
103
105
  - lib/trmnl/liquid/fallback.rb
104
- - lib/trmnl/liquid/file_system.rb
105
106
  - lib/trmnl/liquid/filters.rb
107
+ - lib/trmnl/liquid/memory_system.rb
108
+ - lib/trmnl/liquid/rails_helpers.rb
106
109
  - lib/trmnl/liquid/template_tag.rb
107
- - lib/trmnl/liquid/version.rb
108
110
  - trmnl-liquid.gemspec
109
111
  homepage: https://github.com/usetrmnl/trmnl-liquid
110
112
  licenses:
@@ -123,14 +125,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
125
  requirements:
124
126
  - - ">="
125
127
  - !ruby/object:Gem::Version
126
- version: 3.2.0
128
+ version: '4.0'
127
129
  required_rubygems_version: !ruby/object:Gem::Requirement
128
130
  requirements:
129
131
  - - ">="
130
132
  - !ruby/object:Gem::Version
131
133
  version: '0'
132
134
  requirements: []
133
- rubygems_version: 3.6.9
135
+ rubygems_version: 4.0.5
134
136
  specification_version: 4
135
137
  summary: Liquid templating engine for TRMNL plugins
136
138
  test_files: []
metadata.gz.sig ADDED
Binary file
@@ -1,21 +0,0 @@
1
- module TRMNL
2
- module Liquid
3
- # This in-memory "file system" is the backing storage for custom templates defined {% template [name] %} tags.
4
- class FileSystem < ::Liquid::BlankFileSystem
5
- def initialize
6
- super
7
- @templates = {}
8
- end
9
-
10
- # called by Markup::LiquidTemplateTag to save users' custom shared templates via our custom {% template %} tag
11
- def register(name, body)
12
- @templates[name] = body
13
- end
14
-
15
- # called by ::Liquid::Template for {% render 'foo' %} when rendering screen markup
16
- def read_template_file(name)
17
- @templates[name] || raise(::Liquid::FileSystemError, "Template not found: #{name}")
18
- end
19
- end
20
- end
21
- end
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
- module TRMNL
3
- module Liquid
4
- VERSION = "0.4.0"
5
- end
6
- end