trmnl_preview 0.3.2 → 0.4.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 +4 -4
- data/CHANGELOG.md +32 -0
- data/README.md +46 -17
- data/bin/trmnlp +14 -0
- data/lib/trmnlp/api_client.rb +62 -0
- data/lib/{trmnl_preview → trmnlp}/app.rb +24 -12
- data/lib/trmnlp/cli.rb +51 -0
- data/lib/trmnlp/commands/base.rb +23 -0
- data/lib/trmnlp/commands/build.rb +22 -0
- data/lib/trmnlp/commands/login.rb +25 -0
- data/lib/trmnlp/commands/pull.rb +45 -0
- data/lib/trmnlp/commands/push.rb +43 -0
- data/lib/trmnlp/commands/serve.rb +25 -0
- data/lib/trmnlp/commands.rb +1 -0
- data/lib/trmnlp/config/app.rb +39 -0
- data/lib/trmnlp/config/plugin.rb +74 -0
- data/lib/trmnlp/config/project.rb +47 -0
- data/lib/trmnlp/config.rb +15 -0
- data/lib/trmnlp/context.rb +211 -0
- data/lib/{trmnl_preview → trmnlp}/custom_filters.rb +2 -1
- data/lib/trmnlp/paths.rb +50 -0
- data/lib/{trmnl_preview → trmnlp}/screen_generator.rb +1 -1
- data/lib/trmnlp/version.rb +5 -0
- data/lib/trmnlp.rb +13 -0
- data/trmnl_preview.gemspec +19 -12
- data/web/public/index.js +5 -1
- data/web/views/index.erb +2 -0
- data/web/views/render_html.erb +1 -1
- metadata +87 -24
- data/.ruby-version +0 -1
- data/config.example.toml +0 -14
- data/docs/preview.png +0 -0
- data/exe/trmnlp +0 -14
- data/lib/trmnl_preview/cmd/build.rb +0 -25
- data/lib/trmnl_preview/cmd/serve.rb +0 -31
- data/lib/trmnl_preview/cmd/usage.rb +0 -11
- data/lib/trmnl_preview/cmd/version.rb +0 -1
- data/lib/trmnl_preview/context.rb +0 -135
- data/lib/trmnl_preview/version.rb +0 -5
- data/lib/trmnl_preview.rb +0 -10
| @@ -1,25 +0,0 @@ | |
| 1 | 
            -
            require 'optionparser'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            require_relative '../context'
         | 
| 4 | 
            -
             | 
| 5 | 
            -
            OptionParser.new do |opts|
         | 
| 6 | 
            -
              opts.banner = "Usage: trmnlp build [directory]"
         | 
| 7 | 
            -
            end.parse!
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            root = ARGV[1] || Dir.pwd
         | 
| 10 | 
            -
            begin
         | 
| 11 | 
            -
              context = TRMNLPreview::Context.new(root)
         | 
| 12 | 
            -
            rescue StandardError => e
         | 
| 13 | 
            -
              puts e.message
         | 
| 14 | 
            -
              exit 1
         | 
| 15 | 
            -
            end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
            context.poll_data
         | 
| 18 | 
            -
             | 
| 19 | 
            -
            TRMNLPreview::VIEWS.each do |view|
         | 
| 20 | 
            -
              output_path = File.join(context.temp_dir, "#{view}.html")
         | 
| 21 | 
            -
              puts "Creating #{output_path}..."
         | 
| 22 | 
            -
              File.write(output_path, context.render_full_page(view))
         | 
| 23 | 
            -
            end
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            puts "Done!"
         | 
| @@ -1,31 +0,0 @@ | |
| 1 | 
            -
            require 'optionparser'
         | 
| 2 | 
            -
             | 
| 3 | 
            -
            options = {
         | 
| 4 | 
            -
              bind: '127.0.0.1',
         | 
| 5 | 
            -
              port: 4567
         | 
| 6 | 
            -
            }
         | 
| 7 | 
            -
             | 
| 8 | 
            -
            # Parse options BEFORE requiring the Sinatra app, since it has its own option-parsing behavior.
         | 
| 9 | 
            -
            # This will remove the option items from ARGV, which is what we want.
         | 
| 10 | 
            -
            OptionParser.new do |opts|
         | 
| 11 | 
            -
              opts.banner = "Usage: trmnlp serve [directory] [options]"
         | 
| 12 | 
            -
             | 
