webget-string_extensions 1.0.0 → 1.0.2

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.
@@ -7,6 +7,7 @@
7
7
  #
8
8
  # Ruby String base class extensions
9
9
  #
10
+ # For #prev and #pred see http://snippets.dzone.com/posts/show/2474
10
11
  ##
11
12
 
12
13
  class String
@@ -23,5 +24,156 @@ class String
23
24
  split(/\b/).map{|x| x.capitalize }.join
24
25
  end
25
26
 
27
+
28
+ # The opposite of String::next / String::succ. It is impossible to be a
29
+ # *complete* opposite because both "9".next = "10" and "09".next = "10";
30
+ # if going backwards from "10" there's no way to know whether the result
31
+ # should be "09" or "9". Where the first ranged character is about to
32
+ # underflow and the next character is within the same range the result
33
+ # is shrunk down - that is, "10" goes to "9", "aa" goes to "z"; any non-
34
+ # range prefix or suffix is OK, e.g. "+!$%10-=+" goes to "+!$%9-=+".
35
+ # Items in the middle of a string don't do this - e.g. "12.10" goes to
36
+ # "12.09", to match how "next" would work as best as possible.
37
+ #
38
+ # The standard "next" function works on strings that contain *no*
39
+ # alphanumeric characters, using character codes. This implementation
40
+ # of "prev" does *not* work on such strings - while strings may contain
41
+ # any characters you like, only the alphanumeric components are operated
42
+ # upon.
43
+ #
44
+ # Should total underflow result, "nil" will be returned - e.g. "00".prev
45
+ # returns 'nil', as does "a".prev. This is done even if there are other
46
+ # characters in the string that were not touched - e.g. "+0.0".prev
47
+ # also returns "nil". Broadly speaking, a "nil" return value is used for
48
+ # any attempt to find the previous value of a string that could not have
49
+ # been generated using "next" in the first place.
50
+ #
51
+ # As with "next" sometimes the result of "prev" can be a little obscure
52
+ # so it is often best to try out things using "irb" if unsure. Note in
53
+ # particular that software revision numbers do not necessarily behave
54
+ # predictably, because they don't with "next"! E.g. "12.4.9" might go to
55
+ # "12.4.10" for a revision number, but "12.4.9".next = "12.5.0". Thus
56
+ # "12.5.0".prev = "12.4.9" and "12.4.10".prev = "12.4.09" (because the
57
+ # only way to make "12.4.10" using "next" is to start at "12.4.09").
58
+ #
59
+ # Since 'succ' (successor) is an alias for 'next', so 'pred'
60
+ # (predecessor) is an alias for 'prev'.
61
+ #
62
+ def prev(collapse = false)
63
+ str = self.dup
64
+ early_exit = false
65
+ any_done = false
66
+ ranges = [
67
+ ('0'[0]..'9'[0]),
68
+ ('a'[0]..'z'[0]),
69
+ ('A'[0]..'Z'[0]),
70
+ nil
71
+ ]
72
+
73
+ # Search forward for the first in-range character. If found check
74
+ # to see if that character is "1", "a" or "A". If it is, record
75
+ # its index (from 0 to string length - 1). We'll need this if
76
+ # underflows wrap as far as the found byte because in that case
77
+ # this first found byte should be deleted ("aa..." -> "z...",
78
+ # "10..." -> "9...").
79
+
80
+ first_ranged = nil
81
+
82
+ for index in (1..str.length)
83
+ byte = str[index - 1]
84
+
85
+ # Determine whether or not the current byte is a number, lower case
86
+ # or upper case letter. We expect 'select' to only find one matching
87
+ # array entry in 'ranges', thus we dereference index 0 after the
88
+ # 'end' to put a matching range from within 'ranges' into 'within',
89
+ # or 'nil' for any unmatched byte.
90
+
91
+ within = ranges.select do |range|
92
+ range.nil? or range.include?(byte)
93
+ end [0]
94
+
95
+ unless within.nil?
96
+ case within.first
97
+ when '0'[0]
98
+ match_byte = '1'[0]
99
+ else
100
+ match_byte = within.first
101
+ end
102
+
103
+ first_ranged = index - 1 if (byte == match_byte)
104
+ first_within = within
105
+ break
106
+ end
107
+ end
108
+
109
+ for index in (1..str.length)
110
+
111
+ # Process the input string in reverse character order - fetch the
112
+ # bytes via negative index.
113
+
114
+ byte = str[-index]
115
+
116
+ within = ranges.select do |range|
117
+ range.nil? or range.include?(byte)
118
+ end [0]
119
+
120
+ # Skip this letter unless within a known range. Otherwise note that
121
+ # at least one byte was able to be processed.
122
+
123
+ next if within.nil?
124
+ any_done = true
125
+
126
+ # Decrement the current byte. If it is still within its range, set
127
+ # the byte and bail out - we're finished. Flag the early exit. If
128
+ # the byte is no longer within range, wrap ththe character around
129
+ # and continue the loop to carry the decrement to an earlier byte.
130
+
131
+ byte = byte - 1
132
+
133
+ if (within.include? byte)
134
+ str[-index] = byte
135
+ early_exit = true
136
+ break
137
+ else
138
+ str[-index] = within.last
139
+
140
+ # If we've just wrapped around a character immediately after the
141
+ # one found right at the start ('0', 'a' or 'A') then this first
142
+ # ranged character should be deleted (so "10" -> "09"
143
+
144
+ if (first_ranged != nil and first_within.include?(byte + 1) and (first_ranged - str.length) == -(index + 1))
145
+ str.slice!(-(index + 1))
146
+ early_exit = true
147
+ break
148
+ end
149
+ end
150
+
151
+ end # From outer 'for' loop
152
+
153
+ # If we did process at least one byte but we did not exit early, then
154
+ # the loop completed due to carrying a decrement to other bytes. This
155
+ # means an underflow condition - return 'nil'.
156
+
157
+ if (any_done == true and early_exit == false)
158
+ return nil
159
+ else
160
+ return str
161
+ end
162
+ end
163
+
164
+ # As (extended) String::pred / String::prev, but modifies the string in
165
+ # place rather than returning a copy. If underflow occurs, the string
166
+ # will be unchanged. Returns 'self'.
167
+ #
168
+ def prev!
169
+ new_str = prev
170
+ self.replace(new_str) unless new_str.nil?
171
+ return self
172
+ end
173
+
174
+ alias pred prev
175
+ alias pred! prev!
176
+
177
+
26
178
  end
27
179
 
@@ -15,5 +15,7 @@ class StringExtensionsTest < Test::Unit::TestCase
15
15
  assert_equal("Foo Goo Hoo","foo goo hoo".capitalize_words)
16
16
  end
17
17
 
18
+
19
+
18
20
  end
19
21
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webget-string_extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - WebGet