trmnl_preview 0.7.1 → 0.7.2

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/bin/trmnlp +15 -13
  3. metadata +17 -301
  4. data/CHANGELOG.md +0 -139
  5. data/LICENSE.txt +0 -21
  6. data/README.md +0 -144
  7. data/bin/rake +0 -27
  8. data/lib/trmnlp/api_client.rb +0 -86
  9. data/lib/trmnlp/app.rb +0 -103
  10. data/lib/trmnlp/cli.rb +0 -68
  11. data/lib/trmnlp/commands/base.rb +0 -40
  12. data/lib/trmnlp/commands/build.rb +0 -21
  13. data/lib/trmnlp/commands/clone.rb +0 -30
  14. data/lib/trmnlp/commands/init.rb +0 -55
  15. data/lib/trmnlp/commands/login.rb +0 -27
  16. data/lib/trmnlp/commands/pull.rb +0 -43
  17. data/lib/trmnlp/commands/push.rb +0 -67
  18. data/lib/trmnlp/commands/serve.rb +0 -25
  19. data/lib/trmnlp/commands.rb +0 -1
  20. data/lib/trmnlp/config/app.rb +0 -47
  21. data/lib/trmnlp/config/plugin.rb +0 -75
  22. data/lib/trmnlp/config/project.rb +0 -54
  23. data/lib/trmnlp/config.rb +0 -15
  24. data/lib/trmnlp/context.rb +0 -238
  25. data/lib/trmnlp/paths.rb +0 -58
  26. data/lib/trmnlp/screen_generator.rb +0 -240
  27. data/lib/trmnlp/version.rb +0 -5
  28. data/lib/trmnlp.rb +0 -14
  29. data/templates/init/.trmnlp.yml +0 -14
  30. data/templates/init/bin/trmnlp +0 -44
  31. data/templates/init/src/full.liquid +0 -1
  32. data/templates/init/src/half_horizontal.liquid +0 -1
  33. data/templates/init/src/half_vertical.liquid +0 -1
  34. data/templates/init/src/quadrant.liquid +0 -1
  35. data/templates/init/src/settings.yml +0 -15
  36. data/templates/init/src/shared.liquid +0 -1
  37. data/trmnl_preview.gemspec +0 -68
  38. data/web/public/highlight/highlight.min.js +0 -315
  39. data/web/public/highlight/styles/atom-one-dark.min.css +0 -1
  40. data/web/public/index.css +0 -113
  41. data/web/public/index.js +0 -83
  42. data/web/public/trmnl-picker.js +0 -656
  43. data/web/views/index.erb +0 -68
  44. data/web/views/render_html.erb +0 -23
