Module: Unmagic::Browser::Console::Renderer

Defined in:
lib/unmagic/browser/console/renderer.rb

Overview

Turns whatever the last expression evaluated to into something worth looking at.

IRB normally echoes inspect, which for this gem means a screenful of escaped HTML or, worse, a megabyte of PNG bytes. Every result comes through here instead: source gets highlighted, images get drawn, and everything else falls back to a highlighted inspect.

Constant Summary collapse

MAX_LINES =

Long enough to read a page's shape, short enough not to lose the prompt. The value itself is untouched — puts _ still gives you all of it.

60
MAX_CHARS =

A belt to MAX_LINES' braces: one minified line of HTML is a screenful on its own.

8_000

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.themeClass

Returns defaults to Monokai.

Returns:

  • (Class)

    defaults to Monokai



39
40
41
# File 'lib/unmagic/browser/console/renderer.rb', line 39

def theme
  @theme ||= Rouge::Themes::Monokai if highlighting?
end

Class Method Details

.array(value) ⇒ String

Returns a numbered list for elements, highlighted Ruby otherwise.

Parameters:

  • value (Array)

Returns:

  • (String)

    a numbered list for elements, highlighted Ruby otherwise



130
131
132
133
134
135
136
137
# File 'lib/unmagic/browser/console/renderer.rb', line 130

