motion-strscan 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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