trmnl_preview 0.5.5 → 0.5.7

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: 16a333009aab09891c12d6fe7bf3b8662912cd6e376f31c1a4997de5a6c93314
4
- data.tar.gz: cf57e05fa4ba41059e55fb7281d7644ee07772459b63503ebe4a4023fae14085
3
+ metadata.gz: 4f3284ba2c2879994af5aabd394416875771b008510fffb27798551e3a9c85ad
4
+ data.tar.gz: 9f664bc7134763f88dab40b5075aa77bf08892bf68b1f1e29771cc47a3f2b95e
5
5
  SHA512:
6
- metadata.gz: 43048f869b37533061b92b7e06b2623b8d47e35098942f535301655727efc726f09c004330f277fb1ee7d0a73f3b5785283846b1404e0d8a6b92cec4f94b9105
7
- data.tar.gz: 3a8672a65e06a537fef9c51e759657076bcda0a320cc4f2ee8a211c27bad31bcab09993ab592c520c3103805e2077a5f907c67c2423184688fd612d0c307e276
6
+ metadata.gz: 9e5685d0a9359f229087bbc1d59e2969e87cb54251eb90ca73787f4731ce6b085c829428721838543596f3cfd1c1d7958b363fcd8f3b2d5d0f981490c14bfdd3
7
+ data.tar.gz: 372e8d359ee7ef461ccffb12f8dd6d8a328e0c4d32178e62d4e3e358f38f3ac40b971d25ac63c1cf3b97388b3f483b0c814568f40c4c8d30169e6dfdc9a95811
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.7
4
+
5
+ - Use the `trmnl-liquid` gem so tags and filters stay up-to-date with the core offering
6
+
7
+ ## 0.5.6
8
+
9
+ - Fixed bug that left blank plugins on server after upload failed
10
+ - Fixed bug creating upload.zip after previous upload had failed
11
+ - Added support to read API key fromk `TRMNL_API_KEY` environment variable (@andi4000)
12
+ - Fixed `init` command in Docker container (@jbarreiros)
13
+ - Automatically remove ephemeral Docker container after exit (@andi4000)
14
+
3
15
  ## 0.5.5
4
16
 
5
17
  - Added dark mode (@stephenyeargin)