| 13 | 
            -
              opts.on("-b", "--bind [HOST]", "Bind to host address (default: 127.0.0.1)") do |host|
         | 
| 14 | 
            -
                options[:bind] = host
         | 
| 15 | 
            -
              end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
              opts.on("-p", "--port [PORT]", "Use port (default: 4567)") do |port|
         | 
| 18 | 
            -
                options[:port] = port
         | 
| 19 | 
            -
              end
         | 
| 20 | 
            -
            end.parse!
         | 
| 21 | 
            -
             | 
| 22 | 
            -
            # Must come AFTER parsing options
         | 
| 23 | 
            -
            require_relative '../app'
         | 
| 24 | 
            -
             | 
| 25 | 
            -
            # Now we can configure things
         | 
| 26 | 
            -
            TRMNLPreview::App.set(:user_dir, ARGV[1] || Dir.pwd)
         | 
| 27 | 
            -
            TRMNLPreview::App.set(:bind, options[:bind])
         | 
| 28 | 
            -
            TRMNLPreview::App.set(:port, options[:port])
         | 
| 29 | 
            -
             | 
| 30 | 
            -
            # Finally, start the app!
         | 
| 31 | 
            -
            TRMNLPreview::App.run!
         | 
| @@ -1 +0,0 @@ | |
| 1 | 
            -
            puts TRMNLPreview::VERSION
         | 
| @@ -1,135 +0,0 @@ | |
| 1 | 
            -
            require 'erb'
         | 
| 2 | 
            -
            require 'fileutils'
         | 
| 3 | 
            -
            require 'filewatcher'
         | 
| 4 | 
            -
            require 'json'
         | 
| 5 | 
            -
            require 'liquid'
         | 
| 6 | 
            -
            require 'open-uri'
         | 
| 7 | 
            -
            require 'toml-rb'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
            require_relative 'custom_filters'
         | 
| 10 | 
            -
             | 
| 11 | 
            -
            module TRMNLPreview
         | 
| 12 | 
            -
              class Context
         | 
| 13 | 
            -
                attr_reader :strategy, :temp_dir, :live_render
         | 
| 14 | 
            -
                
         | 
| 15 | 
            -
                def initialize(root, opts = {})
         | 
| 16 | 
            -
                  config_path = File.join(root, 'config.toml')
         | 
| 17 | 
            -
                  @user_views_dir = File.join(root, 'views')
         | 
| 18 | 
            -
                  @temp_dir = File.join(root, 'tmp')
         | 
| 19 | 
            -
                  @data_json_path = File.join(@temp_dir, 'data.json')
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                  unless File.exist?(config_path)
         | 
| 22 | 
            -
                    raise "No config.toml found in #{root}"
         | 
| 23 | 
            -
                  end
         | 
| 24 | 
            -
                
         | 
| 25 | 
            -
                  unless Dir.exist?(@user_views_dir)
         | 
| 26 | 
            -
                    raise "No views found at #{@user_views_dir}"
         | 
| 27 | 
            -
                  end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                  config = TomlRB.load_file(config_path)
         | 
| 30 | 
            -
                  @strategy = config['strategy']
         | 
| 31 | 
            -
                  @url = config['url']
         | 
| 32 | 
            -
                  @polling_headers = config['polling_headers'] || {}
         | 
| 33 | 
            -
                  @live_render = config['live_render'] != false
         | 
| 34 | 
            -
                  @user_filters = config['custom_filters'] || []
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  unless ['polling', 'webhook'].include?(@strategy)
         | 
| 37 | 
            -
                    raise "Invalid strategy: #{strategy} (must be 'polling' or 'webhook')"
         | 
| 38 | 
            -
                  end
         | 
| 39 | 
            -
                  
         | 
| 40 | 
            -
                  FileUtils.mkdir_p(@temp_dir)
         | 
| 41 | 
            -
             | 
| 42 | 
            -
                  @liquid_environment = Liquid::Environment.build do |env|
         | 
| 43 | 
            -
                    env.register_filter(CustomFilters)
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                    @user_filters.each do |module_name, relative_path|
         | 
| 46 | 
            -
                      require File.join(root, relative_path)
         | 
| 47 | 
            -
                      env.register_filter(Object.const_get(module_name))
         | 
| 48 | 
            -
                    end
         | 
| 49 | 
            -
                  end
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                  start_filewatcher_thread if @live_render
         | 
