Module: Tempest::REPL::Formatter
- Defined in:
- lib/tempest/repl/formatter.rb
Overview
Renders posts and Jetstream events as terminal lines, earthquake-style:
[$AA] [HH:MM] @handle: text
The leading [$AA] is only emitted when a Registry is supplied (and the event is something that can be replied to — a post, not a delete or a like/repost record). URLs found in the body are annotated inline with their own ($LA) ids when a registry is supplied. ANSI colors are emitted only when Formatter.color is true (set by the CLI when stdout is a TTY); tests run with color disabled.
Constant Summary collapse
- RESET =
"\e[0m".freeze
- CYAN =
"\e[36m".freeze
- GREEN =
"\e[32m".freeze
- DIM =
"\e[2m".freeze
- HASHTAG_BLUE =
"\e[38;5;110m".freeze
- HASHTAG_PATTERN =
/#[[:alnum:]_]+/.freeze
- URL_PATTERN =
%r{https?://[^\s]+}.freeze
- DECORATE_PATTERN =
Regexp.union(URL_PATTERN, HASHTAG_PATTERN).freeze
Class Attribute Summary collapse
-
.color ⇒ Object
Returns the value of attribute color.
Class Method Summary collapse
- .annotate_urls(text, registry, facets: nil) ⇒ Object
- .annotate_urls_with_facets(text, registry, facets) ⇒ Object
- .bracket(time) ⇒ Object
- .compose(var, time, handle, did, text) ⇒ Object
- .decorate_body(text) ⇒ Object
- .did_label(did) ⇒ Object
- .event_line(event, registry: nil, resolver: nil) ⇒ Object
- .format_status_time(time) ⇒ Object
- .format_time(iso) ⇒ Object
- .handle_label(handle) ⇒ Object
- .host_of(uri) ⇒ Object
- .id_label(var) ⇒ Object
- .post_line(post, registry: nil) ⇒ Object
- .prepend_reply_marker(body, reply_parent_uri, registry) ⇒ Object
- .reply_parent_uri_of(record) ⇒ Object
- .squeeze(text) ⇒ Object
- .status_line(status) ⇒ Object
- .subject_did(subject_uri) ⇒ Object
- .subject_owner_label(subject_uri, resolver, registry = nil) ⇒ Object
Class Attribute Details
.color ⇒ Object
Returns the value of attribute color.
28 29 30 |
# File 'lib/tempest/repl/formatter.rb', line 28 def color @color end |
Class Method Details
.annotate_urls(text, registry, facets: nil) ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/tempest/repl/formatter.rb', line 127 def annotate_urls(text, registry, facets: nil) return text unless registry text = text.to_s if facets && !facets.empty? return annotate_urls_with_facets(text, registry, facets) end urls = URI.extract(text, ["http", "https"]).uniq urls.each do |url| var = registry.assign_url(url) text = text.sub(url, "#{url} (#{var})") end text end |
.annotate_urls_with_facets(text, registry, facets) ⇒ Object
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/tempest/repl/formatter.rb', line 142 def annotate_urls_with_facets(text, registry, facets) text = text.dup.force_encoding(Encoding::UTF_8) bytesize = text.bytesize valid = facets .select { |f| f.byte_start.is_a?(Integer) && f.byte_end.is_a?(Integer) } .select { |f| f.byte_start >= 0 && f.byte_end <= bytesize && f.byte_start < f.byte_end } .sort_by(&:byte_start) # Assign vars in reading order so earlier facets get earlier ids. replacements = valid.map do |facet| var = registry.assign_url(facet.uri) domain = host_of(facet.uri) || facet.uri [facet, "[#{domain} #{var}]"] end # Apply substitutions in reverse byte order so earlier ranges remain valid. replacements.reverse_each do |facet, replacement| head = text.byteslice(0, facet.byte_start) || "" tail = text.byteslice(facet.byte_end, text.bytesize - facet.byte_end) || "" text = (head + replacement + tail).force_encoding(Encoding::UTF_8) end text end |
.bracket(time) ⇒ Object
193 194 195 |
# File 'lib/tempest/repl/formatter.rb', line 193 def bracket(time) Formatter.color ? "#{CYAN}[#{time}]#{RESET} " : "[#{time}] " end |
.compose(var, time, handle, did, text) ⇒ Object
185 186 187 188 189 190 191 |
# File 'lib/tempest/repl/formatter.rb', line 185 def compose(var, time, handle, did, text) prefix = "" prefix += id_label(var) if var prefix += bracket(time) if time identity = handle ? handle_label(handle) : did_label(did) "#{prefix}#{identity}: #{text}" end |
.decorate_body(text) ⇒ Object
43 44 45 46 47 48 49 50 51 |
# File 'lib/tempest/repl/formatter.rb', line 43 def decorate_body(text) return text unless Formatter.color return text if text.nil? || text.empty? text.gsub(DECORATE_PATTERN) do |match| color = match.start_with?("http") ? DIM : HASHTAG_BLUE "#{color}#{match}#{RESET}" end end |
.did_label(did) ⇒ Object
205 206 207 |
# File 'lib/tempest/repl/formatter.rb', line 205 def did_label(did) Formatter.color ? "#{DIM}<#{did}>#{RESET}" : "<#{did}>" end |
.event_line(event, registry: nil, resolver: nil) ⇒ Object
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/tempest/repl/formatter.rb', line 73 def event_line(event, registry: nil, resolver: nil) handle = resolver&.resolve(event.did) if event.operation == :delete body = "(deleted #{event.collection}/#{event.rkey})" var = nil elsif event.respond_to?(:like?) && event.like? body = "liked #{subject_owner_label(event.subject_uri, resolver, registry)}" var = nil elsif event.respond_to?(:repost?) && event.repost? body = "reposted #{subject_owner_label(event.subject_uri, resolver, registry)}" var = nil else facets = event.respond_to?(:facets) ? event.facets : nil body = annotate_urls(squeeze(event.text), registry, facets: facets) body = decorate_body(body) body = prepend_reply_marker(body, reply_parent_uri_of(event), registry) var = registry&.assign_post(event) end compose(var, format_time(event.created_at), handle, event.did, body) end |
.format_status_time(time) ⇒ Object
69 70 71 |
# File 'lib/tempest/repl/formatter.rb', line 69 def format_status_time(time) time.respond_to?(:localtime) ? time.localtime.strftime("%H:%M") : time.to_s end |
.format_time(iso) ⇒ Object
178 179 180 181 182 183 |
# File 'lib/tempest/repl/formatter.rb', line 178 def format_time(iso) return nil if iso.nil? || iso.empty? Time.iso8601(iso).localtime.strftime("%H:%M") rescue ArgumentError nil end |
.handle_label(handle) ⇒ Object
201 202 203 |
# File 'lib/tempest/repl/formatter.rb', line 201 def handle_label(handle) Formatter.color ? "#{GREEN}@#{handle}#{RESET}" : "@#{handle}" end |
.host_of(uri) ⇒ Object
166 167 168 169 170 171 172 |
# File 'lib/tempest/repl/formatter.rb', line 166 def host_of(uri) parsed = URI.parse(uri) host = parsed.host host && !host.empty? ? host : nil rescue URI::InvalidURIError nil end |
.id_label(var) ⇒ Object
197 198 199 |
# File 'lib/tempest/repl/formatter.rb', line 197 def id_label(var) Formatter.color ? "#{DIM}[#{var}]#{RESET} " : "[#{var}] " end |
.post_line(post, registry: nil) ⇒ Object
34 35 36 37 38 39 40 41 |
# File 'lib/tempest/repl/formatter.rb', line 34 def post_line(post, registry: nil) var = registry&.assign_post(post) facets = post.respond_to?(:facets) ? post.facets : nil body = annotate_urls(squeeze(post.text), registry, facets: facets) body = decorate_body(body) body = prepend_reply_marker(body, reply_parent_uri_of(post), registry) compose(var, format_time(post.created_at), post.handle, nil, body) end |
.prepend_reply_marker(body, reply_parent_uri, registry) ⇒ Object
118 119 120 121 122 123 124 125 |
# File 'lib/tempest/repl/formatter.rb', line 118 def prepend_reply_marker(body, reply_parent_uri, registry) return body if reply_parent_uri.nil? || reply_parent_uri.empty? return body unless registry parent_var = registry.var_for_uri(reply_parent_uri) marker = parent_var ? "↪#{parent_var} " : "↪ " "#{marker}#{body}" end |
.reply_parent_uri_of(record) ⇒ Object
114 115 116 |
# File 'lib/tempest/repl/formatter.rb', line 114 def reply_parent_uri_of(record) record.respond_to?(:reply_parent_uri) ? record.reply_parent_uri : nil end |
.squeeze(text) ⇒ Object
174 175 176 |
# File 'lib/tempest/repl/formatter.rb', line 174 def squeeze(text) text.to_s.gsub(/\s*\n\s*/, " ") end |
.status_line(status) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/tempest/repl/formatter.rb', line 53 def status_line(status) body = case status.state when :disconnected status.reason == :error && status.error ? "disconnected: #{status.error.}" : "disconnected" when :reconnecting "reconnecting..." when :live "live" when :gapped "fetching timeline (offline since #{format_status_time(status.since)})" else status.state.to_s end Formatter.color ? "#{DIM}-- #{body}#{RESET}" : "-- #{body}" end |
.subject_did(subject_uri) ⇒ Object
108 109 110 111 112 |
# File 'lib/tempest/repl/formatter.rb', line 108 def subject_did(subject_uri) return nil if subject_uri.nil? || subject_uri.empty? match = subject_uri.match(%r{\Aat://([^/]+)/}) match && match[1] end |
.subject_owner_label(subject_uri, resolver, registry = nil) ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/tempest/repl/formatter.rb', line 94 def subject_owner_label(subject_uri, resolver, registry = nil) did = subject_did(subject_uri) return "a post" unless did handle = resolver&.resolve(did) owner = handle ? handle_label(handle) : did_label(did) label = "#{owner}'s post" var = registry&.var_for_uri(subject_uri) return label unless var bracket = Formatter.color ? "#{DIM}[#{var}]#{RESET}" : "[#{var}]" "#{label} #{bracket}" end |