motion-strscan 0.5.0

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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +63 -0
  3. data/lib/motion-strscan/string.rb +20 -0
  4. data/lib/motion-strscan/strscan.rb +676 -0
  5. data/lib/motion-strscan/version.rb +3 -0
  6. data/lib/motion-strscan.rb +8 -0
  7. data/spec/helpers/it_behaves_like.rb +27 -0
  8. data/spec/string_spec/_shared/slice.rb +244 -0
  9. data/spec/string_spec/byteslice_spec.rb +21 -0
  10. data/spec/strscan_spec/_shared/bol.rb +25 -0
  11. data/spec/strscan_spec/_shared/concat.rb +31 -0
  12. data/spec/strscan_spec/_shared/eos.rb +17 -0
  13. data/spec/strscan_spec/_shared/extract_range.rb +22 -0
  14. data/spec/strscan_spec/_shared/extract_range_matched.rb +22 -0
  15. data/spec/strscan_spec/_shared/get_byte.rb +28 -0
  16. data/spec/strscan_spec/_shared/matched_size.rb +21 -0
  17. data/spec/strscan_spec/_shared/peek.rb +44 -0
  18. data/spec/strscan_spec/_shared/pos.rb +83 -0
  19. data/spec/strscan_spec/_shared/rest_size.rb +18 -0
  20. data/spec/strscan_spec/_shared/terminate.rb +17 -0
  21. data/spec/strscan_spec/append_spec.rb +7 -0
  22. data/spec/strscan_spec/beginning_of_line_spec.rb +3 -0
  23. data/spec/strscan_spec/bol_spec.rb +3 -0
  24. data/spec/strscan_spec/check_spec.rb +13 -0
  25. data/spec/strscan_spec/check_until_spec.rb +14 -0
  26. data/spec/strscan_spec/clear_spec.rb +21 -0
  27. data/spec/strscan_spec/concat_spec.rb +7 -0
  28. data/spec/strscan_spec/dup_spec.rb +36 -0
  29. data/spec/strscan_spec/element_reference_spec.rb +46 -0
  30. data/spec/strscan_spec/empty_spec.rb +21 -0
  31. data/spec/strscan_spec/eos_spec.rb +3 -0
  32. data/spec/strscan_spec/exist_spec.rb +21 -0
  33. data/spec/strscan_spec/get_byte_spec.rb +3 -0
  34. data/spec/strscan_spec/getbyte_spec.rb +23 -0
  35. data/spec/strscan_spec/getch_spec.rb +41 -0
  36. data/spec/strscan_spec/initialize_spec.rb +25 -0
  37. data/spec/strscan_spec/inspect_spec.rb +17 -0
  38. data/spec/strscan_spec/match_spec.rb +25 -0
  39. data/spec/strscan_spec/matched_size_spec.rb +3 -0
  40. data/spec/strscan_spec/matched_spec.rb +37 -0
  41. data/spec/strscan_spec/matchedsize_spec.rb +23 -0
  42. data/spec/strscan_spec/must_C_version_spec.rb +5 -0
  43. data/spec/strscan_spec/peek_spec.rb +4 -0
  44. data/spec/strscan_spec/peep_spec.rb +21 -0
  45. data/spec/strscan_spec/pointer_spec.rb +7 -0
  46. data/spec/strscan_spec/pos_spec.rb +7 -0
  47. data/spec/strscan_spec/post_match_spec.rb +24 -0
  48. data/spec/strscan_spec/pre_match_spec.rb +37 -0
  49. data/spec/strscan_spec/reset_spec.rb +12 -0
  50. data/spec/strscan_spec/rest_size_spec.rb +3 -0
  51. data/spec/strscan_spec/rest_spec.rb +44 -0
  52. data/spec/strscan_spec/restsize_spec.rb +21 -0
  53. data/spec/strscan_spec/scan_full_spec.rb +27 -0
  54. data/spec/strscan_spec/scan_spec.rb +50 -0
  55. data/spec/strscan_spec/scan_until_spec.rb +20 -0
  56. data/spec/strscan_spec/search_full_spec.rb +27 -0
  57. data/spec/strscan_spec/skip_spec.rb +15 -0
  58. data/spec/strscan_spec/skip_until_spec.rb +15 -0
  59. data/spec/strscan_spec/string_spec.rb +37 -0
  60. data/spec/strscan_spec/terminate_spec.rb +3 -0
  61. data/spec/strscan_spec/unscan_spec.rb +26 -0
  62. metadata +172 -0
