net-imap 0.3.7 → 0.4.4
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.
Potentially problematic release.
This version of net-imap might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.github/workflows/pages.yml +46 -0
- data/.github/workflows/test.yml +5 -12
- data/.gitignore +1 -0
- data/Gemfile +3 -0
- data/README.md +15 -4
- data/Rakefile +0 -7
- data/lib/net/imap/authenticators.rb +26 -57
- data/lib/net/imap/command_data.rb +13 -6
- data/lib/net/imap/deprecated_client_options.rb +139 -0
- data/lib/net/imap/errors.rb +20 -0
- data/lib/net/imap/response_data.rb +92 -47
- data/lib/net/imap/response_parser/parser_utils.rb +240 -0
- data/lib/net/imap/response_parser.rb +1265 -986
- data/lib/net/imap/sasl/anonymous_authenticator.rb +69 -0
- data/lib/net/imap/sasl/authentication_exchange.rb +107 -0
- data/lib/net/imap/sasl/authenticators.rb +118 -0
- data/lib/net/imap/sasl/client_adapter.rb +72 -0
- data/lib/net/imap/{authenticators/cram_md5.rb → sasl/cram_md5_authenticator.rb} +21 -11
- data/lib/net/imap/sasl/digest_md5_authenticator.rb +180 -0
- data/lib/net/imap/sasl/external_authenticator.rb +83 -0
- data/lib/net/imap/sasl/gs2_header.rb +80 -0
- data/lib/net/imap/{authenticators/login.rb → sasl/login_authenticator.rb} +25 -16
- data/lib/net/imap/sasl/oauthbearer_authenticator.rb +199 -0
- data/lib/net/imap/sasl/plain_authenticator.rb +101 -0
- data/lib/net/imap/sasl/protocol_adapters.rb +45 -0
- data/lib/net/imap/sasl/scram_algorithm.rb +58 -0
- data/lib/net/imap/sasl/scram_authenticator.rb +287 -0
- data/lib/net/imap/sasl/stringprep.rb +6 -66
- data/lib/net/imap/sasl/xoauth2_authenticator.rb +106 -0
- data/lib/net/imap/sasl.rb +144 -43
- data/lib/net/imap/sasl_adapter.rb +21 -0
- data/lib/net/imap/stringprep/nameprep.rb +70 -0
- data/lib/net/imap/stringprep/saslprep.rb +69 -0
- data/lib/net/imap/stringprep/saslprep_tables.rb +96 -0
- data/lib/net/imap/stringprep/tables.rb +146 -0
- data/lib/net/imap/stringprep/trace.rb +85 -0
- data/lib/net/imap/stringprep.rb +159 -0
- data/lib/net/imap.rb +993 -609
- data/net-imap.gemspec +4 -3
- data/rakelib/benchmarks.rake +98 -0
- data/rakelib/saslprep.rake +4 -4
- data/rakelib/string_prep_tables_generator.rb +82 -60
- metadata +29 -13
- data/benchmarks/stringprep.yml +0 -65
- data/benchmarks/table-regexps.yml +0 -39
- data/lib/net/imap/authenticators/digest_md5.rb +0 -115
- data/lib/net/imap/authenticators/plain.rb +0 -41
- data/lib/net/imap/authenticators/xoauth2.rb +0 -20
- data/lib/net/imap/sasl/saslprep.rb +0 -55
- data/lib/net/imap/sasl/saslprep_tables.rb +0 -98
- data/lib/net/imap/sasl/stringprep_tables.rb +0 -153
@@ -55,17 +55,54 @@ module Net
|
|
55
55
|
|
56
56
|
# Net::IMAP::IgnoredResponse represents intentionally ignored responses.
|
57
57
|
#
|
58
|
-
# This includes untagged response "NOOP" sent by eg. Zimbra to avoid
|
59
|
-
# clients to close the connection.
|
58
|
+
# This includes untagged response "NOOP" sent by eg. Zimbra to avoid
|
59
|
+
# some clients to close the connection.
|
60
60
|
#
|
61
61
|
# It matches no IMAP standard.
|
62
|
+
class IgnoredResponse < UntaggedResponse
|
63
|
+
end
|
64
|
+
|
65
|
+
# **Note:** This represents an intentionally _unstable_ API. Where
|
66
|
+
# instances of this class are returned, future releases may return a
|
67
|
+
# different (incompatible) object <em>without deprecation or warning</em>.
|
62
68
|
#
|
63
|
-
|
69
|
+
# Net::IMAP::UnparsedData represents data for unknown response types or
|
70
|
+
# unknown extensions to response types without a well-defined extension
|
71
|
+
# grammar.
|
72
|
+
#
|
73
|
+
# See also: UnparsedNumericResponseData
|
74
|
+
class UnparsedData < Struct.new(:unparsed_data)
|
64
75
|
##
|
65
|
-
# method:
|
66
|
-
# :call-seq:
|
76
|
+
# method: unparsed_data
|
77
|
+
# :call-seq: unparsed_data -> string
|
67
78
|
#
|
68
|
-
# The
|
79
|
+
# The unparsed data
|
80
|
+
end
|
81
|
+
|
82
|
+
# **Note:** This represents an intentionally _unstable_ API. Where
|
83
|
+
# instances of this class are returned, future releases may return a
|
84
|
+
# different (incompatible) object <em>without deprecation or warning</em>.
|
85
|
+
#
|
86
|
+
# Net::IMAP::UnparsedNumericResponseData represents data for unhandled
|
87
|
+
# response types with a numeric prefix. See the documentation for #number.
|
88
|
+
#
|
89
|
+
# See also: UnparsedData
|
90
|
+
class UnparsedNumericResponseData < Struct.new(:number, :unparsed_data)
|
91
|
+
##
|
92
|
+
# method: number
|
93
|
+
# :call-seq: number -> integer
|
94
|
+
#
|
95
|
+
# Returns a numeric response data prefix, when available.
|
96
|
+
#
|
97
|
+
# Many response types are prefixed with a non-negative #number. For
|
98
|
+
# message data, #number may represent a sequence number or a UID. For
|
99
|
+
# mailbox data, #number may represent a message count.
|
100
|
+
|
101
|
+
##
|
102
|
+
# method: unparsed_data
|
103
|
+
# :call-seq: unparsed_data -> string
|
104
|
+
#
|
105
|
+
# The unparsed data, not including #number or UntaggedResponse#name.
|
69
106
|
end
|
70
107
|
|
71
108
|
# Net::IMAP::TaggedResponse represents tagged responses.
|
@@ -108,6 +145,9 @@ module Net
|
|
108
145
|
# UntaggedResponse#data when the response type is a "condition" ("OK", "NO",
|
109
146
|
# "BAD", "PREAUTH", or "BYE").
|
110
147
|
class ResponseText < Struct.new(:code, :text)
|
148
|
+
# Used to avoid an allocation when ResponseText is empty
|
149
|
+
EMPTY = new(nil, "").freeze
|
150
|
+
|
111
151
|
##
|
112
152
|
# method: code
|
113
153
|
# :call-seq: code -> ResponseCode or nil
|
@@ -891,13 +931,6 @@ module Net
|
|
891
931
|
# should use BodyTypeBasic.
|
892
932
|
# BodyTypeMultipart:: for <tt>multipart/*</tt> parts
|
893
933
|
#
|
894
|
-
# ==== Deprecated BodyStructure classes
|
895
|
-
# The following classes represent invalid server responses or parser bugs:
|
896
|
-
# BodyTypeExtension:: parser bug: used for <tt>message/*</tt> where
|
897
|
-
# BodyTypeBasic should have been used.
|
898
|
-
# BodyTypeAttachment:: server bug: some servers sometimes return the
|
899
|
-
# "Content-Disposition: attachment" data where the
|
900
|
-
# entire body structure for a message part is expected.
|
901
934
|
module BodyStructure
|
902
935
|
end
|
903
936
|
|
@@ -914,6 +947,7 @@ module Net
|
|
914
947
|
:param, :content_id,
|
915
948
|
:description, :encoding, :size,
|
916
949
|
:md5, :disposition, :language,
|
950
|
+
:location,
|
917
951
|
:extension)
|
918
952
|
include BodyStructure
|
919
953
|
|
@@ -1049,6 +1083,7 @@ module Net
|
|
1049
1083
|
:description, :encoding, :size,
|
1050
1084
|
:lines,
|
1051
1085
|
:md5, :disposition, :language,
|
1086
|
+
:location,
|
1052
1087
|
:extension)
|
1053
1088
|
include BodyStructure
|
1054
1089
|
|
@@ -1094,6 +1129,7 @@ module Net
|
|
1094
1129
|
:description, :encoding, :size,
|
1095
1130
|
:envelope, :body, :lines,
|
1096
1131
|
:md5, :disposition, :language,
|
1132
|
+
:location,
|
1097
1133
|
:extension)
|
1098
1134
|
include BodyStructure
|
1099
1135
|
|
@@ -1126,36 +1162,41 @@ module Net
|
|
1126
1162
|
end
|
1127
1163
|
end
|
1128
1164
|
|
1129
|
-
#
|
1130
|
-
# BodyTypeAttachment represents a <tt>body-fld-dsp</tt> that is
|
1131
|
-
# incorrectly in a position where the IMAP4rev1 grammar expects a nested
|
1132
|
-
# +body+ structure.
|
1165
|
+
# BodyTypeAttachment is not used and will be removed in an upcoming release.
|
1133
1166
|
#
|
1134
|
-
#
|
1135
|
-
#
|
1136
|
-
#
|
1137
|
-
#
|
1138
|
-
#
|
1139
|
-
#
|
1140
|
-
#
|
1141
|
-
#
|
1142
|
-
#
|
1143
|
-
#
|
1144
|
-
#
|
1145
|
-
#
|
1146
|
-
#
|
1147
|
-
#
|
1148
|
-
#
|
1149
|
-
#
|
1150
|
-
#
|
1151
|
-
#
|
1152
|
-
#
|
1153
|
-
#
|
1154
|
-
#
|
1167
|
+
# === Bug Analysis
|
1168
|
+
#
|
1169
|
+
# \IMAP body structures are parenthesized lists and assign their fields
|
1170
|
+
# positionally, so missing fields change the intepretation of all
|
1171
|
+
# following fields. Additionally, different body types have a different
|
1172
|
+
# number of required fields, followed by optional "extension" fields.
|
1173
|
+
#
|
1174
|
+
# BodyTypeAttachment was previously returned when a "message/rfc822" part,
|
1175
|
+
# which should be sent as <tt>body-type-msg</tt> with ten required fields,
|
1176
|
+
# was actually sent as a <tt>body-type-basic</tt> with _seven_ required
|
1177
|
+
# fields.
|
1178
|
+
#
|
1179
|
+
# basic => type, subtype, param, id, desc, enc, octets, md5=nil, dsp=nil, lang=nil, loc=nil, *ext
|
1180
|
+
# msg => type, subtype, param, id, desc, enc, octets, envelope, body, lines, md5=nil, ...
|
1181
|
+
#
|
1182
|
+
# Normally, +envelope+ and +md5+ are incompatible, but Net::IMAP leniently
|
1183
|
+
# allowed buggy servers to send +NIL+ for +envelope+. As a result, when a
|
1184
|
+
# server sent a <tt>message/rfc822</tt> part with +NIL+ for +md5+ and a
|
1185
|
+
# non-<tt>NIL</tt> +dsp+, Net::IMAP mis-interpreted the
|
1186
|
+
# <tt>Content-Disposition</tt> as if it were a strange body type. In all
|
1187
|
+
# reported cases, the <tt>Content-Disposition</tt> was "attachment", so
|
1188
|
+
# BodyTypeAttachment was created as the workaround.
|
1189
|
+
#
|
1190
|
+
# === Current behavior
|
1191
|
+
#
|
1192
|
+
# When interpreted strictly, +envelope+ and +md5+ are incompatible. So the
|
1193
|
+
# current parsing algorithm peeks ahead after it has recieved the seventh
|
1194
|
+
# body field. If the next token is not the start of an +envelope+, we assume
|
1195
|
+
# the server has incorrectly sent us a <tt>body-type-basic</tt> and return
|
1196
|
+
# BodyTypeBasic. As a result, what was previously BodyTypeMessage#body =>
|
1197
|
+
# BodyTypeAttachment is now BodyTypeBasic#disposition => ContentDisposition.
|
1155
1198
|
#
|
1156
1199
|
class BodyTypeAttachment < Struct.new(:dsp_type, :_unused_, :param)
|
1157
|
-
include BodyStructure
|
1158
|
-
|
1159
1200
|
# *invalid for BodyTypeAttachment*
|
1160
1201
|
def media_type
|
1161
1202
|
warn(<<~WARN, uplevel: 1)
|
@@ -1190,11 +1231,14 @@ module Net
|
|
1190
1231
|
end
|
1191
1232
|
end
|
1192
1233
|
|
1234
|
+
deprecate_constant :BodyTypeAttachment
|
1235
|
+
|
1193
1236
|
# Net::IMAP::BodyTypeMultipart represents body structures of messages and
|
1194
1237
|
# message parts, when <tt>Content-Type</tt> is <tt>multipart/*</tt>.
|
1195
1238
|
class BodyTypeMultipart < Struct.new(:media_type, :subtype,
|
1196
1239
|
:parts,
|
1197
1240
|
:param, :disposition, :language,
|
1241
|
+
:location,
|
1198
1242
|
:extension)
|
1199
1243
|
include BodyStructure
|
1200
1244
|
|
@@ -1265,23 +1309,24 @@ module Net
|
|
1265
1309
|
end
|
1266
1310
|
end
|
1267
1311
|
|
1268
|
-
# ===
|
1312
|
+
# === Obsolete
|
1313
|
+
# BodyTypeExtension is not used and will be removed in an upcoming release.
|
1314
|
+
#
|
1269
1315
|
# >>>
|
1270
|
-
# BodyTypeExtension
|
1316
|
+
# BodyTypeExtension was (incorrectly) used for <tt>message/*</tt> parts
|
1271
1317
|
# (besides <tt>message/rfc822</tt>, which correctly uses BodyTypeMessage).
|
1272
1318
|
#
|
1273
|
-
#
|
1274
|
-
#
|
1275
|
-
# * BodyTypeBasic for any other <tt>message/*</tt>
|
1319
|
+
# Net::IMAP now (correctly) parses all message types (other than
|
1320
|
+
# <tt>message/rfc822</tt> or <tt>message/global</tt>) as BodyTypeBasic.
|
1276
1321
|
class BodyTypeExtension < Struct.new(:media_type, :subtype,
|
1277
1322
|
:params, :content_id,
|
1278
1323
|
:description, :encoding, :size)
|
1279
|
-
include BodyStructure
|
1280
|
-
|
1281
1324
|
def multipart?
|
1282
1325
|
return false
|
1283
1326
|
end
|
1284
1327
|
end
|
1285
1328
|
|
1329
|
+
deprecate_constant :BodyTypeExtension
|
1330
|
+
|
1286
1331
|
end
|
1287
1332
|
end
|
@@ -0,0 +1,240 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Net
|
4
|
+
class IMAP < Protocol
|
5
|
+
class ResponseParser
|
6
|
+
# basic utility methods for parsing.
|
7
|
+
#
|
8
|
+
# (internal API, subject to change)
|
9
|
+
module ParserUtils # :nodoc:
|
10
|
+
|
11
|
+
module Generator
|
12
|
+
|
13
|
+
LOOKAHEAD = "(@token ||= next_token)"
|
14
|
+
SHIFT_TOKEN = "(@token = nil)"
|
15
|
+
|
16
|
+
# we can skip lexer for single character matches, as a shortcut
|
17
|
+
def def_char_matchers(name, char, token)
|
18
|
+
byte = char.ord
|
19
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
20
|
+
char = char.dump
|
21
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
22
|
+
# frozen_string_literal: true
|
23
|
+
|
24
|
+
# force use of #next_token; no string peeking
|
25
|
+
def lookahead_#{name}?
|
26
|
+
#{LOOKAHEAD}&.symbol == #{token}
|
27
|
+
end
|
28
|
+
|
29
|
+
# use token or string peek
|
30
|
+
def peek_#{name}?
|
31
|
+
@token ? @token.symbol == #{token} : @str.getbyte(@pos) == #{byte}
|
32
|
+
end
|
33
|
+
|
34
|
+
# like accept(token_symbols); returns token or nil
|
35
|
+
def #{name}?
|
36
|
+
if @token&.symbol == #{token}
|
37
|
+
#{SHIFT_TOKEN}
|
38
|
+
#{char}
|
39
|
+
elsif !@token && @str.getbyte(@pos) == #{byte}
|
40
|
+
@pos += 1
|
41
|
+
#{char}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# like match(token_symbols); returns token or raises parse_error
|
46
|
+
def #{match_name}
|
47
|
+
if @token&.symbol == #{token}
|
48
|
+
#{SHIFT_TOKEN}
|
49
|
+
#{char}
|
50
|
+
elsif !@token && @str.getbyte(@pos) == #{byte}
|
51
|
+
@pos += 1
|
52
|
+
#{char}
|
53
|
+
else
|
54
|
+
parse_error("unexpected %s (expected %p)",
|
55
|
+
@token&.symbol || @str[@pos].inspect, #{char})
|
56
|
+
end
|
57
|
+
end
|
58
|
+
RUBY
|
59
|
+
end
|
60
|
+
|
61
|
+
# TODO: move coersion to the token.value method?
|
62
|
+
def def_token_matchers(name, *token_symbols, coerce: nil, send: nil)
|
63
|
+
match_name = name.match(/\A[A-Z]/) ? "#{name}!" : name
|
64
|
+
|
65
|
+
if token_symbols.size == 1
|
66
|
+
token = token_symbols.first
|
67
|
+
matcher = "token&.symbol == %p" % [token]
|
68
|
+
desc = token
|
69
|
+
else
|
70
|
+
matcher = "%p.include? token&.symbol" % [token_symbols]
|
71
|
+
desc = token_symbols.join(" or ")
|
72
|
+
end
|
73
|
+
|
74
|
+
value = "(token.value)"
|
75
|
+
value = coerce.to_s + value if coerce
|
76
|
+
value = [value, send].join(".") if send
|
77
|
+
|
78
|
+
raise_parse_error = <<~RUBY
|
79
|
+
parse_error("unexpected %s (expected #{desc})", token&.symbol)
|
80
|
+
RUBY
|
81
|
+
|
82
|
+
class_eval <<~RUBY, __FILE__, __LINE__ + 1
|
83
|
+
# frozen_string_literal: true
|
84
|
+
|
85
|
+
# lookahead version of match, returning the value
|
86
|
+
def lookahead_#{name}!
|
87
|
+
token = #{LOOKAHEAD}
|
88
|
+
if #{matcher}
|
89
|
+
#{value}
|
90
|
+
else
|
91
|
+
#{raise_parse_error}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def #{name}?
|
96
|
+
token = #{LOOKAHEAD}
|
97
|
+
if #{matcher}
|
98
|
+
#{SHIFT_TOKEN}
|
99
|
+
#{value}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def #{match_name}
|
104
|
+
token = #{LOOKAHEAD}
|
105
|
+
if #{matcher}
|
106
|
+
#{SHIFT_TOKEN}
|
107
|
+
#{value}
|
108
|
+
else
|
109
|
+
#{raise_parse_error}
|
110
|
+
end
|
111
|
+
end
|
112
|
+
RUBY
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
# TODO: after checking the lookahead, use a regexp for remaining chars.
|
120
|
+
# That way a loop isn't needed.
|
121
|
+
def combine_adjacent(*tokens)
|
122
|
+
result = "".b
|
123
|
+
while token = accept(*tokens)
|
124
|
+
result << token.value
|
125
|
+
end
|
126
|
+
if result.empty?
|
127
|
+
parse_error('unexpected token %s (expected %s)',
|
128
|
+
lookahead.symbol, tokens.join(" or "))
|
129
|
+
end
|
130
|
+
result
|
131
|
+
end
|
132
|
+
|
133
|
+
def match(*args)
|
134
|
+
token = lookahead
|
135
|
+
unless args.include?(token.symbol)
|
136
|
+
parse_error('unexpected token %s (expected %s)',
|
137
|
+
token.symbol.id2name,
|
138
|
+
args.collect {|i| i.id2name}.join(" or "))
|
139
|
+
end
|
140
|
+
shift_token
|
141
|
+
token
|
142
|
+
end
|
143
|
+
|
144
|
+
# like match, but does not raise error on failure.
|
145
|
+
#
|
146
|
+
# returns and shifts token on successful match
|
147
|
+
# returns nil and leaves @token unshifted on no match
|
148
|
+
def accept(*args)
|
149
|
+
token = lookahead
|
150
|
+
if args.include?(token.symbol)
|
151
|
+
shift_token
|
152
|
+
token
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
# To be used conditionally:
|
157
|
+
# assert_no_lookahead if Net::IMAP.debug
|
158
|
+
def assert_no_lookahead
|
159
|
+
@token.nil? or
|
160
|
+
parse_error("assertion failed: expected @token.nil?, actual %s: %p",
|
161
|
+
@token.symbol, @token.value)
|
162
|
+
end
|
163
|
+
|
164
|
+
# like accept, without consuming the token
|
165
|
+
def lookahead?(*symbols)
|
166
|
+
@token if symbols.include?((@token ||= next_token)&.symbol)
|
167
|
+
end
|
168
|
+
|
169
|
+
def lookahead
|
170
|
+
@token ||= next_token
|
171
|
+
end
|
172
|
+
|
173
|
+
# like match, without consuming the token
|
174
|
+
def lookahead!(*args)
|
175
|
+
if args.include?((@token ||= next_token)&.symbol)
|
176
|
+
@token
|
177
|
+
else
|
178
|
+
parse_error('unexpected token %s (expected %s)',
|
179
|
+
@token&.symbol, args.join(" or "))
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def peek_str?(str)
|
184
|
+
assert_no_lookahead if Net::IMAP.debug
|
185
|
+
@str[@pos, str.length] == str
|
186
|
+
end
|
187
|
+
|
188
|
+
def peek_re(re)
|
189
|
+
assert_no_lookahead if Net::IMAP.debug
|
190
|
+
re.match(@str, @pos)
|
191
|
+
end
|
192
|
+
|
193
|
+
def accept_re(re)
|
194
|
+
assert_no_lookahead if Net::IMAP.debug
|
195
|
+
re.match(@str, @pos) and @pos = $~.end(0)
|
196
|
+
$~
|
197
|
+
end
|
198
|
+
|
199
|
+
def match_re(re, name)
|
200
|
+
assert_no_lookahead if Net::IMAP.debug
|
201
|
+
if re.match(@str, @pos)
|
202
|
+
@pos = $~.end(0)
|
203
|
+
$~
|
204
|
+
else
|
205
|
+
parse_error("invalid #{name}")
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def shift_token
|
210
|
+
@token = nil
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_error(fmt, *args)
|
214
|
+
msg = format(fmt, *args)
|
215
|
+
if IMAP.debug
|
216
|
+
local_path = File.dirname(__dir__)
|
217
|
+
tok = @token ? "%s: %p" % [@token.symbol, @token.value] : "nil"
|
218
|
+
warn "%s %s: %s" % [self.class, __method__, msg]
|
219
|
+
warn " tokenized : %s" % [@str[...@pos].dump]
|
220
|
+
warn " remaining : %s" % [@str[@pos..].dump]
|
221
|
+
warn " @lex_state: %s" % [@lex_state]
|
222
|
+
warn " @pos : %d" % [@pos]
|
223
|
+
warn " @token : %s" % [tok]
|
224
|
+
caller_locations(1..20).each_with_index do |cloc, idx|
|
225
|
+
next unless cloc.path&.start_with?(local_path)
|
226
|
+
warn " caller[%2d]: %-30s (%s:%d)" % [
|
227
|
+
idx,
|
228
|
+
cloc.base_label,
|
229
|
+
File.basename(cloc.path, ".rb"),
|
230
|
+
cloc.lineno
|
231
|
+
]
|
232
|
+
end
|
233
|
+
end
|
234
|
+
raise ResponseParseError, msg
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|