Module: Everywhere::Console

Defined in:
lib/everywhere/console.rb

Overview

The one place anything writes to the terminal.

every dev runs the iOS and Android builds concurrently, each streaming xcodebuild/Gradle output, while the dev server and desktop shell relay their own output and the main thread prints its own chrome. Without a lock those writes tear into each other mid-line. Everything goes through here, and when more than one task is producing output each line is prefixed with its source so you can tell who said what.

A Dock can install itself as the sink (Console.dock =); it then gets the chance to erase and repaint its pinned footer around each write. With no dock — every other command, CI, a pipe — writes go straight to $stdout and the output is byte-identical to what it always was.

Three rules this file exists to enforce:

* $stdout/$stderr are resolved at WRITE time, never memoized. Minitest's
capture_io swaps the globals, and a good chunk of the suite asserts on
what it captured.
* The lock is a Monitor, not a Mutex. Monitor is reentrant, so a UI helper
or LogFilter that ends up writing from inside a write can't deadlock.
* Nothing calls Console from a Signal.trap handler. Taking a held lock in
trap context raises ThreadError. Traps set a flag; the main loop draws.

Constant Summary collapse

LOCK =
Monitor.new
TAGS =

Short, fixed-width source tags. Colored so a glance separates the streams; padded so the "│" gutter lines up regardless of which task is talking.

{
  web: ["web", :cyan],
  desktop: ["desktop", :magenta],
  ios: ["ios", :blue],
  android: ["android", :green],
  logs: ["logs", :gray]
}.freeze
TAG_WIDTH =
TAGS.values.map { |label, _| label.length }.max

Class Method Summary collapse

Class Method Details

.dockObject

The installed Dock, or nil for the plain path. Never assume it is set — every command other than every dev runs without one.



50
# File 'lib/everywhere/console.rb', line 50

def dock = LOCK.synchronize { @dock }

.dock=(value) ⇒ Object



52
53
54
# File 'lib/everywhere/console.rb', line 52

def dock=(value)
  LOCK.synchronize { @dock = value }
end

.error(str) ⇒ Object

Only UI.die! goes to stderr — UI.warn and friends have always been on stdout and the suite asserts them there. It bypasses the dock: an error is the one thing that must survive even if the dock is wedged.



73
74
75
76
77
78
79
80
# File 'lib/everywhere/console.rb', line 73

def error(str)
  LOCK.synchronize do
    @dock&.clear
    io = $stderr
    io.write(tagged("#{str}\n"))
    io.flush
  end
end

.multiplexed=(bool) ⇒ Object

Tagging switches on only while two or more sources are live, so a single every dev --ios (and every existing test that asserts on stdout) sees byte-identical output to before.



96
97
98
# File 'lib/everywhere/console.rb', line 96

def multiplexed=(bool)
  LOCK.synchronize { @multiplexed = bool }
end

.multiplexed?Boolean

Returns:

  • (Boolean)


100
# File 'lib/everywhere/console.rb', line 100

def multiplexed? = LOCK.synchronize { !!@multiplexed }

.prefix_for(name) ⇒ Object



110
111
112
113
114
115
# File 'lib/everywhere/console.rb', line 110

def prefix_for(name)
  label, color = TAGS[name]
  return nil unless label

  "#{UI.public_send(color, label.ljust(TAG_WIDTH))} #{UI.gray("")} "
end


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

def print(str) = write(str)

.puts(str) ⇒ Object



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

def puts(str)  = write("#{str}\n")

.synchronize(&block) ⇒ Object



46
# File 'lib/everywhere/console.rb', line 46

def synchronize(&block) = LOCK.synchronize(&block)

.tagObject



91
# File 'lib/everywhere/console.rb', line 91

def tag = Thread.current.thread_variable_get(:everywhere_console_tag)

.tag=(name) ⇒ Object

thread_variable_set (thread-scoped), not Thread#[] (fiber-local): tags are set by TaskPool in one place and read by the relay threads it owns.



86
87
88
# File 'lib/everywhere/console.rb', line 86

def tag=(name)
  Thread.current.thread_variable_set(:everywhere_console_tag, name&.to_sym)
end

.tagged(str) ⇒ Object



102
103
104
105
106
107
108
# File 'lib/everywhere/console.rb', line 102

def tagged(str)
  return str unless @multiplexed

  prefix = prefix_for(tag) or return str
  # Prefix every non-empty line, including inside a multi-line chunk.
  str.gsub(/^(?=.)/) { prefix }
end

.write(str) ⇒ Object



56
57
58
59
60
61
62
63
64
65
# File 'lib/everywhere/console.rb', line 56

def write(str)
  LOCK.synchronize do
    text = tagged(str)
    next @dock.emit(text) if @dock

    io = $stdout
    io.write(text)
    io.flush
  end
end