Module: Everywhere::UI

Defined in:
lib/everywhere/ui.rb

Overview

Terminal output helpers. Colors follow the informal standard: on when stdout is a TTY or CLICOLOR_FORCE=1, always off when NO_COLOR is set.

The hosted runner streams every release through a pipe (not a TTY) into the Platform web log, so it exports CLICOLOR_FORCE=1 to keep our color — the web UI renders ANSI. That's why we force rather than sniff the TTY there.

Defined Under Namespace

Classes: Status

Constant Summary collapse

OS_LABELS =

Display casing for os tokens. Target IDs ("macos-arm64") stay lowercase wherever they're typed or stored; use these only in human-facing output.

{
  "macos"   => "macOS",
  "ios"     => "iOS",
  "android" => "Android",
  "windows" => "Windows",
  "linux"   => "Linux"
}.freeze

Class Method Summary collapse

Class Method Details

.bad(msg) ⇒ Object



77
# File 'lib/everywhere/ui.rb', line 77

def bad(msg)     = puts(bad_line(msg))

.bad_line(msg) ⇒ Object



68
# File 'lib/everywhere/ui.rb', line 68

def bad_line(msg)     = "#{red("")} #{msg}"

.blue(t) ⇒ Object



29
# File 'lib/everywhere/ui.rb', line 29

def blue(t)    = paint(t, 34)

.bold(t) ⇒ Object



24
# File 'lib/everywhere/ui.rb', line 24

def bold(t)    = paint(t, 1)

.color?Boolean

Returns:

  • (Boolean)


13
14
15
16
17
18
# File 'lib/everywhere/ui.rb', line 13

def color?
  return false if ENV["NO_COLOR"]
  return true if ENV["CLICOLOR_FORCE"] == "1"

  $stdout.tty?
end

.cyan(t) ⇒ Object



31
# File 'lib/everywhere/ui.rb', line 31

def cyan(t)    = paint(t, 36)

.detail(msg) ⇒ Object



75
# File 'lib/everywhere/ui.rb', line 75

def detail(msg)  = puts(detail_line(msg))

.detail_line(msg) ⇒ Object



66
# File 'lib/everywhere/ui.rb', line 66

def detail_line(msg)  = "    #{gray("·")} #{gray(msg)}"

.die!(msg) ⇒ Object



89
90
91
# File 'lib/everywhere/ui.rb', line 89

def die!(msg)
  abort "#{red("")} #{red(msg)}"
end

.dim(t) ⇒ Object



25
# File 'lib/everywhere/ui.rb', line 25

def dim(t)     = paint(t, 2)

.elapsed(seconds) ⇒ Object

Human elapsed time — "48s", "2m 14s", "1h 3m".



152
153
154
155
156
157
158
# File 'lib/everywhere/ui.rb', line 152

def elapsed(seconds)
  secs = seconds.to_i
  return "#{secs}s" if secs < 60
  return "#{secs / 60}m #{secs % 60}s" if secs < 3600

  "#{secs / 3600}h #{(secs % 3600) / 60}m"
end

.gray(t) ⇒ Object

bright black — dimmer than dim() on most themes



32
# File 'lib/everywhere/ui.rb', line 32

def gray(t)    = paint(t, 90) # bright black — dimmer than dim() on most themes

.green(t) ⇒ Object



27
# File 'lib/everywhere/ui.rb', line 27

def green(t)   = paint(t, 32)

.magenta(t) ⇒ Object



30
# File 'lib/everywhere/ui.rb', line 30

def magenta(t) = paint(t, 35)

.note(msg, marker: "·", color: :gray) ⇒ Object



87
# File 'lib/everywhere/ui.rb', line 87

def note(msg, marker: "·", color: :gray) = puts(note_line(msg, marker: marker, color: color))

.note_line(msg, marker: "·", color: :gray) ⇒ Object

An indented, dim outcome under a substep — e.g. "accepted", "valid on disk".



82
83
84
85
# File 'lib/everywhere/ui.rb', line 82

def note_line(msg, marker: "·", color: :gray)
  tint = respond_to?(color) ? method(color) : method(:gray)
  "    #{tint.call(marker)} #{tint.call(msg)}"
end

.ok(msg) ⇒ Object



76
# File 'lib/everywhere/ui.rb', line 76

def ok(msg)      = puts(ok_line(msg))