def array(value)
  return ruby(value.inspect) unless value.any? && value.all?(Unmagic::Browser::Element)

  width = value.size.to_s.length
  value.each_with_index.map do |item, index|
    "#{Terminal.dim("[#{index.to_s.rjust(width)}]")} #{element(item)}"
  end.join("\n")
end

.browser(value) ⇒ String

Returns the driver it's running against.

Parameters:

Returns:

  • (String)

    the driver it's running against



141
142
143
# File 'lib/unmagic/browser/console/renderer.rb', line 141

def browser(value)
  "#{Terminal.paint(Terminal::BOLD, "browser")} #{Terminal.dim("via #{value.driver.name}")}"
end

.code(value) ⇒ String

Returns the source, highlighted and truncated.

Parameters:

Returns:

  • (String)

    the source, highlighted and truncated



89
90
91
92
# File 'lib/unmagic/browser/console/renderer.rb', line 89

def code(value)
  body, omitted = clamp(value)
  [highlight(body, value.language), footer(value, omitted)].compact.join("\n")
end

.document(value) ⇒ String

Returns where it was written.

Parameters:

Returns:

  • (String)

    where it was written



81
82
83
84
85
# File 'lib/unmagic/browser/console/renderer.rb', line 81

def document(value)
  path = Graphics.save(value)
  "#{Terminal.paint(Terminal::YELLOW, "[#{value.extension}]")} " +
    Terminal.dim("#{value.bytesize} bytes · #{path}")
end

.element(value) ⇒ String

Returns the element's tag and the text in it.

Parameters:

Returns:

  • (String)

    the element's tag and the text in it



121
122
123
124
125
126
# File 'lib/unmagic/browser/console/renderer.rb', line 121

def element(value)
  text = value.text
  text = "#{text[0, 80]}" if text.length > 80
  tag = Terminal.paint(Terminal::BLUE, "<#{value.tag_name}>")
  text.empty? ? tag : "#{tag} #{text.inspect}"
end

.emulation(value) ⇒ String

Returns the knobs that are set, or that none are.

Parameters:

Returns:

  • (String)

    the knobs that are set, or that none are



147
148
149
150
151
152
# File 'lib/unmagic/browser/console/renderer.rb', line 147

def emulation(value)
  knobs = value.to_h.compact
  return Terminal.dim("emulating nothing — desktop, light, screen") if knobs.empty?

  knobs.map { |key, setting| "#{Terminal.paint(Terminal::BLUE, key.to_s)} #{setting.inspect}" }.join("\n")
end

.formatterRouge::Formatter

Every terminal that can draw an image can also do 24-bit colour, so there's no reason to quantise to 256.

Returns:

  • (Rouge::Formatter)


177
178
179
# File 'lib/unmagic/browser/console/renderer.rb', line 177

def formatter
  @formatter ||= Rouge::Formatters::TerminalTruecolor.new(theme.new)
end

.highlight(source, language) ⇒ String

Returns the source with terminal colour, or untouched without a tty or without Rouge.

Parameters:

  • source (String)
  • language (String)

    a Rouge lexer name

Returns:

  • (String)

    the source with terminal colour, or untouched without a tty or without Rouge



164
165
166
167
168
169
170
171
# File 'lib/unmagic/browser/console/renderer.rb', line 164

def highlight(source, language)
  return source unless Terminal.tty? && highlighting?

  lexer = Rouge::Lexer.find(language) || Rouge::Lexers::PlainText
  formatter.format(lexer.new.lex(source)).chomp
rescue StandardError
  source # Never let a highlighting failure swallow the value itself.
end

.highlighting?Boolean

Returns whether Rouge is loaded and can highlight.

Returns:

  • (Boolean)

    whether Rouge is loaded and can highlight



44
45
46
# File 'lib/unmagic/browser/console/renderer.rb', line 44

def highlighting?
  defined?(Rouge) ? true : false
end

.image(value) ⇒ String

Drawing happens as a side effect, because the image goes out as a raw escape sequence rather than as text IRB could indent or wrap.

Parameters:

Returns:

  • (String)

    the caption printed under the image



71
72
73
74
75
76
77
# File 'lib/unmagic/browser/console/renderer.rb', line 71

def image(value)
  path = Graphics.show(value)
  width, height = value.dimensions
  size = width ? "#{width}×#{height}" : "#{value.bytesize} bytes"
  caption = Terminal.dim("#{size} · #{path}")
  Terminal.graphics? ? caption : "#{Terminal.paint(Terminal::YELLOW, "[image]")} #{caption}"
end

.render(value) ⇒ String

Returns what to print.

Parameters:

  • value (Object)

    whatever the last expression returned

Returns:

  • (String)

    what to print



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/unmagic/browser/console/renderer.rb', line 50

def render(value)
  case value
  when Image then image(value)
  when Document then document(value)
  when Code then code(value)
  when Unmagic::Browser::Session then session(value)
  when Unmagic::Browser::Element then element(value)
  when Unmagic::Browser then browser(value)
  when Unmagic::Browser::Emulation then emulation(value)
  when Array then array(value)
  when String then string(value)
  when nil then Terminal.dim("nil")
  else ruby(value.inspect)
  end
end

.ruby(source) ⇒ String

Returns it, highlighted.

Parameters:

  • source (String)

    Ruby source, usually from #inspect

Returns:

  • (String)

    it, highlighted



156
157
158
# File 'lib/unmagic/browser/console/renderer.rb', line 156

def ruby(source)
  highlight(source, "ruby")
end

.session(value) ⇒ String

Returns where the session is, and what it's emulating.

Parameters:

Returns:

  • (String)

    where the session is, and what it's emulating



105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/unmagic/browser/console/renderer.rb', line 105

def session(value)
  return Terminal.paint(Terminal::GREY, "session (closed)") if value.closed?

  # Plenty of pages have no <title> — Hacker News' login page, for one
  # — and a bold empty string reads as something having gone wrong.
  title = value.title.to_s.strip
  heading = title.empty? ? Terminal.dim("(untitled)") : Terminal.paint(Terminal::BOLD, title)
  lines = ["#{Terminal.paint(Terminal::GREEN, "")} #{heading}"]
  lines << Terminal.dim("  #{value.current_url}")
  knobs = value.emulation.to_h.compact
  lines << Terminal.dim("  emulating #{describe(knobs)}") if knobs.any?
  lines.join("\n")
end

.string(value) ⇒ String

Returns the string, as prose if it's long, as Ruby if it's short.

Parameters:

  • value (String)

Returns:

  • (String)

    the string, as prose if it's long, as Ruby if it's short



96
97
98
99
100
101
# File 'lib/unmagic/browser/console/renderer.rb', line 96

def string(value)
  return ruby(value.inspect) unless value.include?("\n") || value.length > 100

  body, omitted = clamp(value)
  [body, footer(value, omitted)].compact.join("\n")
end