data/README.md CHANGED
@@ -4,6 +4,8 @@ A basic self-hosted web server to ease the development and sharing of [TRMNL](ht
4
4
 
5
5
  [Liquid](https://shopify.github.io/liquid/) templates are rendered leveraging the [TRMNL Design System](https://usetrmnl.com/framework). They may be generated as HTML (faster, and a good approximation of the final result) or as PNG images (slower, but more accurate).
6
6
 
7
+ Custom Liquid filters and tags are provided by the [trmnl-liquid](https://github.com/usetrmnl/trmnl-liquid) gem.
8
+
7
9
  The server watches the filesystem for changes to the Liquid templates, seamlessly updating the preview without the need to refresh.
8
10
 
9
11
  ![Screenshot](docs/preview.png)
@@ -31,11 +33,11 @@ This is the structure of a plugin project:
31
33
  You can start building a plugin locally, then `push` it to the TRMNL server for display on your device.
32
34
 
33
35
  ```sh
34
- trmnlp init my_plugin # generate
35
- cd my_plugin
36
- trmnlp serve # develop locally
37
- trmnlp login # authenticate
38
- trmnlp push # upload
36
+ trmnlp init [my_plugin] # generate
37
+ cd [my_plugin]
38
+ trmnlp serve # develop locally
39
+ trmnlp login # authenticate
40
+ trmnlp push # upload
39
41
  ```
40
42
 
41
43
  ## Modifying an Existing Plugin
@@ -43,13 +45,19 @@ trmnlp push # upload
43
45
  If you have built a plugin with the web-based editor, you can `clone` it, work on it locally, and `push` changes back to the server.
44
46
 
45
47
  ```sh
46
- trmnlp login # authenticate
47
- trmnlp clone my_plugin [id] # first download
48
- cd my_plugin
49
- trmnlp serve # develop locally
50
- trmnlp push # upload
48
+ trmnlp login # authenticate
49
+ trmnlp clone [my_plugin] [id] # first download
50
+ cd [my_plugin]
51
+ trmnlp serve # develop locally
52
+ trmnlp push # upload
51
53
  ```
52
54
 
55
+ ## Authentication
56
+
57
+ The `trmnlp login` command saves your API key to `~/.config/trmnlp/config.yml`.
58
+
59
+ If an environment variable is more convenient (for example in a CI/CD pipeline), you can set `$TRMNL_API_KEY` instead.
60
+
53
61
  ## Running trmnlp
54
62
 
55
63
  The `bin/trmnlp` script is provided as a convenience. It will use the local Ruby gem if available, falling back to the `trmnl/trmnlp` Docker image.
@@ -53,6 +53,16 @@ module TRMNLP
53
53
  end
54
54
  end
55
55
 
56
+ def delete_plugin_setting(id)
57
+ response = conn.delete("plugin_settings/#{id}")
58
+
59
+ if response.status == 204
60
+ true
61
+ else
62
+ raise Error, "failed to delete plugin setting: #{response.status} #{response.body}"
63
+ end
64
+ end
65
+
56
66
  private
57
67
 
58
68
  attr_reader :config
@@ -6,7 +6,9 @@ module TRMNLP
6
6
  def call
7
7
  if config.app.logged_in?
8
8
  anonymous_key = config.app.api_key[0..10] + '*' * (config.app.api_key.length - 11)
9
- output "Currently authenticated as: #{anonymous_key}"
9
+ output "Currently authenticated as: #{anonymous_key}"
10
+ confirm = prompt("You are already authenticated. Do you want to re-authenticate? (y/N): ")
11
+ return unless confirm.strip.downcase == 'y'
10
12
  end
11
13
 
12
14
  output "Please visit #{config.app.account_uri} to grab your API key, then paste it here."
@@ -11,6 +11,7 @@ module TRMNLP
11
11
  authenticate!
12
12
 
13
13
  is_new = false
14
+ zip_path = 'upload.zip'
14
15
 
15
16
  api = APIClient.new(config)
16
17
 
@@ -27,10 +28,7 @@ module TRMNLP
27
28
  raise Error, 'aborting' unless answer == 'y' || answer == 'yes'
28
29
  end
29
30
 
30
- size = 0
31
-
32
- zip_path = 'upload.zip'
33
- f = Zip::File.open(zip_path, Zip::File::CREATE) do |zip_file|
31
+ Zip::File.open(zip_path, Zip::File::CREATE) do |zip_file|
34
32
  paths.src_files.each do |file|
35
33
  zip_file.add(File.basename(file), file)
36
34
  end
@@ -40,7 +38,6 @@ module TRMNLP
40
38
  paths.plugin_config.write(response.dig('data', 'settings_yaml'))
41
39
 
42
40
  size = File.size(zip_path)
43
- File.delete(zip_path)
44
41
 
45
42
  output <<~HEREDOC
46
43
  Uploaded plugin (#{size} bytes)
@@ -55,6 +52,15 @@ module TRMNLP
55
52
  #{config.app.playlists_uri}
56
53
  HEREDOC
57
54
  end
55
+ rescue
56
+ if is_new && plugin_settings_id
57
+ output 'Error during creation, cleaning up...'
58
+ api.delete_plugin_setting(plugin_settings_id)
59
+ end
60
+
61
+ raise
62
+ ensure
63
+ File.delete(zip_path) if File.exist?(zip_path)
58
64
  end
59
65
  end
60
66
  end
@@ -17,7 +17,11 @@ module TRMNLP
17
17
  def logged_in? = api_key && !api_key.empty?
18
18
  def logged_out? = !logged_in?
19
19
 
20
- def api_key = @config['api_key']
20
+ def api_key
21
+ env_api_key = ENV['TRMNL_API_KEY']
22
+ return env_api_key if env_api_key && !env_api_key.empty?
23
+ @config['api_key']
24
+ end
21
25
 
22
26
  def api_key=(key)
23
27
  @config['api_key'] = key
@@ -1,3 +1,4 @@
1
+ require 'trmnl/liquid'
1
2
  require 'yaml'
2
3
 
3
4
  module TRMNLP
@@ -33,7 +34,7 @@ module TRMNLP
33
34
  # for interpolating custom_fields into polling_* options
34
35
  def with_custom_fields(value)
35
36
  custom_fields_with_env = custom_fields.transform_values { |v| with_env(v) }
36
- Liquid::Template.parse(value).render(custom_fields_with_env)
37
+ parse_liquid(value).render(custom_fields_with_env)
37
38
  end
38
39
 
39
40
  def time_zone = @config['time_zone'] || 'UTC'
@@ -42,7 +43,11 @@ module TRMNLP
42
43
 
43
44
  # for interpolating ENV vars into custom_fields
44
45
  def with_env(value)
45
- Liquid::Template.parse(value).render({ 'env' => ENV.to_h })
46
+ parse_liquid(value).render({ 'env' => ENV.to_h })
47
+ end
48
+
49
+ def parse_liquid(contents)
50
+ Liquid::Template.parse(contents, environment: TRMNL::Liquid.build_environment)
46
51
  end
47
52
  end
48
53
  end
@@ -3,11 +3,10 @@ require 'erb'
3
3
  require 'faraday'
4
4
  require 'filewatcher'
5
5
  require 'json'
6
- require 'liquid'
6
+ require 'trmnl/liquid'
7
7
 
8
8
  require_relative 'config'
9
9
  require_relative 'paths'
10
- require_relative '../markup/template'
11
10
 
12
11
  module TRMNLP
13
12
  class Context
@@ -129,7 +128,7 @@ module TRMNLP
129
128
  full_markup = template_path.read
130
129
  end
131
130
 
132
- user_template = Markup::Template.parse(full_markup, environment: liquid_environment)
131
+ user_template = Liquid::Template.parse(full_markup, environment: liquid_environment)
133
132
  user_template.render(user_data)
134
133
  rescue StandardError => e
135
134
  e.message
@@ -206,10 +205,7 @@ module TRMNLP
206
205
  end
207
206
 
208
207
  def liquid_environment
209
- @liquid_environment ||= Liquid::Environment.build do |env|
210
- env.register_filter(Markup::CustomLiquidFilters)
211
- env.register_tag('template', Markup::TemplateTag)
212
-
208
+ @liquid_environment ||= TRMNL::Liquid.build_environment do |env|
213
209
  config.project.user_filters.each do |module_name, relative_path|
214
210
  require paths.root_dir.join(relative_path)
215
211
  env.register_filter(Object.const_get(module_name))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TRMNLP
4
- VERSION = "0.5.5".freeze
4
+ VERSION = "0.5.7".freeze
5
5
  end
@@ -13,6 +13,7 @@ fi
13
13
  if command -v docker &> /dev/null
14
14
  then
15
15
  docker run \
16
+ --rm \
16
17
  --publish 4567:4567 \
17
18
  --volume "$(pwd):/plugin" \
18
19
  trmnl/trmnlp "$@"
@@ -27,4 +28,4 @@ Or install Docker:
27
28
 
28
29
  https://docs.docker.com/get-docker/"
29
30
 
30
- exit 1
31
+ exit 1
@@ -43,9 +43,8 @@ Gem::Specification.new do |spec|
43
43
  spec.add_dependency "faye-websocket", "~> 0.11.3"
44
44
 
45
45
  # HTML rendering
46
- spec.add_dependency "liquid", "~> 5.6"
47
46
  spec.add_dependency "activesupport", "~> 8.0"
48
- spec.add_dependency "actionview", "~> 8.0"
47
+ spec.add_dependency "trmnl-liquid", "~> 0.2.0"
49
48
 
50
49
  # PNG rendering
51
50
  spec.add_dependency 'puppeteer-ruby', '~> 0.45.6'
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trmnl_preview
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.5
4
+ version: 0.5.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rockwell Schrock
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-27 00:00:00.000000000 Z
10
+ date: 2025-08-19 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: sinatra
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: 0.11.3
68
- - !ruby/object:Gem::Dependency
69
- name: liquid
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '5.6'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '5.6'
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: activesupport
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -94,19 +80,19 @@ dependencies:
94
80
  - !ruby/object:Gem::Version
95
81
  version: '8.0'
96
82
  - !ruby/object:Gem::Dependency
97
- name: actionview
83
+ name: trmnl-liquid
98
84
  requirement: !ruby/object:Gem::Requirement
99
85
  requirements:
100
86
  - - "~>"
101
87
  - !ruby/object:Gem::Version
102
- version: '8.0'
88
+ version: 0.2.0
103
89
  type: :runtime
104
90
  prerelease: false
105
91
  version_requirements: !ruby/object:Gem::Requirement
106
92
  requirements:
107
93
  - - "~>"
108
94
  - !ruby/object:Gem::Version
109
- version: '8.0'
95
+ version: 0.2.0
110
96
  - !ruby/object:Gem::Dependency
111
97
  name: puppeteer-ruby
112
98
  requirement: !ruby/object:Gem::Requirement
@@ -260,10 +246,6 @@ files:
260
246
  - README.md
261
247
  - bin/rake
262
248
  - bin/trmnlp
263
- - lib/markup/custom_liquid_filters.rb
264
- - lib/markup/inline_templates_file_system.rb
265
- - lib/markup/template.rb
266
- - lib/markup/template_tag.rb
267
249
  - lib/trmnlp.rb
268
250
  - lib/trmnlp/api_client.rb
269
251
  - lib/trmnlp/app.rb
@@ -1,36 +0,0 @@
1
- require 'action_view'
2
- require 'singleton'
3
-
4
- module Markup
5
- module CustomLiquidFilters
6
- class ActionViewHelpers
7
- include Singleton
8
- include ActionView::Helpers
9
- end
10
-
11
- def number_with_delimiter(number, delimiter = ',', separator = ',')
12
- ActionViewHelpers.instance.number_with_delimiter(number, delimiter:, separator:)
13
- end
14
-
15
- def number_to_currency(number, unit = '$', delimiter = ',', separator = '.')
16
- ActionViewHelpers.instance.number_to_currency(number, unit: unit, delimiter:, separator:)
17
- end
18
-
19
- def l_word(word, locale)
20
- I18n.t("custom_plugins.#{word}", locale: locale)
21
- end
22
-
23
- def l_date(date, format, locale = 'en')
24
- format = format.to_sym unless format.include?('%')
25
- I18n.l(date.to_datetime, :format => format, locale: locale)
26
- end
27
-
28
- def pluralize(singular, count)
29
- ActionViewHelpers.instance.pluralize(count, singular)
30
- end
31
-
32
- def json(obj)
33
- JSON.generate(obj)
34
- end
35
- end
36
- end
@@ -1,19 +0,0 @@
1
- module Markup
2
- # This in-memory "file system" is the backing storage for custom templates defined {% template [name] %} tags.
3
- class InlineTemplatesFileSystem < Liquid::BlankFileSystem
4
- def initialize
5
- super
6
- @templates = {}
7
- end
8
-
9
- # called by Markup::LiquidTemplateTag to save users' custom shared templates via our custom {% template %} tag
10
- def register(name, body)
11
- @templates[name] = body
12
- end
13
-
14
- # called by Liquid::Template for {% render 'foo' %} when rendering screen markup
15
- def read_template_file(name)
16
- @templates[name] || raise(Liquid::FileSystemError, "Template not found: #{name}")
17
- end
18
- end
19
- end
@@ -1,17 +0,0 @@
1
- # get all files in the current directory as this file
2
- Pathname.new(__dir__).glob('*.rb').each { |file| require file }
3
-
4
- module Markup
5
- # A very thin wrapper around Liquid::Template with TRMNL-specific functionality.
6
- class Template < Liquid::Template
7
- def self.parse(*)
8
- template = super
9
-
10
- # set up a temporary in-memory file system for custom user templates, via the magic :file_system register
11
- # which will override the default file system
12
- template.registers[:file_system] = InlineTemplatesFileSystem.new
13
-
14
- template
15
- end
16
- end
17
- end
@@ -1,31 +0,0 @@
1
- module Markup
2
- # The {% template [name] %} tag block is used in conjunction with InlineTemplatesFileSystem to allow users to define
3
- # custom templates within the context of the current Liquid template. Generally speaking, they will define their own
4
- # templates in the "shared" markup content, which is prepended to the individual screen templates before rendering.
5
- class TemplateTag < Liquid::Block
6
- NAME_REGEX = %r{\A[a-zA-Z0-9_/]+\z}
7
-
8
- def initialize(tag_name, markup, options)
9
- super
10
- @name = markup.strip
11
- end
12
-
13
- def parse(tokens)
14
- @body = ""
15
- while (token = tokens.shift)
16
- break if token.strip == "{% endtemplate %}"
17
-
18
- @body << token
19
- end
20
- end
21
-
22
- def render(context)
23
- unless @name =~ NAME_REGEX
24
- return "Liquid error: invalid template name #{@name.inspect} - template names must contain only letters, numbers, underscores, and slashes"
25
- end
26
-
27
- context.registers[:file_system].register(@name, @body.strip)
28
- ''
29
- end
30
- end
31
- end