.ok_line(msg) ⇒ Object



67
# File 'lib/everywhere/ui.rb', line 67

def ok_line(msg)      = "#{green("")} #{msg}"

.os_label(os) ⇒ Object



44
# File 'lib/everywhere/ui.rb', line 44

def os_label(os) = OS_LABELS.fetch(os.to_s, os.to_s)

.paint(text, *codes) ⇒ Object



20
21
22
# File 'lib/everywhere/ui.rb', line 20

def paint(text, *codes)
  color? ? "\e[#{codes.join(";")}m#{text}\e[0m" : text.to_s
end

.phase(msg) ⇒ Object



72
# File 'lib/everywhere/ui.rb', line 72

def phase(msg)   = puts(phase_line(msg))

.phase_line(msg) ⇒ Object

--- output levels --------------------------------------------------------

phase   ●  a major phase boundary (build / sign / notarize)
step    →  a top-level action inside a phase
substep ▸  a sub-action under a step (indented)
detail  ·  raw-ish detail under a substep (indented + dim)

Every level has a *_line string builder (used by LogFilter, which returns lines for someone else to print) and a printer that puts it. ok / success / warn / bad / die! carry outcomes at any level.



63
# File 'lib/everywhere/ui.rb', line 63

def phase_line(msg)   = "\n#{bold(cyan(""))} #{bold(msg)}"

.red(t) ⇒ Object



26
# File 'lib/everywhere/ui.rb', line 26

def red(t)     = paint(t, 31)

.short_path(str) ⇒ Object

Collapse the noisy machine paths that external tools echo (codesign, notarytool, stapler) down to something a human can scan. In particular the runner works out of a deep /var/folders/…/every-runner…/src/ sandbox — we strip that prefix so only the meaningful dist/App.app tail remains. Then $HOME → ~ and the cwd → a leading (dropped) prefix.



167
168
169
170
171
172
173
174
175
176
# File 'lib/everywhere/ui.rb', line 167

def short_path(str)
  s = str.to_s.dup
  # Runner/local temp sandboxes: keep only the part after the extracted src/.
  s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/every-[a-z]+[0-9A-Za-z_-]+/src/}, "")
  # Any other everywhere-* scratch dir: keep the basename tail.
  s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/everywhere-[a-z]+[0-9A-Za-z_.-]*/}, "")
  home = ENV["HOME"]
  s = s.gsub("#{home}/", "~/") if home && !home.empty?
  s
end

.step(msg) ⇒ Object



73
# File 'lib/everywhere/ui.rb', line 73

def step(msg)    = puts(step_line(msg))

.step_line(msg) ⇒ Object



64
# File 'lib/everywhere/ui.rb', line 64

def step_line(msg)    = "#{cyan("")} #{msg}"

.substep(msg) ⇒ Object



74
# File 'lib/everywhere/ui.rb', line 74

def substep(msg) = puts(substep_line(msg))

.substep_line(msg) ⇒ Object



65
# File 'lib/everywhere/ui.rb', line 65

def substep_line(msg) = "  #{cyan("")} #{msg}"

.success(msg) ⇒ Object



79
# File 'lib/everywhere/ui.rb', line 79

def success(msg) = puts(success_line(msg))

.success_line(msg) ⇒ Object



70
# File 'lib/everywhere/ui.rb', line 70

def success_line(msg) = "#{green("")} #{bold(msg)}"

.target_label(target) ⇒ Object

"macos-arm64" → "macOS-arm64"



47
48
49
50
# File 'lib/everywhere/ui.rb', line 47

def target_label(target)
  os, arch = target.to_s.split("-", 2)
  [ os_label(os), arch ].compact.join("-")
end

.warn(msg) ⇒ Object



78
# File 'lib/everywhere/ui.rb', line 78

def warn(msg)    = puts(warn_line(msg))

.warn_line(msg) ⇒ Object



69
# File 'lib/everywhere/ui.rb', line 69

def warn_line(msg)    = "#{yellow("!")} #{msg}"

.yellow(t) ⇒ Object



28
# File 'lib/everywhere/ui.rb', line 28

def yellow(t)  = paint(t, 33)

.yn(bool) ⇒ Object

A yes/no fact, colored by truthiness (for signing receipts).



94
# File 'lib/everywhere/ui.rb', line 94

def yn(bool) = bool ? green("yes") : red("no")