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
-
.dock ⇒ Object
The installed Dock, or nil for the plain path.
- .dock=(value) ⇒ Object
-
.error(str) ⇒ Object
Only UI.die! goes to stderr — UI.warn and friends have always been on stdout and the suite asserts them there.
-
.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. - .multiplexed? ⇒ Boolean
- .prefix_for(name) ⇒ Object
- .print(str) ⇒ Object
- .puts(str) ⇒ Object
- .synchronize(&block) ⇒ Object
- .tag ⇒ Object
-
.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.
- .tagged(str) ⇒ Object
- .write(str) ⇒ Object
Class Method Details
.dock ⇒ Object
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
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 |
.print(str) ⇒ Object
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) |
.tag ⇒ Object
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 |