@@ -0,0 +1,676 @@
1
+ # RubyMotion (at least as of 3.6) does not include an implementation of
2
+ # StringScanner (strscan.rb). So an implementation was borrowed from MacRuby:
3
+ # https://github.com/MacRuby/MacRuby/blob/d1e47668d4345c78871f49b06849f3c7e4355943/lib/strscan.rb
4
+ #
5
+ # However, that implementation treats the pointer as a character pointer, instead
6
+ # of a byte pointer, as specified in the MRI. It was therefore having difficulties
7
+ # with multibyte strings, and with libraries that expect the pointer to be byte-based.
8
+ #
9
+ # The code has been modifed to use byte-based positioning and sizing, and will now
10
+ # return the same sizes and values as Ruby 1.9 and 2.0
11
+ #------------------------------------------------------------------------------
12
+
13
+
14
+ # (modified) MacRuby implementation of strscan.
15
+ #
16
+ # This file is covered by the Ruby license. See COPYING for more details.
17
+ #
18
+ # Copyright (C) 2012, The MacRuby Team. All rights reserved.
19
+ # Copyright (C) 2009-2011, Apple Inc. All rights reserved.
20
+
21
+ class ScanError < StandardError; end
22
+
23
+ # StringScanner provides for lexical scanning operations on a String. Here is
24
+ # an example of its usage:
25
+ #
26
+ # s = StringScanner.new('This is an example string')
27
+ # s.eos? # -> false
28
+ #
29
+ # p s.scan(/\w+/) # -> "This"
30
+ # p s.scan(/\w+/) # -> nil
31
+ # p s.scan(/\s+/) # -> " "
32
+ # p s.scan(/\s+/) # -> nil
33
+ # p s.scan(/\w+/) # -> "is"
34
+ # s.eos? # -> false
35
+ #
36
+ # p s.scan(/\s+/) # -> " "
37
+ # p s.scan(/\w+/) # -> "an"
38
+ # p s.scan(/\s+/) # -> " "
39
+ # p s.scan(/\w+/) # -> "example"
40
+ # p s.scan(/\s+/) # -> " "
41
+ # p s.scan(/\w+/) # -> "string"
42
+ # s.eos? # -> true
43
+ #
44
+ # p s.scan(/\s+/) # -> nil
45
+ # p s.scan(/\w+/) # -> nil
46
+ #
47
+ # Scanning a string means remembering the position of a <i>scan pointer</i>,
48
+ # which is just an index. The point of scanning is to move forward a bit at
49
+ # a time, so matches are sought after the scan pointer; usually immediately
50
+ # after it.
51
+ #
52
+ # Given the string "test string", here are the pertinent scan pointer
53
+ # positions:
54
+ #
55
+ # t e s t s t r i n g
56
+ # 0 1 2 ... 1
57
+ # 0
58
+ #
59
+ # When you #scan for a pattern (a regular expression), the match must occur
60
+ # at the character after the scan pointer. If you use #scan_until, then the
61
+ # match can occur anywhere after the scan pointer. In both cases, the scan
62
+ # pointer moves <i>just beyond</i> the last character of the match, ready to
63
+ # scan again from the next character onwards. This is demonstrated by the
64
+ # example above.
65
+ #
66
+ # == Method Categories
67
+ #
68
+ # There are other methods besides the plain scanners. You can look ahead in
69
+ # the string without actually scanning. You can access the most recent match.
70
+ # You can modify the string being scanned, reset or terminate the scanner,
71
+ # find out or change the position of the scan pointer, skip ahead, and so on.
72
+ #
73
+ # === Advancing the Scan Pointer
74
+ #
75
+ # - #getch
76
+ # - #get_byte
77
+ # - #scan
78
+ # - #scan_until
79
+ # - #skip
80
+ # - #skip_until
81
+ #
82
+ # === Looking Ahead
83
+ #
84
+ # - #check
85
+ # - #check_until
86
+ # - #exist?
87
+ # - #match?
88
+ # - #peek
89
+ #
90
+ # === Finding Where we Are
91
+ #
92
+ # - #beginning_of_line? (#bol?)
93
+ # - #eos?
94
+ # - #rest?
95
+ # - #rest_size
96
+ # - #pos
97
+ #
98
+ # === Setting Where we Are
99
+ #
100
+ # - #reset
101
+ # - #terminate
102
+ # - #pos=
103
+ #
104
+ # === Match Data
105
+ #
106
+ # - #matched
107
+ # - #matched?
108
+ # - #matched_size
109
+ # - []
110
+ # - #pre_match
111
+ # - #post_match
112
+ #
113
+ # === Miscellaneous
114
+ #
115
+ # - <<
116
+ # - #concat
117
+ # - #string
118
+ # - #string=
119
+ # - #unscan
120
+ #
121
+ # There are aliases to several of the methods.
122
+ #
123
+ class StringScanner
124
+
125
+ # string <String>:: The string to scan
126
+ # pos <Integer>:: The position of the scan pointer. In the 'reset' position, this
127
+ # value is zero. In the 'terminated' position (i.e. the string is exhausted),
128
+ # this value is the length of the string.
129
+ #
130
+ # In short, it's a 0-based index into the string.
131
+ #
132
+ # s = StringScanner.new('test string')
133
+ # s.pos # -> 0
134
+ # s.scan_until /str/ # -> "test str"
135
+ # s.pos # -> 8
136
+ # s.terminate # -> #<StringScanner fin>
137
+ # s.pos # -> 11
138
+ #
139
+ # match <String>:: Matched string
140
+ #
141
+ attr_reader :string, :char_pos, :byte_pos
142
+
143
+ # This method is defined for backward compatibility.
144
+ #
145
+ def self.must_C_version
146
+ self
147
+ end
148
+
149
+ # StringScanner.new(string, dup = false)
150
+ #
151
+ # Creates a new StringScanner object to scan over the given +string+.
152
+ # +dup+ argument is obsolete and not used now.
153
+ #
154
+ def initialize(string, dup = false)
155
+ begin
156
+ @string = string.to_str
157
+ rescue
158
+ raise TypeError, "can't convert #{string.class.name} into String"
159
+ end
160
+ @byte_pos = 0
161
+ end
162
+
163
+ # Duplicates a StringScanner object when dup or clone are called on the
164
+ # object.
165
+ #
166
+ def initialize_copy(orig)
167
+ @string = orig.string
168
+ @byte_pos = orig.byte_pos
169
+ @match = orig.instance_variable_get("@match")
170
+ end
171
+
172
+ # Reset the scan pointer (index 0) and clear matching data.
173
+ #
174
+ def reset
175
+ @byte_pos = 0
176
+ @match = nil
177
+ self
178
+ end
179
+
180
+ # Set the scan pointer to the end of the string and clear matching data.
181
+ #
182
+ def terminate
183
+ @match = nil
184
+ @byte_pos = @string.bytesize
185
+ self
186
+ end
187
+
188
+ # Equivalent to #terminate.
189
+ # This method is obsolete; use #terminate instead.
190
+ #
191
+ def clear
192
+ warn "StringScanner#clear is obsolete; use #terminate instead" if $VERBOSE
193
+ terminate
194
+ end
195
+
196
+ # Changes the string being scanned to +str+ and resets the scanner.
197
+ # Returns +str+.
198
+ #
199
+ def string=(str)
200
+ reset
201
+ begin
202
+ @string = str.to_str
203
+ rescue
204
+ raise TypeError, "can't convert #{str.class.name} into String"
205
+ end
206
+ end
207
+
208
+ # Appends +str+ to the string being scanned.
209
+ # This method does not affect scan pointer.
210
+ #
211
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
212
+ # s.scan(/Fri /)
213
+ # s << " +1000 GMT"
214
+ # s.string # -> "Fri Dec 12 1975 14:39 +1000 GMT"
215
+ # s.scan(/Dec/) # -> "Dec"
216
+ #
217
+ def concat(str)
218
+ begin
219
+ @string << str.to_str
220
+ rescue
221
+ raise TypeError, "can't convert #{str.class.name} into String"
222
+ end
223
+ self
224
+ end
225
+ alias :<< :concat
226
+
227
+ # Returns the byte postition of the scan pointer (not the character position)
228
+ #------------------------------------------------------------------------------
229
+ def pos
230
+ @byte_pos
231
+ end
232
+
233
+ # Modify the scan pointer.
234
+ #
235
+ # s = StringScanner.new('test string')
236
+ # s.pos = 7 # -> 7
237
+ # s.rest # -> "ring"
238
+ #
239
+ def pos=(n)
240
+ n = (n + @string.bytesize) if (n < 0)
241
+ raise RangeError, "index out of range" if (n < 0 || (@string && n > @string.bytesize))
242
+ @byte_pos = n
243
+ end
244
+
245
+ alias :pointer :pos
246
+ alias :pointer= :pos=
247
+
248
+ # Scans one byte and returns it.
249
+ # This method is not multibyte sensitive.
250
+ # See also: #getch.
251
+ #
252
+ # s = StringScanner.new('ab')
253
+ # s.get_byte # => "a"
254
+ # s.get_byte # => "b"
255
+ # s.get_byte # => nil
256
+ #
257
+ # # encoding: EUC-JP
258
+ # s = StringScanner.new("\244\242")
259
+ # s.get_byte # => "244"
260
+ # s.get_byte # => "242"
261
+ # s.get_byte # => nil
262
+ #
263
+ def get_byte
264
+ # temp hack
265
+ # TODO replace by a solution that will work with UTF-8
266
+ scan(/./mn)
267
+ end
268
+
269
+ # Equivalent to #get_byte.
270
+ # This method is obsolete; use #get_byte instead.
271
+ #
272
+ def getbyte
273
+ warn "StringScanner#getbyte is obsolete; use #get_byte instead" if $VERBOSE
274
+ get_byte
275
+ end
276
+
277
+ # Tries to match with +pattern+ at the current position. If there's a match,
278
+ # the scanner advances the "scan pointer" and returns the matched string.
279
+ # Otherwise, the scanner returns +nil+.
280
+ #
281
+ # s = StringScanner.new('test string')
282
+ # p s.scan(/\w+/) # -> "test"
283
+ # p s.scan(/\w+/) # -> nil
284
+ # p s.scan(/\s+/) # -> " "
285
+ # p s.scan(/\w+/) # -> "string"
286
+ # p s.scan(/./) # -> nil
287
+ #
288
+ def scan(pattern)
289
+ _scan(pattern, true, true, true)
290
+ end
291
+
292
+ # Scans the string _until_ the +pattern+ is matched. Returns the substring up
293
+ # to and including the end of the match, advancing the scan pointer to that
294
+ # location. If there is no match, +nil+ is returned.
295
+ #
296
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
297
+ # s.scan_until(/1/) # -> "Fri Dec 1"
298
+ # s.pre_match # -> "Fri Dec "
299
+ # s.scan_until(/XYZ/) # -> nil
300
+ #
301
+ def scan_until(pattern)
302
+ _scan(pattern, true, true, false)
303
+ end
304
+
305
+ # Tests whether the given +pattern+ is matched from the current scan pointer.
306
+ # Returns the matched string if +return_string_p+ is true.
307
+ # Advances the scan pointer if +advance_pointer_p+ is true.
308
+ # The match register is affected.
309
+ #
310
+ # "full" means "#scan with full parameters".
311
+ #
312
+ def scan_full(pattern, succptr, getstr)
313
+ _scan(pattern, succptr, getstr, true)
314
+ end
315
+
316
+ # Scans the string _until_ the +pattern+ is matched.
317
+ # Returns the matched string if +return_string_p+ is true, otherwise
318
+ # returns the number of bytes advanced.
319
+ # Advances the scan pointer if +advance_pointer_p+, otherwise not.
320
+ # This method does affect the match register.
321
+ #
322
+ def search_full(pattern, succptr, getstr)
323
+ _scan(pattern, succptr, getstr, false)
324
+ end
325
+
326
+ # Scans one character and returns it.
327
+ # This method is multibyte character sensitive.
328
+ #
329
+ # s = StringScanner.new("ab")
330
+ # s.getch # => "a"
331
+ # s.getch # => "b"
332
+ # s.getch # => nil
333
+ #
334
+ def getch
335
+ scan(/./m)
336
+ end
337
+
338
+ # Returns +true+ if the scan pointer is at the end of the string.
339
+ #
340
+ # s = StringScanner.new('test string')
341
+ # p s.eos? # => false
342
+ # s.scan(/test/)
343
+ # p s.eos? # => false
344
+ # s.terminate
345
+ # p s.eos? # => true
346
+ #
347
+ def eos?
348
+ @byte_pos >= @string.bytesize
349
+ end
350
+
351
+ # Equivalent to #eos?.
352
+ # This method is obsolete, use #eos? instead.
353
+ #
354
+ def empty?
355
+ warn "StringScanner#empty? is obsolete; use #eos? instead" if $VERBOSE
356
+ eos?
357
+ end
358
+
359
+ # Returns true iff there is more data in the string. See #eos?.
360
+ # This method is obsolete; use #eos? instead.
361
+ #
362
+ # s = StringScanner.new('test string')
363
+ # s.eos? # These two
364
+ # s.rest? # are opposites.
365
+ #
366
+ def rest?
367
+ !eos?
368
+ end
369
+
370
+ # Returns the "rest" of the string (i.e. everything after the scan pointer).
371
+ # If there is no more data (eos? = true), it returns <tt>""</tt>.
372
+ #
373
+ def rest
374
+ @string.byteslice(@byte_pos..-1) || ""
375
+ end
376
+
377
+ # <tt>s.rest_size</tt> is equivalent to <tt>s.rest.size</tt>.
378
+ #
379
+ def rest_size
380
+ @string.bytesize - @byte_pos
381
+ end
382
+
383
+ # <tt>s.restsize</tt> is equivalent to <tt>s.rest_size</tt>.
384
+ # This method is obsolete; use #rest_size instead.
385
+ #
386
+ def restsize
387
+ warn "StringScanner#restsize is obsolete; use #rest_size instead" if $VERBOSE
388
+ rest_size
389
+ end
390
+
391
+ # Returns a string that represents the StringScanner object, showing:
392
+ # - the current position
393
+ # - the size of the string
394
+ # - the characters surrounding the scan pointer
395
+ #
396
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
397
+ # s.inspect # -> '#<StringScanner 0/21 @ "Fri D...">'
398
+ # s.scan_until /12/ # -> "Fri Dec 12"
399
+ # s.inspect # -> '#<StringScanner 10/21 "...ec 12" @ " 1975...">'
400
+ #
401
+ def inspect
402
+ if defined?(@string)
403
+ rest = @string.size > 5 ? @string[@byte_pos..@byte_pos+4] + "..." : @string
404
+ to_return = if eos? then
405
+ "#<StringScanner fin>"
406
+ elsif @byte_pos > 0 then
407
+ prev = @string[0...@byte_pos].inspect
408
+ "#<StringScanner #{@byte_pos}/#{@string.bytesize} #{prev} @ #{rest.inspect}>"
409
+ else
410
+ "#<StringScanner #{@byte_pos}/#{@string.bytesize} @ #{rest.inspect}>"
411
+ end
412
+ to_return.taint if @string.tainted?
413
+ to_return
414
+ else
415
+ "#<StringScanner (uninitialized)>"
416
+ end
417
+ end
418
+
419
+ # Tests whether the given +pattern+ is matched from the current scan pointer.
420
+ # Returns the length of the match, or +nil+. The scan pointer is not advanced.
421
+ #
422
+ # s = StringScanner.new('test string')
423
+ # p s.match?(/\w+/) # -> 4
424
+ # p s.match?(/\w+/) # -> 4
425
+ # p s.match?(/\s+/) # -> nil
426
+ #
427
+ def match?(pattern)
428
+ _scan(pattern, false, false, true)
429
+ end
430
+
431
+ # Returns the last matched string.
432
+ #
433
+ # s = StringScanner.new('test string')
434
+ # s.match?(/\w+/) # -> 4
435
+ # s.matched # -> "test"
436
+ #
437
+ def matched
438
+ @match.to_s if matched?
439
+ end
440
+
441
+ # Returns +true+ iff the last match was successful.
442
+ #
443
+ # s = StringScanner.new('test string')
444
+ # s.match?(/\w+/) # => 4
445
+ # s.matched? # => true
446
+ # s.match?(/\d+/) # => nil
447
+ # s.matched? # => false
448
+ #
449
+ def matched?
450
+ !@match.nil?
451
+ end
452
+
453
+ # Returns the last matched string.
454
+ #
455
+ # s = StringScanner.new('test string')
456
+ # s.match?(/\w+/) # -> 4
457
+ # s.matched # -> "test"
458
+ #
459
+ def matched_size
460
+ # @match.to_s.size if matched?
461
+ @match.to_s.bytesize if matched?
462
+ end
463
+
464
+ # Equivalent to #matched_size.
465
+ # This method is obsolete; use #matched_size instead.
466
+ #
467
+ def matchedsize
468
+ warn "StringScanner#matchedsize is obsolete; use #matched_size instead" if $VERBOSE
469
+ matched_size
470
+ end
471
+
472
+ # Attempts to skip over the given +pattern+ beginning with the scan pointer.
473
+ # If it matches, the scan pointer is advanced to the end of the match, and the
474
+ # length of the match is returned. Otherwise, +nil+ is returned.
475
+ #
476
+ # It's similar to #scan, but without returning the matched string.
477
+ #
478
+ # s = StringScanner.new('test string')
479
+ # p s.skip(/\w+/) # -> 4
480
+ # p s.skip(/\w+/) # -> nil
481
+ # p s.skip(/\s+/) # -> 1
482
+ # p s.skip(/\w+/) # -> 6
483
+ # p s.skip(/./) # -> nil
484
+ #
485
+ def skip(pattern)
486
+ _scan(pattern, true, false, true)
487
+ end
488
+
489
+ # Advances the scan pointer until +pattern+ is matched and consumed. Returns
490
+ # the number of bytes advanced, or +nil+ if no match was found.
491
+ #
492
+ # Look ahead to match +pattern+, and advance the scan pointer to the _end_
493
+ # of the match. Return the number of characters advanced, or +nil+ if the
494
+ # match was unsuccessful.
495
+ #
496
+ # It's similar to #scan_until, but without returning the intervening string.
497
+ #
498
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
499
+ # s.skip_until /12/ # -> 10
500
+ # s
501
+ #
502
+ def skip_until(pattern)
503
+ _scan(pattern, true, false, false)
504
+ end
505
+
506
+ # This returns the value that #scan would return, without advancing the scan
507
+ # pointer. The match register is affected, though.
508
+ #
509
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
510
+ # s.check /Fri/ # -> "Fri"
511
+ # s.pos # -> 0
512
+ # s.matched # -> "Fri"
513
+ # s.check /12/ # -> nil
514
+ # s.matched # -> nil
515
+ #
516
+ # Mnemonic: it "checks" to see whether a #scan will return a value.
517
+ #
518
+ def check(pattern)
519
+ _scan(pattern, false, true, true)
520
+ end
521
+
522
+
523
+ # This returns the value that #scan_until would return, without advancing the
524
+ # scan pointer. The match register is affected, though.
525
+ #
526
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
527
+ # s.check_until /12/ # -> "Fri Dec 12"
528
+ # s.pos # -> 0
529
+ # s.matched # -> 12
530
+ #
531
+ # Mnemonic: it "checks" to see whether a #scan_until will return a value.
532
+ #
533
+ def check_until(pattern)
534
+ _scan(pattern, false, true, false)
535
+ end
536
+
537
+ # Looks _ahead_ to see if the +pattern+ exists _anywhere_ in the string,
538
+ # without advancing the scan pointer. This predicates whether a #scan_until
539
+ # will return a value.
540
+ #
541
+ # s = StringScanner.new('test string')
542
+ # s.exist? /s/ # -> 3
543
+ # s.scan /test/ # -> "test"
544
+ # s.exist? /s/ # -> 6
545
+ # s.exist? /e/ # -> nil
546
+ #
547
+ def exist?(pattern)
548
+ _scan(pattern, false, false, false)
549
+ end
550
+
551
+ # Extracts a string corresponding to <tt>string[pos,len]</tt>, without
552
+ # advancing the scan pointer.
553
+ #
554
+ # s = StringScanner.new('test string')
555
+ # s.peek(7) # => "test st"
556
+ # s.peek(7) # => "test st"
557
+ #
558
+ def peek(length)
559
+ raise TypeError, "can't convert #{length.class.name} into Integer" unless length.respond_to?(:to_int)
560
+ raise ArgumentError if length < 0
561
+ length.zero? ? "" : @string.byteslice(@byte_pos, length)
562
+ end
563
+
564
+ # Equivalent to #peek.
565
+ # This method is obsolete; use #peek instead.
566
+ #
567
+ def peep(length)
568
+ warn "StringScanner#peep is obsolete; use #peek instead" if $VERBOSE
569
+ peek(length)
570
+ end
571
+
572
+ # Set the scan pointer to the previous position. Only one previous position is
573
+ # remembered, and it changes with each scanning operation.
574
+ #
575
+ # s = StringScanner.new('test string')
576
+ # s.scan(/\w+/) # => "test"
577
+ # s.unscan
578
+ # s.scan(/../) # => "te"
579
+ # s.scan(/\d/) # => nil
580
+ # s.unscan # ScanError: unscan failed: previous match record not exist
581
+ #
582
+ def unscan
583
+ raise(ScanError, "unscan failed: previous match record not exist") if @match.nil?
584
+ @byte_pos = @prev_byte_pos
585
+ @prev_byte_pos = nil
586
+ @match = nil
587
+ self
588
+ end
589
+
590
+ # Returns +true+ iff the scan pointer is at the beginning of the line.
591
+ #
592
+ # s = StringScanner.new("test\ntest\n")
593
+ # s.bol? # => true
594
+ # s.scan(/te/)
595
+ # s.bol? # => false
596
+ # s.scan(/st\n/)
597
+ # s.bol? # => true
598
+ # s.terminate
599
+ # s.bol? # => true
600
+ #
601
+ def bol?
602
+ (@byte_pos == 0) || (@string.getbyte(@byte_pos - 1) == 10) # 10 == '\n'
603
+ end
604
+ alias :beginning_of_line? :bol?
605
+
606
+ # Return the n-th subgroup in the most recent match.
607
+ #
608
+ # s = StringScanner.new("Fri Dec 12 1975 14:39")
609
+ # s.scan(/(\w+) (\w+) (\d+) /) # -> "Fri Dec 12 "
610
+ # s[0] # -> "Fri Dec 12 "
611
+ # s[1] # -> "Fri"
612
+ # s[2] # -> "Dec"
613
+ # s[3] # -> "12"
614
+ # s.post_match # -> "1975 14:39"
615
+ # s.pre_match # -> ""
616
+ #
617
+ def [](n)
618
+ raise TypeError, "Bad argument #{n.inspect}" unless n.respond_to? :to_int
619
+ @match.nil? ? nil : @match[n]
620
+ end
621
+
622
+ # Return the <i><b>pre</b>-match</i> (in the regular expression sense) of the last scan.
623
+ #
624
+ # s = StringScanner.new('test string')
625
+ # s.scan(/\w+/) # -> "test"
626
+ # s.scan(/\s+/) # -> " "
627
+ # s.pre_match # -> "test"
628
+ # s.post_match # -> "string"
629
+ #
630
+ def pre_match
631
+ if matched?
632
+ # match.begin is in characters (not bytes)
633
+ p = @string.size - @match.string.size + @match.begin(0)
634
+ @string[0...p]
635
+ end
636
+ end
637
+
638
+ # Return the <i><b>post</b>-match</i> (in the regular expression sense) of the last scan.
639
+ #
640
+ # s = StringScanner.new('test string')
641
+ # s.scan(/\w+/) # -> "test"
642
+ # s.scan(/\s+/) # -> " "
643
+ # s.pre_match # -> "test"
644
+ # s.post_match # -> "string"
645
+ #
646
+ def post_match
647
+ @match.post_match if matched?
648
+ end
649
+
650
+ private
651
+
652
+ def _scan(pattern, succptr, getstr, headonly)
653
+ raise TypeError, "wrong argument type #{pattern.class.name} (expected Regexp)" unless
654
+ Regexp === pattern
655
+
656
+ @match = nil
657
+ rest = @byte_pos == 0 ? @string : self.rest
658
+
659
+ if headonly
660
+ headonly_pattern = Regexp.new("\\A(?:#{pattern.source})", pattern.options)
661
+ @match = headonly_pattern.match rest
662
+ else
663
+ @match = pattern.match rest
664
+ end
665
+ return nil unless @match
666
+
667
+ m = rest[0, @match.end(0)]
668
+ if succptr
669
+ @prev_byte_pos = @byte_pos
670
+ @byte_pos += m.bytesize
671
+ end
672
+
673
+ getstr ? m : m.bytesize
674
+ end
675
+
676
+ end
@@ -0,0 +1,3 @@
1
+ module MotionStrscan
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,8 @@
1
+ unless defined?(Motion::Project::Config)
2
+ raise 'The motion-strscan gem must be required within a RubyMotion project Rakefile.'
3
+ end
4
+
5
+ lib_dir_path = File.dirname(File.expand_path(__FILE__))
6
+ Motion::Project::App.setup do |app|
7
+ app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion-strscan/**/*.rb")))
8
+ end
@@ -0,0 +1,27 @@
1
+ #------------------------------------------------------------------------------
2
+ def it_behaves_like(name, method = nil)
3
+ @method = method
4
+ behaves_like(name)
5
+ end
6
+
7
+ class Should
8
+
9
+ # usage: .should.be_true and .should.be_false
10
+ #------------------------------------------------------------------------------
11
+ def be_true
12
+ self == true
13
+ end
14
+
15
+ def be_false
16
+ self == false
17
+ end
18
+ end
19
+
20
+ # usage: .should.be truthy and .should.be falsey
21
+ #------------------------------------------------------------------------------
22
+ def truthy
23
+ lambda { |obj| obj == true }
24
+ end
25
+ def falsey
26
+ lambda { |obj| obj == false }
27
+ end