| 52 | 
            -
                end
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                def start_filewatcher_thread
         | 
| 55 | 
            -
                  Thread.new do
         | 
| 56 | 
            -
                    loop do
         | 
| 57 | 
            -
                      begin
         | 
| 58 | 
            -
                        Filewatcher.new(@user_views_dir).watch do |changes|
         | 
| 59 | 
            -
                          views = changes.map { |path, _change| File.basename(path, '.liquid') }
         | 
| 60 | 
            -
                          views.each do |view|
         | 
| 61 | 
            -
                            @view_change_callback.call(view) if @view_change_callback
         | 
| 62 | 
            -
                          end
         | 
| 63 | 
            -
                        end
         | 
| 64 | 
            -
                      rescue => e
         | 
| 65 | 
            -
                        puts "Error during live render: #{e}"
         | 
| 66 | 
            -
                      end
         | 
| 67 | 
            -
                    end
         | 
| 68 | 
            -
                  end
         | 
| 69 | 
            -
                end
         | 
| 70 | 
            -
             | 
| 71 | 
            -
                def on_view_change(&block)
         | 
| 72 | 
            -
                  @view_change_callback = block
         | 
| 73 | 
            -
                end
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                def user_data
         | 
| 76 | 
            -
                  data = JSON.parse(File.read(@data_json_path))
         | 
| 77 | 
            -
                  data = { data: data } if data.is_a?(Array) # per TRMNL docs, bare array is wrapped in 'data' key
         | 
| 78 | 
            -
                  data
         | 
| 79 | 
            -
                end
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                def poll_data
         | 
| 82 | 
            -
                  if @url.nil?
         | 
| 83 | 
            -
                    raise "URL is required for polling strategy"
         | 
| 84 | 
            -
                  end
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                  print "Fetching #{@url}... "
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                  if @url.match?(/^https?:\/\//)
         | 
| 89 | 
            -
                    payload = URI.open(@url, @polling_headers).read
         | 
| 90 | 
            -
                  else
         | 
| 91 | 
            -
                    payload = File.read(@url)
         | 
| 92 | 
            -
                  end
         | 
| 93 | 
            -
             | 
| 94 | 
            -
                  File.write(@data_json_path, payload)
         | 
| 95 | 
            -
                  puts "got #{payload.size} bytes"
         | 
| 96 | 
            -
             | 
| 97 | 
            -
                  user_data
         | 
| 98 | 
            -
                end
         | 
| 99 | 
            -
             | 
| 100 | 
            -
                def set_data(payload)
         | 
| 101 | 
            -
                  File.write(@data_json_path, payload)
         | 
| 102 | 
            -
                end
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                def view_path(view)
         | 
| 105 | 
            -
                  File.join(@user_views_dir, "#{view}.liquid")
         | 
| 106 | 
            -
                end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
                def render_template(view)
         | 
| 109 | 
            -
                    path = view_path(view)
         | 
| 110 | 
            -
                    unless File.exist?(path)
         | 
| 111 | 
            -
                      return "Missing plugin template: views/#{view}.liquid"
         | 
| 112 | 
            -
                    end
         | 
| 113 | 
            -
             | 
| 114 | 
            -
                    user_template = Liquid::Template.parse(File.read(path), environment: @liquid_environment)
         | 
| 115 | 
            -
                    user_template.render(user_data)
         | 
| 116 | 
            -
                rescue StandardError => e
         | 
| 117 | 
            -
                  e.message
         | 
| 118 | 
            -
                end
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                def render_full_page(view)
         | 
| 121 | 
            -
                  page_erb_template = File.read(File.join(__dir__, '..', '..', 'web', 'views', 'render_html.erb'))
         | 
| 122 | 
            -
                  
         | 
| 123 | 
            -
                  ERB.new(page_erb_template).result(ERBBinding.new(view).get_binding do
         | 
| 124 | 
            -
                    render_template(view)
         | 
| 125 | 
            -
                  end)
         | 
| 126 | 
            -
                end
         | 
| 127 | 
            -
             | 
| 128 | 
            -
                private 
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                class ERBBinding
         | 
| 131 | 
            -
                  def initialize(view) = @view = view
         | 
| 132 | 
            -
                  def get_binding = binding
         | 
| 133 | 
            -
                end
         | 
| 134 | 
            -
              end
         | 
| 135 | 
            -
            end
         | 
    
        data/lib/trmnl_preview.rb
    DELETED