Module: Kino::CLI

Defined in:
lib/kino/cli.rb

Overview

The kino executable (CLI.start) plus startup presentation shared with Server.run: the banner, ANSI styling, and the exit credit. Nothing here is part of the serving API. (The native layer has a twin of paint in style.rs for the few places Rust writes to the terminal.)

This file deliberately loads no native code: require "kino" happens inside the actions that need it, so --help and --version stay instant.

Constant Summary collapse

MOTD =

The plain banner art: "Kino" in TheDraw's Mindbenders font; motd adds the original three-tone shading.

"ggg    .o\n$$$_,o$$P aaa $$$eea,.   .,aaa,.\n%$$`4eP'  $$$ $$$``$$$% $$$```$$$\n$$$--`$$o ggg $$$---$$$ $$$---$$$\n$$$ \u2591 $$$ $$$ $$$ \u2591 $$$ $$$ \u2591 $$$\n$$$---$$$ $$$ $$$---$$$ $$$---$$$\n$$$   $$$ $$' $$$   $$$ ^$$aaaS$'\n"

Class Method Summary collapse

Class Method Details

.action!(server) ⇒ void

This method returns an undefined value.

The ready block: what this process is (Ruby build with its JIT and parser flags, environment, topology, pid) and where it listens.

Parameters:



126
127
128
129
130
131
132
133
134
135
136
# File 'lib/kino/cli.rb', line 126

def action!(server)
  stats = server.stats
  puts dim("- ruby:      #{RUBY_DESCRIPTION}")
  puts dim("- env:       #{ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"}")
  puts dim("- mode:      #{server.mode}, #{workers_label(stats)} × #{count(stats[:threads], "thread")}")
  puts dim("- pid:       #{Process.pid}")
  puts dim("- listening: #{server.url}")
  puts dim("- control:   #{server.control_url}") if server.control_url
  puts dim("- Ctrl-C to drain and stop")
  puts "\n#{bold("Action!")}\n\n"
end

.bold(text, io: $stdout) ⇒ String

Bold styling for headings and the Action!/Fin. bookends.

Returns:

  • (String)


72
73
74
# File 'lib/kino/cli.rb', line 72

def bold(text, io: $stdout)
  paint("1", text, io: io)
end

.color?(io = $stdout) ⇒ Boolean

True when output to io may use ANSI styling.

Parameters:

  • io (IO) (defaults to: $stdout)

Returns:

  • (Boolean)


49
50
51
# File 'lib/kino/cli.rb', line 49

def color?(io = $stdout)
  io.tty? && ENV["NO_COLOR"].nil? && ENV["TERM"] != "dumb"
end

.dim(text, io: $stdout) ⇒ String

Startup-output styling, same gray family as the banner.

Returns:

  • (String)


66
67
68
# File 'lib/kino/cli.rb', line 66

def dim(text, io: $stdout)
  paint("38;5;243", text, io: io)
end

.fin_at_exitvoid

This method returns an undefined value.

Roll credits when the process ends: normal exit or crash (at_exit also runs after an uncaught exception; only a force-exit skips it).



154
155
156
157
158
159
# File 'lib/kino/cli.rb', line 154

def fin_at_exit
  return if @fin_registered

  @fin_registered = true
  at_exit { $stdout.puts bold("\nFin.\n") }
end

.motd(color: color?) ) ⇒ String

The banner with its three-tone shading applied per character.

Parameters:

  • color (Boolean) (defaults to: color?) )

Returns:

  • (String)


85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/kino/cli.rb', line 85

def motd(color: color?)
  return MOTD unless color

  MOTD.lines.zip(MOTD_TONES.lines).map do |art, tones|
    current = nil
    line = art.chomp.each_char.with_index.map { |char, i|
      sgr = TONE_SGR[tones.to_s[i]]
      if char != " " && sgr && sgr != current
        current = sgr
        "\e[#{sgr}m#{char}"
      else
        char
      end
    }.join
    "#{line}\e[0m\n"
  end.join
end

.opening_creditsvoid

This method returns an undefined value.

The two banner halves around Server#start: credits before, the ready block plus a bold "Action!" after, once mode and port are known. Server.run is the one caller; the kino CLI funnels into it.



117
118
119
120
# File 'lib/kino/cli.rb', line 117

def opening_credits
  puts motd
  puts dim("\nKino #{VERSION} presents:")
end

.paint(code, text, io: $stdout) ⇒ String

Wrap text in an SGR code ("1" bold, "31" red, "38;5;N" 256-color), resetting at the end; plain when io is not a color terminal.

Parameters:

  • code (String)

    an SGR code

  • text (String)
  • io (IO) (defaults to: $stdout)

    the stream the text is destined for (gates coloring)

Returns:

  • (String)


60
61
62
# File 'lib/kino/cli.rb', line 60

def paint(code, text, io: $stdout)
  color?(io) ? "\e[#{code}m#{text}\e[0m" : text
end

.red(text, io: $stderr) ⇒ String

Errors are red (gated on stderr unless another io is given).

Returns:

  • (String)


78
79
80
# File 'lib/kino/cli.rb', line 78

def red(text, io: $stderr)
  paint("91", text, io: io)
end

.start(argv) ⇒ Integer

The kino executable: parse flags, then init/check/serve. Returns the process exit status (exe/kino passes it to Kernel#exit), except for -v and -h, which print and exit in place per optparse convention.

Parameters:

  • argv (Array<String>)

    command-line arguments (consumed)

Returns:

  • (Integer)

    process exit status



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/kino/cli.rb', line 167

def start(argv)
  options = {overrides: {}}
  parser = option_parser(options)
  parser.parse!(argv)

  return write_sample(options[:init_path]) if options[:init_path]

  config = resolve_config(options)

  # Precedence for the rackup file: positional arg > `rackup` in config > config.ru
  rackup_file = argv.first || config[:rackup] || "config.ru"
  unless File.exist?(rackup_file)
    warn red("Kino: #{rackup_file} not found")
    puts
    print_help(parser)
    return 1
  end

  ENV["RACK_ENV"] ||= config[:environment] if config[:environment]

  app = Rack::Builder.parse_file(rackup_file)
  app = app.first if app.is_a?(Array) # rack < 3 compat

  return Check.print_report(app) ? 0 : 1 if options[:check]

  serve(app, config)
  0
end

.stats_line(stats) ⇒ String

One-line stats dump (what the SIGUSR1 handler logs). Excludes worker_status: it's an array with one entry per execution slot, and printing it inline would break the one-line contract (see /stats for per-worker detail).

Parameters:

Returns:

  • (String)


109
110
111
# File 'lib/kino/cli.rb', line 109

def stats_line(stats)
  "stats #{stats.except(:worker_status).map { |k, v| "#{k}=#{v.inspect}" }.join(" ")}"
end

.workers_label(stats) ⇒ Object

The pool as configured: "8 workers", or "8-32 workers" when it can grow.



145
146
147
148
149
# File 'lib/kino/cli.rb', line 145

def workers_label(stats)
  return count(stats[:workers], "worker") if stats[:max_workers] == stats[:workers]

  "#{stats[:workers]}-#{stats[:max_workers]} workers"
end