Class: Terminalwire::V2::Server::Handler

Inherits:
Object
  • Object
show all
Defined in:
lib/terminalwire/v2/server/handler.rb

Overview

The framework-agnostic server entrypoint: performs the handshake, runs your CLI with a Terminalwire-backed context, handles errors, and exits the client. A Rails or Rack adapter just builds a transport and calls this.

Your CLI can be ANY of:

* a block / callable run(context, args) — works with OptionParser, GLI,
dry-cli, or hand-rolled parsing. The block runs inside `Server.redirect`,
so $stdout/$stderr/$stdin and bare puts/gets already target the client.
* a Thor class via `cli_class:` (it gets Thor's dedicated shell adapter).

# OptionParser (or anything using the standard IO globals):
Handler.new do |ctx, args|
opts = {}
OptionParser.new { |o| o.on("--name NAME") { |v| opts[:name] = v } }.parse!(args)
puts "hello #{opts[:name]}"
end

# Thor:
Handler.new(cli_class: MyThorCLI)

Constant Summary collapse

DEFAULT_ERROR_MESSAGE =
"An error occurred. Please try again."

Instance Method Summary collapse

Constructor Details

#initialize(cli_class: nil, run: nil, report: nil, verbose: false, error_message: DEFAULT_ERROR_MESSAGE) {|context, args| ... } ⇒ Handler

Returns a new instance of Handler.

Parameters:

  • cli_class (Class, nil) (defaults to: nil)

    a Thor CLI that includes Server::Thor

  • run (#call, nil) (defaults to: nil)

    a callable (context, args) for non-Thor CLIs

  • report (#call, nil) (defaults to: nil)

    optional callable invoked with unexpected errors

  • verbose (Boolean) (defaults to: false)

    show full backtraces to the client (dev only)

Yields:

  • (context, args)

    block form of run:

Raises:

  • (ArgumentError)


32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/terminalwire/v2/server/handler.rb', line 32

def initialize(cli_class: nil, run: nil, report: nil, verbose: false,
               error_message: DEFAULT_ERROR_MESSAGE, &block)
  @cli_class = cli_class
  @run = run || block
  @report = report
  @verbose = verbose
  @error_message = error_message

  return if @cli_class || @run

  raise ArgumentError, "provide a Thor cli_class:, a run: callable, or a block"
end

Instance Method Details

#call(transport:, request: {}) ⇒ Object

Run one session over the given transport. Returns the exit status. request is the incoming HTTP connection profile from the Rack env ({ host:, ip:, user_agent:, headers: }) — threaded in so URL helpers can use the host (v1 set cli.default_url_options[:host] the same way) and so server code / about can see who connected.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/terminalwire/v2/server/handler.rb', line 50

def call(transport:, request: {})
  runtime = Runtime.new(transport: transport).handshake
  context = Context.new(runtime)
  context.request = request
  arguments = context.program_arguments
  status = 0

  begin
    begin
      dispatch(context, arguments, request[:host])
    rescue Interrupt, Interrupted
      status = 130
    rescue SystemExit => e
      # A command that called `exit`/`abort` raises SystemExit (not a
      # StandardError). Without this it would slip past the rescue below, the
      # ensure would send the client `exit(0)` — reporting success — and the
      # exception would then silently kill the CLI thread. Honor the real code.
      status = e.status
    rescue StandardError => e
      status = handle_error(e, context)
    ensure
      # Teardown must not be interrupted. A late Ctrl-C (delivered as an async
      # Interrupted via Thread#raise) landing here would abort the exit-frame
      # write or runtime close and hang the client — the very failure the
      # interrupt machinery exists to avoid. Mask async interrupts for the
      # duration so the exit frame always flushes and the runtime always closes.
      Thread.handle_interrupt(Interrupt => :never, Interrupted => :never) do
        context.exit(status)
        runtime.close
      end
    end
  rescue Interrupt, Interrupted
    # An interrupt that fired in a rescue clause above, before the mask took
    # hold, surfaces here. Teardown still ran in the ensure, so just report it.
    status = 130
  end

  status
end