data/bin/rake DELETED
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- #
5
- # This file was generated by Bundler.
6
- #
7
- # The application 'rake' is installed as part of a gem, and
8
- # this file is here to facilitate running it.
9
- #
10
-
11
- ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
-
13
- bundle_binstub = File.expand_path("bundle", __dir__)
14
-
15
- if File.file?(bundle_binstub)
16
- if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
- load(bundle_binstub)
18
- else
19
- abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
- Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
- end
22
- end
23
-
24
- require "rubygems"
25
- require "bundler/setup"
26
-
27
- load Gem.bin_path("rake", "rake")
@@ -1,86 +0,0 @@
1
- require 'faraday'
2
- require 'faraday/multipart'
3
-
4
- require_relative 'config'
5
-
6
- module TRMNLP
7
- class APIClient
8
- def initialize(config)
9
- @config = config
10
- end
11
-
12
- def get_plugin_setting_archive(id)
13
- response = conn.get("plugin_settings/#{id}/archive")
14
-
15
- if response.status == 200
16
- temp_file = Tempfile.new(["plugin_settings_#{id}", '.zip'])
17
- temp_file.binmode
18
- temp_file.write(response.body)
19
- temp_file.rewind
20
-
21
- # return the temp file IO
22
- temp_file
23
- else
24
- raise Error, "failed to download plugin settings archive: #{response.status} #{response.body}"
25
- end
26
- end
27
-
28
- def post_plugin_setting_archive(id, path)
29
- filepart = Faraday::Multipart::FilePart.new(path, 'application/zip')
30
-
31
- payload = {
32
- file: filepart
33
- }
34
-
35
- response = conn.post("plugin_settings/#{id}/archive", payload)
36
-
37
- filepart.close
38
-
39
- if response.status == 200
40
- JSON.parse(response.body)
41
- else
42
- raise Error, "failed to upload plugin settings archive: #{response.status} #{response.body}"
43
- end
44
- end
45
-
46
- def post_plugin_setting(params)
47
- response = conn.post("plugin_settings", params.to_json, content_type: 'application/json')
48
-
49
- if response.status == 200
50
- JSON.parse(response.body)
51
- else
52
- raise Error, "failed to create plugin setting: #{response.status} #{response.body}"
53
- end
54
- end
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
-
66
- private
67
-
68
- attr_reader :config
69
-
70
- def api_uri = config.app.api_uri
71
-
72
- def conn
73
- @conn ||= Faraday.new(url: api_uri, headers:) do |f|
74
- f.request :multipart
75
- end
76
- end
77
-
78
-
79
- def headers
80
- {
81
- 'Authorization' => "Bearer #{config.app.api_key}",
82
- 'User-Agent' => "trmnlp/#{VERSION}",
83
- }
84
- end
85
- end
86
- end
data/lib/trmnlp/app.rb DELETED
@@ -1,103 +0,0 @@
1
-
2
- require 'faye/websocket'
3
- require 'sinatra'
4
- require 'sinatra/base'
5
-
6
- require_relative 'context'
7
- require_relative 'screen_generator'
8
-
9
- module TRMNLP
10
- class App < Sinatra::Base
11
- # Sinatra settings
12
- set :views, File.join(File.dirname(__FILE__), '..', '..', 'web', 'views')
13
- set :public_folder, File.join(File.dirname(__FILE__), '..', '..', 'web', 'public')
14
-
15
- def initialize(*args)
16
- super
17
-
18
- @context = settings.context
19
-
20
- @context.poll_data
21
-
22
- @context.start_filewatcher if @context.config.project.live_render?
23
-
24
- @live_reload_clients = []
25
- @context.on_view_change do |view, user_data|
26
- @live_reload_clients.each do |ws|
27
- payload = {
28
- 'type' => 'reload',
29
- 'view' => view,
30
- 'user_data' => user_data
31
- }
32
-
33
- ws.send(payload.to_json)
34
- end
35
- end
36
- end
37
-
38
- post '/webhook' do
39
- @context.put_webhook(request.body.read)
40
- "OK"
41
- end
42
-
43
- get '/' do
44
- redirect '/full'
45
- end
46
-
47
- get '/data' do
48
- content_type :json
49
- JSON.pretty_generate(@context.user_data)
50
- end
51
-
52
- get '/live_reload' do
53
- ws = Faye::WebSocket.new(request.env)
54
-
55
- ws.on(:open) do |event|
56
- @live_reload_clients << ws
57
- end
58
-
59
- ws.on(:close) do |event|
60
- @live_reload_clients.delete(ws)
61
- end
62
-
63
- ws.rack_response
64
- end
65
-
66
- get '/poll' do
67
- @context.poll_data
68
- redirect back
69
- end
70
-
71
- VIEWS.each do |view|
72
- get "/#{view}" do
73
- @view = view
74
- @user_data = JSON.pretty_generate(@context.user_data)
75
- @live_reload = @context.config.project.live_render?
76
-
77
- erb :index
78
- end
79
-
80
- get "/render/#{view}.html" do
81
- @context.render_full_page(view, params)
82
- end
83
-
84
- get "/render/#{view}.png" do
85
- @view = view
86
- html = @context.render_full_page(view, params)
87
-
88
- # Parse optional rendering params (sent by the web UI for PNG output)
89
- width = params[:width] && params[:width].to_i
90
- height = params[:height] && params[:height].to_i
91
- color_depth = params[:color_depth] && params[:color_depth].to_i
92
-
93
- generator = ScreenGenerator.new(html, image: true, width: width, height: height, color_depth: color_depth)
94
- temp_image = generator.process
95
-
96
- send_file temp_image.path, type: 'image/png', disposition: 'inline'
97
-
98
- temp_image.close
99
- temp_image.unlink
100
- end
101
- end
102
- end
103
- end
data/lib/trmnlp/cli.rb DELETED
@@ -1,68 +0,0 @@
1
- require 'thor'
2
-
3
- require_relative '../trmnlp'
4
- require_relative '../trmnlp/commands'
5
-
6
- module TRMNLP
7
- class CLI < Thor
8
- package_name 'trmnlp'
9
-
10
- class_option :dir, type: :string, default: Dir.pwd, aliases: '-d',
11
- desc: 'Plugin project directory'
12
-
13
- class_option :quiet, type: :boolean, default: false, desc: 'Suppress output', aliases: '-q'
14
-
15
- def self.exit_on_failure? = true
16
-
17
- def self.default_bind = File.exist?('/.dockerenv') ? '0.0.0.0' : '127.0.0.1'
18
-
19
- desc 'build', 'Generate static HTML files'
20
- def build
21
- Commands::Build.new(options).call
22
- end
23
-
24
- desc 'login', 'Authenticate with TRMNL server'
25
- def login
26
- Commands::Login.new(options).call
27
- end
28
-
29
- desc 'init NAME', 'Start a new plugin project'
30
- method_option :skip_liquid, type: :boolean, default: false, desc: 'Skip generating liquid templates'
31
- def init(name)
32
- Commands::Init.new(options).call(name)
33
- end
34
-
35
- desc 'clone NAME ID', 'Copy a plugin project from TRMNL server'
36
- def clone(name, id)
37
- Commands::Clone.new(options).call(name, id)
38
- end
39
-
40
- desc 'pull', 'Download latest plugin settings from TRMNL server'
41
- method_option :force, type: :boolean, default: false, aliases: '-f',
42
- desc: 'Skip confirmation prompts'
43
- method_option :id, type: :string, aliases: '-i', desc: 'Plugin settings ID'
44
- def pull
45
- Commands::Pull.new(options).call
46
- end
47
-
48
- desc 'push', 'Upload latest plugin settings to TRMNL server'
49
- method_option :force, type: :boolean, default: false, aliases: '-f',
50
- desc: 'Skip confirmation prompts'
51
- method_option :id, type: :string, aliases: '-i', desc: 'Plugin settings ID'
52
- def push
53
- Commands::Push.new(options).call
54
- end
55
-
56
- desc 'serve', 'Start a local dev server'
57
- method_option :bind, type: :string, default: default_bind, aliases: '-b', desc: 'Bind address'
58
- method_option :port, type: :numeric, default: 4567, aliases: '-p', desc: 'Port number'
59
- def serve
60
- Commands::Serve.new(options).call
61
- end
62
-
63
- desc 'version', 'Show version'
64
- def version
65
- puts VERSION
66
- end
67
- end
68
- end
@@ -1,40 +0,0 @@
1
- require 'thor/core_ext/hash_with_indifferent_access'
2
-
3
- require_relative '../context'
4
-
5
- module TRMNLP
6
- module Commands
7
- class Base
8
- include Thor::CoreExt
9
-
10
- def initialize(options = HashWithIndifferentAccess.new)
11
- @options = HashWithIndifferentAccess.new(options)
12
- @context = Context.new(@options.dir)
13
- end
14
-
15
- def call
16
- raise NotImplementedError
17
- end
18
-
19
- protected
20
-
21
- attr_accessor :options, :context
22
-
23
- def config = context.config
24
- def paths = context.paths
25
-
26
- def authenticate!
27
- raise Error, "please run `trmnlp login`" unless config.app.logged_in?
28
- end
29
-
30
- def output(message)
31
- puts(message) unless options.quiet?
32
- end
33
-
34
- def prompt(message)
35
- print message
36
- $stdin.gets.chomp
37
- end
38
- end
39
- end
40
- end
@@ -1,21 +0,0 @@
1
- require_relative 'base'
2
-
3
- module TRMNLP
4
- module Commands
5
- class Build < Base
6
- def call
7
- context.validate!
8
- context.poll_data
9
- context.paths.create_build_dir
10
-
11
- VIEWS.each do |view|
12
- output_path = context.paths.build_dir.join("#{view}.html")
13
- output "Writing #{output_path}..."
14
- output_path.write(context.render_full_page(view))
15
- end
16
-
17
- output "Done!"
18
- end
19
- end
20
- end
21
- end
@@ -1,30 +0,0 @@
1
- require_relative 'base'
2
- require_relative 'pull'
3
-
4
- module TRMNLP
5
- module Commands
6
- class Clone < Base
7
- def call(directory_name, id)
8
- authenticate!
9
-
10
- destination_path = Pathname.new(options.dir).join(directory_name)
11
- raise Error, "directory #{destination_path} already exists, aborting" if destination_path.exist?
12
-
13
- Init.new(dir: options.dir, skip_liquid: true, quiet: true).call(directory_name)
14
-
15
- Pull.new(dir: destination_path.to_s, force: true, id: id).call
16
-
17
- output <<~HEREDOC
18
-
19
- To start the local server:
20
-
21
- cd #{Pathname.new(destination_path).relative_path_from(Dir.pwd)} && trmnlp serve
22
- HEREDOC
23
- end
24
-
25
- private
26
-
27
- def template_dir = paths.templates_dir.join('init')
28
- end
29
- end
30
- end
@@ -1,55 +0,0 @@
1
- require 'fileutils'
2
-
3
- require_relative 'base'
4
-
5
- module TRMNLP
6
- module Commands
7
- class Init < Base
8
- def call(name)
9
- destination_dir = Pathname.new(options.dir).join(name)
10
-
11
- unless destination_dir.exist?
12
- output "Creating #{destination_dir}"
13
- destination_dir.mkpath
14
- end
15
-
16
- template_dir.glob('**/{*,.*}').each do |source_pathname|
17
- next if source_pathname.directory?
18
- next if options.skip_liquid && source_pathname.extname == '.liquid'
19
-
20
- relative_pathname = source_pathname.relative_path_from(template_dir)
21
- destination_pathname = destination_dir.join(relative_pathname)
22
- destination_pathname.dirname.mkpath
23
-
24
- if destination_pathname.exist?
25
- answer = prompt("#{destination_pathname} already exists. Overwrite? (y/n): ").downcase
26
- if answer != 'y'
27
- output "Skipping #{destination_pathname}"
28
- next
29
- end
30
- end
31
-
32
- output "Creating #{destination_pathname}"
33
- FileUtils.cp(source_pathname, destination_pathname)
34
- end
35
-
36
- output <<~HEREDOC
37
-
38
- To start the local server:
39
-
40
- cd #{Pathname.new(destination_dir).relative_path_from(Dir.pwd)}
41
- trmnlp serve
42
-
43
- To publish the plugin:
44
-
45
- trmnlp login
46
- trmnlp push
47
- HEREDOC
48
- end
49
-
50
- private
51
-
52
- def template_dir = paths.templates_dir.join('init')
53
- end
54
- end
55
- end
@@ -1,27 +0,0 @@
1
- require_relative 'base'
2
-
3
- module TRMNLP
4
- module Commands
5
- class Login < Base
6
- def call
7
- if config.app.logged_in?
8
- anonymous_key = config.app.api_key[0..10] + '*' * (config.app.api_key.length - 11)
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'
12
- end
13
-
14
- output "Please visit #{config.app.account_uri} to grab your API key, then paste it here."
15
-
16
- api_key = prompt("API Key: ")
17
- raise Error, "API key cannot be empty" if api_key.empty?
18
- raise Error, "Invalid API key; did you copy it from the right place?" unless api_key.start_with?("user_")
19
-
20
- config.app.api_key = api_key
21
- config.app.save
22
-
23
- output "Saved changes to #{paths.app_config}"
24
- end
25
- end
26
- end
27
- end
@@ -1,43 +0,0 @@
1
- require 'zip'
2
-
3
- require_relative 'base'
4
- require_relative '../api_client'
5
-
6
- module TRMNLP
7
- module Commands
8
- class Pull < Base
9
- def call
10
- context.validate!
11
- authenticate!
12
-
13
- plugin_settings_id = options.id || config.plugin.id
14
- raise Error, 'plugin ID must be specified' if plugin_settings_id.nil?
15
-
16
- unless options.force
17
- answer = prompt("Local plugin files will be overwritten. Are you sure? (y/n) ").downcase
18
- raise Error, 'aborting' unless answer == 'y' || answer == 'yes'
19
- end
20
-
21
- api = APIClient.new(config)
22
- tempfile = api.get_plugin_setting_archive(plugin_settings_id)
23
- size = 0
24
-
25
- begin
26
- Zip::File.open(tempfile.path) do |zip_file|
27
- zip_file.each do |entry|
28
- dest_path = paths.src_dir.join(entry.name)
29
- dest_path.dirname.mkpath
30
- zip_file.extract(entry, dest_path) { true } # overwrite existing
31
- end
32
- end
33
-
34
- size = File.size(tempfile.path)
35
- ensure
36
- tempfile.close
37
- end
38
-
39
- puts "Downloaded plugin (#{size} bytes)"
40
- end
41
- end
42
- end
43
- end
@@ -1,67 +0,0 @@
1
- require 'zip'
2
-
3
- require_relative 'base'
4
- require_relative '../api_client'
5
-
6
- module TRMNLP
7
- module Commands
8
- class Push < Base
9
- def call
10
- context.validate!
11
- authenticate!
12
-
13
- is_new = false
14
- zip_path = 'upload.zip'
15
-
16
- api = APIClient.new(config)
17
-
18
- plugin_settings_id = options.id || config.plugin.id
19
- if plugin_settings_id.nil?
20
- output 'Creating a new plugin on the server...'
21
- response = api.post_plugin_setting(name: 'New TRMNLP Plugin', plugin_id: 37) # hardcoded id for private_plugin
22
- plugin_settings_id = response.dig('data', 'id')
23
- is_new = true
24
- end
25
-
26
- unless is_new || options.force
27
- answer = prompt("Plugin settings on the server will be overwritten. Are you sure? (y/n) ").downcase
28
- raise Error, 'aborting' unless answer == 'y' || answer == 'yes'
29
- end
30
-
31
- Zip::File.open(zip_path, Zip::File::CREATE) do |zip_file|
32
- paths.src_files.each do |file|
33
- zip_file.add(File.basename(file), file)
34
- end
35
- end
36
-
37
- response = api.post_plugin_setting_archive(plugin_settings_id, zip_path)
38
- paths.plugin_config.write(response.dig('data', 'settings_yaml'))
39
-
40
- size = File.size(zip_path)
41
-
42
- output <<~HEREDOC
43
- Uploaded plugin (#{size} bytes)
44
- Dashboard: #{config.app.edit_plugin_settings_uri(plugin_settings_id)}
45
- HEREDOC
46
-
47
- if is_new
48
- output <<~HEREDOC
49
-
50
- IMPORTANT! Don't forget to add it to your device playlist!
51
-
52
- #{config.app.playlists_uri}
53
- HEREDOC
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)
64
- end
65
- end
66
- end
67
- end
@@ -1,25 +0,0 @@
1
- require 'zip'
2
-
3
- require_relative 'base'
4
- require_relative '../api_client'
5
-
6
- module TRMNLP
7
- module Commands
8
- class Serve < Base
9
- def call
10
- context.validate!
11
-
12
- # Must come AFTER parsing options
13
- require_relative '../app'
14
-
15
- # Now we can configure things
16
- App.set(:context, context)
17
- App.set(:bind, options.bind)
18
- App.set(:port, options.port)
19
-
20
- # Finally, start the app!
21
- App.run!
22
- end
23
- end
24
- end
25
- end
@@ -1 +0,0 @@
1
- Dir[File.join(__dir__, 'commands', '*.rb')].each { |file| require file }
@@ -1,47 +0,0 @@
1
- require 'yaml'
2
-
3
- module TRMNLP
4
- class Config
5
- # Stores trmnlp-wide configuration (irrespective of the current plugin)
6
- class App
7
- def initialize(paths)
8
- @paths = paths
9
- @config = read_config
10
- end
11
-
12
- def save
13
- paths.app_config_dir.mkpath
14
- paths.app_config.write(YAML.dump(@config))
15
- end
16
-
17
- def logged_in? = api_key && !api_key.empty?
18
- def logged_out? = !logged_in?
19
-
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
25
-
26
- def api_key=(key)
27
- @config['api_key'] = key
28
- end
29
-
30
- def base_uri = URI.parse(@config['base_url'] || 'https://trmnl.com')
31
-
32
- def api_uri = URI.join(base_uri, '/api')
33
-
34
- def account_uri = URI.join(base_uri, '/account')
35
-
36
- def edit_plugin_settings_uri(id) = URI.join(base_uri, "/plugin_settings/#{id.to_s}/edit")
37
-
38
- def playlists_uri = URI.join(base_uri, '/playlists')
39
-
40
- private
41
-
42
- attr_reader :paths
43
-
44
- def read_config = paths.app_config.exist? ? YAML.safe_load(paths.app_config.read) : {}
45
- end
46
- end
47
- end