trmnl_preview 0.3.2 → 0.5.1

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -0
  3. data/README.md +75 -28
  4. data/bin/trmnlp +14 -0
  5. data/lib/trmnlp/api_client.rb +72 -0
  6. data/lib/{trmnl_preview → trmnlp}/app.rb +24 -12
  7. data/lib/trmnlp/cli.rb +66 -0
  8. data/lib/trmnlp/commands/base.rb +40 -0
  9. data/lib/trmnlp/commands/build.rb +21 -0
  10. data/lib/trmnlp/commands/clone.rb +30 -0
  11. data/lib/trmnlp/commands/init.rb +55 -0
  12. data/lib/trmnlp/commands/login.rb +24 -0
  13. data/lib/trmnlp/commands/pull.rb +43 -0
  14. data/lib/trmnlp/commands/push.rb +61 -0
  15. data/lib/trmnlp/commands/serve.rb +25 -0
  16. data/lib/trmnlp/commands.rb +1 -0
  17. data/lib/trmnlp/config/app.rb +43 -0
  18. data/lib/trmnlp/config/plugin.rb +74 -0
  19. data/lib/trmnlp/config/project.rb +47 -0
  20. data/lib/trmnlp/config.rb +15 -0
  21. data/lib/trmnlp/context.rb +211 -0
  22. data/lib/{trmnl_preview → trmnlp}/custom_filters.rb +2 -1
  23. data/lib/trmnlp/paths.rb +56 -0
  24. data/lib/{trmnl_preview → trmnlp}/screen_generator.rb +1 -1
  25. data/lib/trmnlp/version.rb +5 -0
  26. data/lib/trmnlp.rb +14 -0
  27. data/templates/init/.trmnlp.yml +14 -0
  28. data/templates/init/bin/dev +25 -0
  29. data/templates/init/src/full.liquid +1 -0
  30. data/templates/init/src/half_horizontal.liquid +1 -0
  31. data/templates/init/src/half_vertical.liquid +1 -0
  32. data/templates/init/src/quadrant.liquid +1 -0
  33. data/templates/init/src/settings.yml +15 -0
  34. data/trmnl_preview.gemspec +22 -13
  35. data/web/public/index.js +5 -1
  36. data/web/views/index.erb +2 -0
  37. data/web/views/render_html.erb +1 -1
  38. metadata +110 -24
  39. data/.ruby-version +0 -1
  40. data/config.example.toml +0 -14
  41. data/docs/preview.png +0 -0
  42. data/exe/trmnlp +0 -14
  43. data/lib/trmnl_preview/cmd/build.rb +0 -25
  44. data/lib/trmnl_preview/cmd/serve.rb +0 -31
  45. data/lib/trmnl_preview/cmd/usage.rb +0 -11
  46. data/lib/trmnl_preview/cmd/version.rb +0 -1
  47. data/lib/trmnl_preview/context.rb +0 -135
  48. data/lib/trmnl_preview/version.rb +0 -5
  49. data/lib/trmnl_preview.rb +0 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e965687c88e23b15e8727f9b871b6a2529bc28c388fa2e8733fb5034915ec405
4
- data.tar.gz: c1299d3ce7b27a13be79d0018535c8f1b64171f7154d9dd1f425eeacbcbc0129
3
+ metadata.gz: 9cee09b6deb0fc063e5358d6d636536513dc31cba86f0fe4e71ee4d2e1d3a7a5
4
+ data.tar.gz: 7b421d7ae1ace73b2e7370191e853946a857278f4f17332ace859bcf9581822a
5
5
  SHA512:
