spoom 1.0.1 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +4 -1
- data/README.md +253 -1
- data/Rakefile +2 -0
- data/exe/spoom +7 -0
- data/lib/spoom.rb +9 -1
- data/lib/spoom/cli.rb +68 -0
- data/lib/spoom/cli/bump.rb +59 -0
- data/lib/spoom/cli/config.rb +51 -0
- data/lib/spoom/cli/coverage.rb +191 -0
- data/lib/spoom/cli/helper.rb +70 -0
- data/lib/spoom/cli/lsp.rb +165 -0
- data/lib/spoom/cli/run.rb +79 -0
- data/lib/spoom/config.rb +11 -0
- data/lib/spoom/coverage.rb +73 -0
- data/lib/spoom/coverage/d3.rb +110 -0
- data/lib/spoom/coverage/d3/base.rb +50 -0
- data/lib/spoom/coverage/d3/circle_map.rb +195 -0
- data/lib/spoom/coverage/d3/pie.rb +175 -0
- data/lib/spoom/coverage/d3/timeline.rb +486 -0
- data/lib/spoom/coverage/report.rb +308 -0
- data/lib/spoom/coverage/snapshot.rb +132 -0
- data/lib/spoom/file_tree.rb +196 -0
- data/lib/spoom/git.rb +98 -0
- data/lib/spoom/printer.rb +81 -0
- data/lib/spoom/sorbet.rb +83 -0
- data/lib/spoom/sorbet/config.rb +21 -9
- data/lib/spoom/sorbet/errors.rb +139 -0
- data/lib/spoom/sorbet/lsp.rb +196 -0
- data/lib/spoom/sorbet/lsp/base.rb +58 -0
- data/lib/spoom/sorbet/lsp/errors.rb +45 -0
- data/lib/spoom/sorbet/lsp/structures.rb +312 -0
- data/lib/spoom/sorbet/metrics.rb +33 -0
- data/lib/spoom/sorbet/sigils.rb +98 -0
- data/lib/spoom/test_helpers/project.rb +103 -0
- data/lib/spoom/timeline.rb +53 -0
- data/lib/spoom/version.rb +2 -1
- data/templates/card.erb +8 -0
- data/templates/card_snapshot.erb +22 -0
- data/templates/page.erb +50 -0
- metadata +80 -20
@@ -0,0 +1,196 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'open3'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require_relative 'lsp/base'
|
8
|
+
require_relative 'lsp/structures'
|
9
|
+
require_relative 'lsp/errors'
|
10
|
+
|
11
|
+
module Spoom
|
12
|
+
module LSP
|
13
|
+
class Client
|
14
|
+
def initialize(sorbet_cmd, *sorbet_args, path: ".")
|
15
|
+
@id = 0
|
16
|
+
Bundler.with_clean_env do
|
17
|
+
opts = {}
|
18
|
+
opts[:chdir] = path
|
19
|
+
@in, @out, @err, @status = Open3.popen3([sorbet_cmd, *sorbet_args].join(" "), opts)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def next_id
|
24
|
+
@id += 1
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_raw(json_string)
|
28
|
+
@in.puts("Content-Length:#{json_string.length}\r\n\r\n#{json_string}")
|
29
|
+
end
|
30
|
+
|
31
|
+
def send(message)
|
32
|
+
send_raw(message.to_json)
|
33
|
+
read if message.is_a?(Request)
|
34
|
+
end
|
35
|
+
|
36
|
+
def read_raw
|
37
|
+
header = @out.gets
|
38
|
+
|
39
|
+
# Sorbet returned an error and forgot to answer
|
40
|
+
raise Error::BadHeaders, "bad response headers" unless header&.match?(/Content-Length: /)
|
41
|
+
|
42
|
+
len = header.slice(::Range.new(16, nil)).to_i
|
43
|
+
@out.read(len + 2) # +2 'cause of the final \r\n
|
44
|
+
end
|
45
|
+
|
46
|
+
def read
|
47
|
+
json = JSON.parse(read_raw)
|
48
|
+
|
49
|
+
# Handle error in the LSP protocol
|
50
|
+
raise ResponseError.from_json(json['error']) if json['error']
|
51
|
+
|
52
|
+
# Handle typechecking errors
|
53
|
+
raise Error::Diagnostics.from_json(json['params']) if json['method'] == "textDocument/publishDiagnostics"
|
54
|
+
|
55
|
+
json
|
56
|
+
end
|
57
|
+
|
58
|
+
# LSP requests
|
59
|
+
|
60
|
+
def open(workspace_path)
|
61
|
+
raise Error::AlreadyOpen, "Error: CLI already opened" if @open
|
62
|
+
send(Request.new(
|
63
|
+
next_id,
|
64
|
+
'initialize',
|
65
|
+
{
|
66
|
+
'rootPath' => workspace_path,
|
67
|
+
'rootUri' => "file://#{workspace_path}",
|
68
|
+
'capabilities' => {},
|
69
|
+
},
|
70
|
+
))
|
71
|
+
send(Notification.new('initialized', {}))
|
72
|
+
@open = true
|
73
|
+
end
|
74
|
+
|
75
|
+
def hover(uri, line, column)
|
76
|
+
json = send(Request.new(
|
77
|
+
next_id,
|
78
|
+
'textDocument/hover',
|
79
|
+
{
|
80
|
+
'textDocument' => {
|
81
|
+
'uri' => uri,
|
82
|
+
},
|
83
|
+
'position' => {
|
84
|
+
'line' => line,
|
85
|
+
'character' => column,
|
86
|
+
},
|
87
|
+
}
|
88
|
+
))
|
89
|
+
return nil unless json['result']
|
90
|
+
Hover.from_json(json['result'])
|
91
|
+
end
|
92
|
+
|
93
|
+
def signatures(uri, line, column)
|
94
|
+
json = send(Request.new(
|
95
|
+
next_id,
|
96
|
+
'textDocument/signatureHelp',
|
97
|
+
{
|
98
|
+
'textDocument' => {
|
99
|
+
'uri' => uri,
|
100
|
+
},
|
101
|
+
'position' => {
|
102
|
+
'line' => line,
|
103
|
+
'character' => column,
|
104
|
+
},
|
105
|
+
}
|
106
|
+
))
|
107
|
+
json['result']['signatures'].map { |loc| SignatureHelp.from_json(loc) }
|
108
|
+
end
|
109
|
+
|
110
|
+
def definitions(uri, line, column)
|
111
|
+
json = send(Request.new(
|
112
|
+
next_id,
|
113
|
+
'textDocument/definition',
|
114
|
+
{
|
115
|
+
'textDocument' => {
|
116
|
+
'uri' => uri,
|
117
|
+
},
|
118
|
+
'position' => {
|
119
|
+
'line' => line,
|
120
|
+
'character' => column,
|
121
|
+
},
|
122
|
+
}
|
123
|
+
))
|
124
|
+
json['result'].map { |loc| Location.from_json(loc) }
|
125
|
+
end
|
126
|
+
|
127
|
+
def type_definitions(uri, line, column)
|
128
|
+
json = send(Request.new(
|
129
|
+
next_id,
|
130
|
+
'textDocument/typeDefinition',
|
131
|
+
{
|
132
|
+
'textDocument' => {
|
133
|
+
'uri' => uri,
|
134
|
+
},
|
135
|
+
'position' => {
|
136
|
+
'line' => line,
|
137
|
+
'character' => column,
|
138
|
+
},
|
139
|
+
}
|
140
|
+
))
|
141
|
+
json['result'].map { |loc| Location.from_json(loc) }
|
142
|
+
end
|
143
|
+
|
144
|
+
def references(uri, line, column, include_decl = true)
|
145
|
+
json = send(Request.new(
|
146
|
+
next_id,
|
147
|
+
'textDocument/references',
|
148
|
+
{
|
149
|
+
'textDocument' => {
|
150
|
+
'uri' => uri,
|
151
|
+
},
|
152
|
+
'position' => {
|
153
|
+
'line' => line,
|
154
|
+
'character' => column,
|
155
|
+
},
|
156
|
+
'context' => {
|
157
|
+
'includeDeclaration' => include_decl,
|
158
|
+
},
|
159
|
+
}
|
160
|
+
))
|
161
|
+
json['result'].map { |loc| Location.from_json(loc) }
|
162
|
+
end
|
163
|
+
|
164
|
+
def symbols(query)
|
165
|
+
json = send(Request.new(
|
166
|
+
next_id,
|
167
|
+
'workspace/symbol',
|
168
|
+
{
|
169
|
+
'query' => query,
|
170
|
+
}
|
171
|
+
))
|
172
|
+
json['result'].map { |loc| DocumentSymbol.from_json(loc) }
|
173
|
+
end
|
174
|
+
|
175
|
+
def document_symbols(uri)
|
176
|
+
json = send(Request.new(
|
177
|
+
next_id,
|
178
|
+
'textDocument/documentSymbol',
|
179
|
+
{
|
180
|
+
'textDocument' => {
|
181
|
+
'uri' => uri,
|
182
|
+
},
|
183
|
+
}
|
184
|
+
))
|
185
|
+
json['result'].map { |loc| DocumentSymbol.from_json(loc) }
|
186
|
+
end
|
187
|
+
|
188
|
+
def close
|
189
|
+
send(Request.new(next_id, "shutdown", nil))
|
190
|
+
@in.close
|
191
|
+
@out.close
|
192
|
+
@err.close
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module LSP
|
6
|
+
# Base messaging
|
7
|
+
# We don't use T::Struct for those so we can subclass them
|
8
|
+
|
9
|
+
# A general message as defined by JSON-RPC.
|
10
|
+
#
|
11
|
+
# The language server protocol always uses `"2.0"` as the `jsonrpc` version.
|
12
|
+
class Message
|
13
|
+
attr_reader :jsonrpc
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@jsonrpc = '2.0'
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_json
|
20
|
+
instance_variables.each_with_object({}) do |var, obj|
|
21
|
+
val = instance_variable_get(var)
|
22
|
+
obj[var.to_s.delete('@')] = val if val
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_json(*args)
|
27
|
+
as_json.to_json(*args)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# A request message to describe a request between the client and the server.
|
32
|
+
#
|
33
|
+
# Every processed request must send a response back to the sender of the request.
|
34
|
+
class Request < Message
|
35
|
+
attr_reader :id, :method, :params
|
36
|
+
|
37
|
+
def initialize(id, method, params)
|
38
|
+
super()
|
39
|
+
@id = id
|
40
|
+
@method = method
|
41
|
+
@params = params
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# A notification message.
|
46
|
+
#
|
47
|
+
# A processed notification message must not send a response back. They work like events.
|
48
|
+
class Notification < Message
|
49
|
+
attr_reader :method, :params
|
50
|
+
|
51
|
+
def initialize(method, params)
|
52
|
+
super()
|
53
|
+
@method = method
|
54
|
+
@params = params
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
module Spoom
|
5
|
+
module LSP
|
6
|
+
class Error < StandardError
|
7
|
+
class AlreadyOpen < Error; end
|
8
|
+
class BadHeaders < Error; end
|
9
|
+
|
10
|
+
class Diagnostics < Error
|
11
|
+
attr_reader :uri, :diagnostics
|
12
|
+
|
13
|
+
def self.from_json(json)
|
14
|
+
Diagnostics.new(
|
15
|
+
json['uri'],
|
16
|
+
json['diagnostics'].map { |d| Diagnostic.from_json(d) }
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(uri, diagnostics)
|
21
|
+
@uri = uri
|
22
|
+
@diagnostics = diagnostics
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ResponseError < Error
|
28
|
+
attr_reader :code, :message, :data
|
29
|
+
|
30
|
+
def self.from_json(json)
|
31
|
+
ResponseError.new(
|
32
|
+
json['code'],
|
33
|
+
json['message'],
|
34
|
+
json['data']
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(code, message, data)
|
39
|
+
@code = code
|
40
|
+
@message = message
|
41
|
+
@data = data
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,312 @@
|
|
1
|
+
# typed: true
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require_relative "../../printer"
|
5
|
+
|
6
|
+
module Spoom
|
7
|
+
module LSP
|
8
|
+
module PrintableSymbol
|
9
|
+
extend T::Sig
|
10
|
+
extend T::Helpers
|
11
|
+
|
12
|
+
interface!
|
13
|
+
|
14
|
+
sig { abstract.params(printer: SymbolPrinter).void }
|
15
|
+
def accept_printer(printer); end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Hover < T::Struct
|
19
|
+
extend T::Sig
|
20
|
+
include PrintableSymbol
|
21
|
+
|
22
|
+
const :contents, String
|
23
|
+
const :range, T.nilable(Range)
|
24
|
+
|
25
|
+
def self.from_json(json)
|
26
|
+
Hover.new(
|
27
|
+
contents: json['contents']['value'],
|
28
|
+
range: json['range'] ? Range.from_json(json['range']) : nil
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
sig { override.params(printer: SymbolPrinter).void }
|
33
|
+
def accept_printer(printer)
|
34
|
+
printer.print("#{contents}\n")
|
35
|
+
printer.print_object(range) if range
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
"#{contents} (#{range})."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Position < T::Struct
|
44
|
+
extend T::Sig
|
45
|
+
include PrintableSymbol
|
46
|
+
|
47
|
+
const :line, Integer
|
48
|
+
const :char, Integer
|
49
|
+
|
50
|
+
def self.from_json(json)
|
51
|
+
Position.new(
|
52
|
+
line: json['line'].to_i,
|
53
|
+
char: json['character'].to_i
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
sig { override.params(printer: SymbolPrinter).void }
|
58
|
+
def accept_printer(printer)
|
59
|
+
printer.print_colored("#{line}:#{char}", :light_black)
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"#{line}:#{char}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class Range < T::Struct
|
68
|
+
extend T::Sig
|
69
|
+
include PrintableSymbol
|
70
|
+
|
71
|
+
const :start, Position
|
72
|
+
const :end, Position
|
73
|
+
|
74
|
+
def self.from_json(json)
|
75
|
+
Range.new(
|
76
|
+
start: Position.from_json(json['start']),
|
77
|
+
end: Position.from_json(json['end'])
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
sig { override.params(printer: SymbolPrinter).void }
|
82
|
+
def accept_printer(printer)
|
83
|
+
printer.print_object(start)
|
84
|
+
printer.print_colored("-", :light_black)
|
85
|
+
printer.print_object(self.end)
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_s
|
89
|
+
"#{start}-#{self.end}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
class Location < T::Struct
|
94
|
+
extend T::Sig
|
95
|
+
include PrintableSymbol
|
96
|
+
|
97
|
+
const :uri, String
|
98
|
+
const :range, LSP::Range
|
99
|
+
|
100
|
+
def self.from_json(json)
|
101
|
+
Location.new(
|
102
|
+
uri: json['uri'],
|
103
|
+
range: Range.from_json(json['range'])
|
104
|
+
)
|
105
|
+
end
|
106
|
+
|
107
|
+
sig { override.params(printer: SymbolPrinter).void }
|
108
|
+
def accept_printer(printer)
|
109
|
+
printer.print_colored("#{printer.clean_uri(uri)}:", :light_black)
|
110
|
+
printer.print_object(range)
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
"#{uri}:#{range}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class SignatureHelp < T::Struct
|
119
|
+
extend T::Sig
|
120
|
+
include PrintableSymbol
|
121
|
+
|
122
|
+
const :label, T.nilable(String)
|
123
|
+
const :doc, Object # TODO
|
124
|
+
const :params, T::Array[T.untyped] # TODO
|
125
|
+
|
126
|
+
def self.from_json(json)
|
127
|
+
SignatureHelp.new(
|
128
|
+
label: json['label'],
|
129
|
+
doc: json['documentation'],
|
130
|
+
params: json['parameters'],
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
134
|
+
sig { override.params(printer: SymbolPrinter).void }
|
135
|
+
def accept_printer(printer)
|
136
|
+
printer.print(label)
|
137
|
+
printer.print("(")
|
138
|
+
printer.print(params.map { |l| "#{l['label']}: #{l['documentation']}" }.join(", "))
|
139
|
+
printer.print(")")
|
140
|
+
end
|
141
|
+
|
142
|
+
def to_s
|
143
|
+
"#{label}(#{params})."
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
class Diagnostic < T::Struct
|
148
|
+
extend T::Sig
|
149
|
+
include PrintableSymbol
|
150
|
+
|
151
|
+
const :range, LSP::Range
|
152
|
+
const :code, Integer
|
153
|
+
const :message, String
|
154
|
+
const :informations, Object
|
155
|
+
|
156
|
+
def self.from_json(json)
|
157
|
+
Diagnostic.new(
|
158
|
+
range: Range.from_json(json['range']),
|
159
|
+
code: json['code'].to_i,
|
160
|
+
message: json['message'],
|
161
|
+
informations: json['relatedInformation']
|
162
|
+
)
|
163
|
+
end
|
164
|
+
|
165
|
+
sig { override.params(printer: SymbolPrinter).void }
|
166
|
+
def accept_printer(printer)
|
167
|
+
printer.print(to_s)
|
168
|
+
end
|
169
|
+
|
170
|
+
def to_s
|
171
|
+
"Error: #{message} (#{code})."
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
class DocumentSymbol < T::Struct
|
176
|
+
extend T::Sig
|
177
|
+
include PrintableSymbol
|
178
|
+
|
179
|
+
const :name, String
|
180
|
+
const :detail, T.nilable(String)
|
181
|
+
const :kind, Integer
|
182
|
+
const :location, T.nilable(Location)
|
183
|
+
const :range, T.nilable(Range)
|
184
|
+
const :children, T::Array[DocumentSymbol]
|
185
|
+
|
186
|
+
def self.from_json(json)
|
187
|
+
DocumentSymbol.new(
|
188
|
+
name: json['name'],
|
189
|
+
detail: json['detail'],
|
190
|
+
kind: json['kind'],
|
191
|
+
location: json['location'] ? Location.from_json(json['location']) : nil,
|
192
|
+
range: json['range'] ? Range.from_json(json['range']) : nil,
|
193
|
+
children: json['children'] ? json['children'].map { |symbol| DocumentSymbol.from_json(symbol) } : [],
|
194
|
+
)
|
195
|
+
end
|
196
|
+
|
197
|
+
sig { override.params(printer: SymbolPrinter).void }
|
198
|
+
def accept_printer(printer)
|
199
|
+
h = serialize.hash
|
200
|
+
return if printer.seen.include?(h)
|
201
|
+
printer.seen.add(h)
|
202
|
+
|
203
|
+
printer.printt
|
204
|
+
printer.print(kind_string)
|
205
|
+
printer.print(' ')
|
206
|
+
printer.print_colored(name, :blue, :bold)
|
207
|
+
printer.print_colored(' (', :light_black)
|
208
|
+
if range
|
209
|
+
printer.print_object(range)
|
210
|
+
elsif location
|
211
|
+
printer.print_object(location)
|
212
|
+
end
|
213
|
+
printer.print_colored(')', :light_black)
|
214
|
+
printer.printn
|
215
|
+
unless children.empty?
|
216
|
+
printer.indent
|
217
|
+
printer.print_objects(children)
|
218
|
+
printer.dedent
|
219
|
+
end
|
220
|
+
# TODO: also display details?
|
221
|
+
end
|
222
|
+
|
223
|
+
def to_s
|
224
|
+
"#{name} (#{range})"
|
225
|
+
end
|
226
|
+
|
227
|
+
def kind_string
|
228
|
+
return "<unknown:#{kind}>" unless SYMBOL_KINDS.key?(kind)
|
229
|
+
SYMBOL_KINDS[kind]
|
230
|
+
end
|
231
|
+
|
232
|
+
SYMBOL_KINDS = {
|
233
|
+
1 => "file",
|
234
|
+
2 => "module",
|
235
|
+
3 => "namespace",
|
236
|
+
4 => "package",
|
237
|
+
5 => "class",
|
238
|
+
6 => "def",
|
239
|
+
7 => "property",
|
240
|
+
8 => "field",
|
241
|
+
9 => "constructor",
|
242
|
+
10 => "enum",
|
243
|
+
11 => "interface",
|
244
|
+
12 => "function",
|
245
|
+
13 => "variable",
|
246
|
+
14 => "const",
|
247
|
+
15 => "string",
|
248
|
+
16 => "number",
|
249
|
+
17 => "boolean",
|
250
|
+
18 => "array",
|
251
|
+
19 => "object",
|
252
|
+
20 => "key",
|
253
|
+
21 => "null",
|
254
|
+
22 => "enum_member",
|
255
|
+
23 => "struct",
|
256
|
+
24 => "event",
|
257
|
+
25 => "operator",
|
258
|
+
26 => "type_parameter",
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
class SymbolPrinter < Printer
|
263
|
+
extend T::Sig
|
264
|
+
|
265
|
+
attr_accessor :seen, :prefix
|
266
|
+
|
267
|
+
sig do
|
268
|
+
params(
|
269
|
+
out: T.any(IO, StringIO),
|
270
|
+
colors: T::Boolean,
|
271
|
+
indent_level: Integer,
|
272
|
+
prefix: T.nilable(String)
|
273
|
+
).void
|
274
|
+
end
|
275
|
+
def initialize(out: $stdout, colors: true, indent_level: 0, prefix: nil)
|
276
|
+
super(out: out, colors: colors, indent_level: indent_level)
|
277
|
+
@seen = Set.new
|
278
|
+
@out = out
|
279
|
+
@colors = colors
|
280
|
+
@indent_level = indent_level
|
281
|
+
@prefix = prefix
|
282
|
+
end
|
283
|
+
|
284
|
+
sig { params(object: T.nilable(PrintableSymbol)).void }
|
285
|
+
def print_object(object)
|
286
|
+
return unless object
|
287
|
+
object.accept_printer(self)
|
288
|
+
end
|
289
|
+
|
290
|
+
sig { params(objects: T::Array[PrintableSymbol]).void }
|
291
|
+
def print_objects(objects)
|
292
|
+
objects.each { |object| print_object(object) }
|
293
|
+
end
|
294
|
+
|
295
|
+
sig { params(uri: String).returns(String) }
|
296
|
+
def clean_uri(uri)
|
297
|
+
return uri unless prefix
|
298
|
+
uri.delete_prefix(prefix)
|
299
|
+
end
|
300
|
+
|
301
|
+
sig { params(objects: T::Array[PrintableSymbol]).void }
|
302
|
+
def print_list(objects)
|
303
|
+
objects.each do |object|
|
304
|
+
printt
|
305
|
+
print "* "
|
306
|
+
print_object(object)
|
307
|
+
printn
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|