6
- metadata.gz: d507258ba8a315daae87b83dc5609f2aa99c9d1c674f7067cd2b67b84382a4746c8f299c7c3d0e3e9cc4f378846ca3f5696f4aa8f1dcb65c6eec8b6e44f46d74
7
- data.tar.gz: 02231d9584b0b24a4b7b2749c85cbf14af2c3b6d5321c63dc03bf6e8bb7655b03ee37d86ba69b7ac16ebb3acb367cad2bf54ec437e405a1a842eb1f9263090f3
6
+ metadata.gz: 5d97a1e0f9c461796aa141b88d7c4c2826e2693764c5cdba9013260162b9dad16220b47bd49b36b2f8cbbaeb2c985d1851f3780b33326bd9f518f1d9604774eb
7
+ data.tar.gz: 16a38e4c92c20db294e80e6c729a3e7f12cdf5667785d80baabeb4ab93720a56b02a3346cb6efdae01d5d715b1919eab1d7fb4f34f40ffee2c7e96ce80158a69
data/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ - Fixed `trmnl init`
6
+
7
+ ## 0.5.0
8
+
9
+ - Added `trmlnp init` command
10
+ - Added `trmnlp clone` command
11
+ - Improved `trmnlp push` to create remote plugin on first publish
12
+ - Changed syntax of `trmnlp push` and `trmnlp pull` commands
13
+ - Added `oj` gem for JSON parsing (#32)
14
+
15
+ ## 0.4.0
16
+
17
+ ### Plugin Migration Strategy
18
+
19
+ The plugin directory structure has changed to better align with the [plugin archive format](https://help.usetrmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0).
20
+
21
+ Here is a migration strategy for existing plugin repositories:
22
+
23
+ 1. Create `.trmnlp.yml` and bring over preview settings from `config.toml` - [see README](README.md)
24
+ 2. Rename directory `views/` to `src/`
25
+ 3. Create `src/settings.yml` and bring over plugin settings from `config.toml` - [see TRMNL docs](https://help.usetrmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0)
26
+ 4. Delete `config.toml`
27
+
28
+ ### Changes
29
+
30
+ - Change plugin directory structure (see README for details)
31
+ - Add `login`, `push`, and `pull` commands
32
+ - Bring up-to-date with latest private plugin features:
33
+ - Add `static` strategy
34
+ - Add polling features: multiple URLs, new verbs, and request body
35
+ - Add settings `dark_mode`, `no_screen_padding`, `custom_fields`
36
+ - Add interpolation of custom fields in `polling\_\*` options
37
+ - Add `{{ trmnl }}` variables
38
+ - Add `watch` config
39
+ - Add interpolation of environment variables in `.trmnlp.yml` via `{{ env }}`
40
+ - Add auto-reload when `.trmnlp.yml` or `settings.yml` changes
41
+ - Add variable display
42
+ - Fix crash when #poll_data fails (#12)
43
+ - Fix git runtime error in Docker container (#12)
44
+
45
+
46
+
3
47
  ## 0.3.2
4
48
 
5
49
  - Add bitmap rendering
data/README.md CHANGED
@@ -8,33 +8,50 @@ The server watches the filesystem for changes to the Liquid templates, seamlessl
8
8
 
9
9
  ![Screenshot](docs/preview.png)
10
10
 
11
- ## Creating a Plugin
11
+ ## Project Structure
12
12
 
13
- This is the structure of a plugin repository.
13
+ This is the structure of a plugin project:
14
14
 
15
15
  ```
16
- views/
17
- full.liquid
18
- half_horizontal.liquid
19
- half_vertical.liquid
20
- quadrant.liquid
21
- config.toml
16
+ .
17
+ ├── .trmnlp.yml
18
+ ├── bin
19
+ │ └── dev
20
+ └── src
21
+ ├── full.liquid
22
+ ├── half_horizontal.liquid
23
+ ├── half_vertical.liquid
24
+ ├── quadrant.liquid
25
+ └── settings.yml
22
26
  ```
23
27
 
24
- See [config.example.toml](config.example.toml) for an example config.
28
+ ## Creating a New Plugin
25
29
 
26
- The [trmnl-hello](https://github.com/schrockwell/trmnl-hello) repository is provided as a jumping-off point for creating new plugins. Simply fork the repo, clone it, and start hacking.
30
+ You can start building a plugin locally, then `push` it to the TRMNL server for display on your device.
27
31
 
28
- ## Running the Server (Docker)
32
+ ```sh
33
+ trmnlp init my_plugin # generate
34
+ cd my_plugin
35
+ trmnlp serve # develop locally
36
+ trmnlp login # authenticate
37
+ trmnlp push # upload
38
+ ```
39
+
40
+ ## Modifying an Existing Plugin
41
+
42
+ 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.
29
43
 
30
44
  ```sh
31
- docker run \
32
- -p 4567:4567 \
33
- -v /path/to/plugin/on/host:/plugin \
34
- schrockwell/trmnlp
45
+ trmnlp login # authenticate
46
+ trmnlp clone my_plugin [id] # first download
47
+ cd my_plugin
48
+ trmnlp serve # develop locally
49
+ trmnlp push # upload
35
50
  ```
36
51
 
37
- ## Running the Server (Local Ruby)
52
+ ## Running trmnlp
53
+
54
+ ### Via RubyGems
38
55
 
39
56
  Prerequisites:
40
57
 
@@ -43,29 +60,59 @@ Prerequisites:
43
60
  - Firefox
44
61
  - ImageMagick
45
62
 
46
- In the plugin repository:
47
-
48
63
  ```sh
49
64
  gem install trmnl_preview
50
- trmnlp serve # Starts the server
65
+ trmnlp serve
51
66
  ```
52
67
 
53
- ## Usage Notes
68
+ ### Via Docker (`trmnlp serve` only)
54
69
 
55
- When the strategy is "polling", the specified URL will be fetched once, when the server starts.
70
+ ```sh
71
+ docker run \
72
+ -p 4567:4567 \
73
+ -v /path/to/plugin/on/host:/plugin \
74
+ trmnl/trmnlp
75
+ ```
76
+
77
+ ## `.trmnlp.yml` Reference - Project Config
78
+
79
+ The `.trmnlp.yml` file lives in the root of the plugin project, and is for configuring the local dev server.
80
+
81
+ System environment variables are made available in the `{{ env }}` Liquid varible in this file only. This can be used to safely
82
+ supply plugin secrets, like API keys.
83
+
84
+ All fields are optional.
85
+
86
+ ```yaml
87
+ ---
88
+ # auto-reload when files change (`watch: false` to disable)
89
+ watch:
90
+ - src
91
+ - .trmnlp.yml
92
+
93
+ # values of custom fields (defined in src/settings.yml)
94
+ custom_fields:
95
+ station: "{{ env.ICAO }}" # interpolate $IACO environment variable
96
+
97
+ # override variables
98
+ variables:
99
+ trmnl:
100
+ user:
101
+ name: Peter Quill
102
+ plugin_settings:
103
+ instance_name: Kevin Bacon Facts
104
+
105
+ ```
56
106
 
57
- When the strategy is "webhook", payloads can be POSTed to the `/webhook` endpoint. They are saved to `tmp/data.json` for future renders.
107
+ ## `src/settings.yml` Reference (Plugin Config)
58
108
 
59
- ## `config.toml` Reference
109
+ The `settings.yml` file is part of the plugin definition.
60
110
 
61
- - `strategy` - Either "polling" or "webhook"
62
- - `url` - The URL from which to fetch JSON data (polling strategy only)
63
- - `live_render` - Set to `false` to disable automatic rendering when Liquid templates change (default `true`)
64
- - `[polling_headers]` - A section of headers to append to the HTTP poll request (polling strategy only)
111
+ See [TRMNL documentation](https://help.usetrmnl.com/en/articles/10542599-importing-and-exporting-private-plugins#h_581fb988f0) for details on this file's contents.
65
112
 
66
113
  ## Contributing
67
114
 
68
- Bug reports and pull requests are welcome on GitHub at https://github.com/schrockwell/trmnl_preview.
115
+ Bug reports and pull requests are welcome on GitHub at https://github.com/usetrmnl/trmnlp.
69
116
 
70
117
  ## License
71
118
 
data/bin/trmnlp ADDED
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require "thor"
4
+
5
+ require_relative '../lib/trmnlp/cli'
6
+
7
+ begin
8
+ TRMNLP::CLI.start
9
+ rescue TRMNLP::Error => e
10
+ puts "Error: #{e.message}"
11
+ exit 1
12
+ rescue Interrupt
13
+ exit 1
14
+ end
@@ -0,0 +1,72 @@
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 path to the temp file
22
+ Pathname.new(temp_file.path)
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
+ payload = {
30
+ file: Faraday::Multipart::FilePart.new(path, 'application/zip')
31
+ }
32
+
33
+ response = conn.post("plugin_settings/#{id}/archive", payload)
34
+
35
+ if response.status == 200
36
+ JSON.parse(response.body)
37
+ else
38
+ raise Error, "failed to upload plugin settings archive: #{response.status} #{response.body}"
39
+ end
40
+ end
41
+
42
+ def post_plugin_setting(params)
43
+ response = conn.post("plugin_settings", params.to_json, content_type: 'application/json')
44
+
45
+ if response.status == 200
46
+ JSON.parse(response.body)
47
+ else
48
+ raise Error, "failed to create plugin setting: #{response.status} #{response.body}"
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ attr_reader :config
55
+
56
+ def api_uri = config.app.api_uri
57
+
58
+ def conn
59
+ @conn ||= Faraday.new(url: api_uri, headers:) do |f|
60
+ f.request :multipart
61
+ end
62
+ end
63
+
64
+
65
+ def headers
66
+ {
67
+ 'Authorization' => "Bearer #{config.app.api_key}",
68
+ 'User-Agent' => "trmnlp/#{VERSION}",
69
+ }
70
+ end
71
+ end
72
+ end
@@ -6,7 +6,7 @@ require 'sinatra/base'
6
6
  require_relative 'context'
7
7
  require_relative 'screen_generator'
8
8
 
9
- module TRMNLPreview
9
+ module TRMNLP
10
10
  class App < Sinatra::Base
11
11
  # Sinatra settings
12
12
  set :views, File.join(File.dirname(__FILE__), '..', '..', 'web', 'views')
@@ -15,25 +15,28 @@ module TRMNLPreview
15
15
  def initialize(*args)
16
16
  super
17
17
 
18
- begin
19
- @context = Context.new(settings.user_dir)
20
- rescue StandardError => e
21
- puts e.message
22
- exit 1
23
- end
18
+ @context = settings.context
19
+
20
+ @context.poll_data
24
21
 
25
- @context.poll_data if @context.strategy == 'polling'
22
+ @context.start_filewatcher if @context.config.project.live_render?
26
23
 
27
24
  @live_reload_clients = []
28
- @context.on_view_change do |view|
25
+ @context.on_view_change do |view, user_data|
29
26
  @live_reload_clients.each do |ws|
30
- ws.send('reload')
27
+ payload = {
28
+ 'type' => 'reload',
29
+ 'view' => view,
30
+ 'user_data' => user_data
31
+ }
32
+
33
+ ws.send(payload.to_json)
31
34
  end
32
35
  end
33
36
  end
34
37
 
35
38
  post '/webhook' do
36
- @context.set_data(request.body.read)
39
+ @context.put_webhook(request.body.read)
37
40
  "OK"
38
41
  end
39
42
 
@@ -41,6 +44,11 @@ module TRMNLPreview
41
44
  redirect '/full'
42
45
  end
43
46
 
47
+ get '/data' do
48
+ content_type :json
49
+ JSON.pretty_generate(@context.user_data)
50
+ end
51
+
44
52
  get '/live_reload' do
45
53
  ws = Faye::WebSocket.new(request.env)
46
54
 
@@ -63,12 +71,16 @@ module TRMNLPreview
63
71
  VIEWS.each do |view|
64
72
  get "/#{view}" do
65
73
  @view = view
66
- @live_reload = @context.live_render
74
+ @user_data = JSON.pretty_generate(@context.user_data)
75
+ @live_reload = @context.config.project.live_render?
76
+
67
77
  erb :index
68
78
  end
69
79
 
70
80
  get "/render/#{view}.html" do
71
81
  @view = view
82
+ @screen_classes = @context.screen_classes
83
+
72
84
  erb :render_html do
73
85
  @context.render_template(view)
74
86
  end
data/lib/trmnlp/cli.rb ADDED
@@ -0,0 +1,66 @@
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
+ desc 'build', 'Generate static HTML files'
18
+ def build
19
+ Commands::Build.new(options).call
20
+ end
21
+
22
+ desc 'login', 'Authenticate with TRMNL server'
23
+ def login
24
+ Commands::Login.new(options).call
25
+ end
26
+
27
+ desc 'init NAME', 'Start a new plugin project'
28
+ method_option :skip_liquid, type: :boolean, default: false, desc: 'Skip generating liquid templates'
29
+ def init(name)
30
+ Commands::Init.new(options).call(name)
31
+ end
32
+
33
+ desc 'clone NAME ID', 'Copy a plugin project from TRMNL server'
34
+ def clone(name, id)
35
+ Commands::Clone.new(options).call(name, id)
36
+ end
37
+
38
+ desc 'pull', 'Download latest plugin settings from TRMNL server'
39
+ method_option :force, type: :boolean, default: false, aliases: '-f',
40
+ desc: 'Skip confirmation prompts'
41
+ method_option :id, type: :string, aliases: '-i', desc: 'Plugin settings ID'
42
+ def pull
43
+ Commands::Pull.new(options).call
44
+ end
45
+
46
+ desc 'push', 'Upload latest plugin settings to TRMNL server'
47
+ method_option :force, type: :boolean, default: false, aliases: '-f',
48
+ desc: 'Skip confirmation prompts'
49
+ method_option :id, type: :string, aliases: '-i', desc: 'Plugin settings ID'
50
+ def push
51
+ Commands::Push.new(options).call
52
+ end
53
+
54
+ desc 'serve', 'Start a local dev server'
55
+ method_option :bind, type: :string, default: '127.0.0.1', aliases: '-b', desc: 'Bind address'
56
+ method_option :port, type: :numeric, default: 4567, aliases: '-p', desc: 'Port number'
57
+ def serve
58
+ Commands::Serve.new(options).call
59
+ end
60
+
61
+ desc 'version', 'Show version'
62
+ def version
63
+ puts VERSION
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,40 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,55 @@
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
@@ -0,0 +1,24 @@
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
+ end
11
+
12
+ output "Please visit #{config.app.account_uri} to grab your API key, then paste it here."
13
+
14
+ api_key = prompt("API Key: ")
15
+ raise Error, "API key cannot be empty" if api_key.empty?
16
+
17
+ config.app.api_key = api_key
18
+ config.app.save
19
+
20
+ output "Saved changes to #{paths.app_config}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,43 @@
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
+ temp_path = api.get_plugin_setting_archive(plugin_settings_id)
23
+ size = 0
24
+
25
+ begin
26
+ Zip::File.open(temp_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(temp_path)
35
+ ensure
36
+ temp_path.delete
37
+ end
38
+
39
+ puts "Downloaded plugin (#{size} bytes)"
40
+ end
41
+ end
42
+ end
43
+ end