gstring 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +26 -0
- data/Rakefile +1 -0
- data/gstring.gemspec +32 -0
- data/lib/gstring.rb +1403 -0
- data/lib/gstring/version.rb +3 -0
- metadata +126 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0accef1dd11956fb3f9d63591b265b3a180aeb87
|
4
|
+
data.tar.gz: 72240f555419b95a7eb134aaf945a46350a6e045
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: df82a5b5dd451ecbeef30eeee3756ea52723252cdad4aa13373ceb47568c37b3793fc8624ddf584910cea06a83c1b5af893a10c433ca8c27e9a0d890b7f997f3
|
7
|
+
data.tar.gz: 2b3af8e31c06de166105fd82466b52915542ca53cfaa2e01c4d7f6fdf17163006efefc7d12888784edec66a6edd5e2704c4e18f4408b3984a43eee9ceb3b39bd
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Bryan Colvin
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# gstring
|
2
|
+
|
3
|
+
##Purpose:
|
4
|
+
The gstring gem adds new functionality to the base `String` class.
|
5
|
+
Some existing methods are extended to utilize Set instances (gem: setfu)
|
6
|
+
Other methods solve some nasty string manipulation problems with ease.
|
7
|
+
Future releases will attempt to add more functionality with the goal
|
8
|
+
of making gstring the standard String extension gem.
|
9
|
+
|
10
|
+
##Documentation:
|
11
|
+
See: http://bdlsys.com/gems/gstring.html
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add these lines to your application's Gemfile:
|
16
|
+
gem 'setfu'
|
17
|
+
gem 'gstring'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
$ bundle
|
21
|
+
|
22
|
+
Or install it yourself as:
|
23
|
+
$ gem install gstring
|
24
|
+
|
25
|
+
## Contributing
|
26
|
+
One day I will put this on github, but for now, enjoy!
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/gstring.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gstring/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gstring"
|
8
|
+
spec.version = Gstring::VERSION
|
9
|
+
spec.authors = ["Bryan Colvin"]
|
10
|
+
spec.email = ["bryan@bdlsys.com"]
|
11
|
+
spec.description = %q{
|
12
|
+
Adds 60+ new methods to the base String class. Most help with parsing, others are more of a hodgepodge of nifty utilities.
|
13
|
+
Utilities include: engineering_notation, cryptogram generator, password generator, sort by embedded numbers, etc.
|
14
|
+
Some represent some of the better ideas from other string related gems.
|
15
|
+
Most are from 20 years of multiple language string extensions that were used in some project.}
|
16
|
+
spec.summary = %q{Adds 60+ new methods String class.}
|
17
|
+
spec.homepage = "http://bdlsys.com/gems/gstring.html"
|
18
|
+
spec.license = "MIT"
|
19
|
+
|
20
|
+
spec.files = `git ls-files`.split($/)
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
23
|
+
spec.require_paths = ["lib"]
|
24
|
+
spec.required_ruby_version = '>= 1.9.1'
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'setfu', '>= 1.2.1'
|
27
|
+
|
28
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
29
|
+
spec.add_development_dependency "rake", ">= 10.1.0"
|
30
|
+
spec.add_development_dependency 'rspec', ">= 3.3.0"
|
31
|
+
spec.add_development_dependency 'byebug', ">= 5.0.0"
|
32
|
+
end
|
data/lib/gstring.rb
ADDED
@@ -0,0 +1,1403 @@
|
|
1
|
+
## Copyright 2015 Bryan Colvin ... public domain so long as my name is present ##
|
2
|
+
#
|
3
|
+
# This gem collects various extensions from competing string gems, and my personal collection.
|
4
|
+
# Expect many future releases as new ideas/needs come to light
|
5
|
+
#
|
6
|
+
##
|
7
|
+
|
8
|
+
require "gstring/version"
|
9
|
+
require "setfu"
|
10
|
+
|
11
|
+
## ACTION ITEMS ##
|
12
|
+
# 1: replace options hash with array ... use *options so you can list them one-by-one
|
13
|
+
# 2: index, rindex ... add array of strings ... refactor parse to use new index
|
14
|
+
|
15
|
+
## FEATURES TODO NEXT REVISION ##
|
16
|
+
# 1: to_morse (maybe ... limited appeal my guess)
|
17
|
+
# 2: encryption? (maybe a separate gem, or include a wrapper from that gem)
|
18
|
+
# 3: format number to engineering notation ... 15530.to_eng => 1515.53e3
|
19
|
+
|
20
|
+
class String
|
21
|
+
@@gs_bracketing_pairs =
|
22
|
+
{
|
23
|
+
'[' => ']',
|
24
|
+
'(' => ')',
|
25
|
+
'{' => '}',
|
26
|
+
'<' => '>'
|
27
|
+
}
|
28
|
+
SI_UNIT_PREFIXES = {1=>'da', 2=>'h', 3=>'k', 6=>'M', 9=>'G', 12=>'T', 15=>'P', 18=>'E', 21=>'Z', 24=>'Y', 27=>'kY', 30=>'MY', 33=>'GY', 36=>'TY', 39=>'PY', 42=>'EY', 45=>'ZY', 48=>'YY',
|
29
|
+
-1=>'d',-2=>'c', -3=>'m', -6=>'µ',-9=>'n',-12=>'p',-15=>'f',-18=>'a',-21=>'z',-24=>'y',-27=>'my',-30=>'µy', -33=>'ny', -36=>'py', -39=>'fy', -42=>'ay', -45=>'zy', -48=>'yy'}
|
30
|
+
RGX_FLOAT = /\A[\+\-]?(0|[1-9]\d*)(([eE][\+\-]?\d+)|(\.\d+((e)?[\+\-]?\d+)?))/
|
31
|
+
STD_ESCAPE_SET_RUBY = [0..31,'"',"'","\\","\;","\#"].to_set
|
32
|
+
STD_ESCAPE_HASH = {7=>"\\a", 8=>"\\b", 12=>"\\f", 10=>"\\n", 13=>"\\r", 9=>"\\t", 11=>"\\v"}
|
33
|
+
SET_PARSE_CHARS = Set.new.add_parse_chars!
|
34
|
+
SET_SPLIT_CHARS = SET_PARSE_CHARS | "_#`\""
|
35
|
+
SET_UPPERS = Set.uppercase_chars
|
36
|
+
SET_LOWERS = Set.lowercase_chars
|
37
|
+
SET_CHARS = Set.letter_chars
|
38
|
+
SET_INT_CHARS = Set.digit_chars
|
39
|
+
GS_SENTENCE_TERM = '?!.'.to_set
|
40
|
+
GS_TITLE_EXCEPTIONS =
|
41
|
+
{
|
42
|
+
"a" =>true,
|
43
|
+
"an" =>true,
|
44
|
+
"and" =>true,
|
45
|
+
"as" =>true,
|
46
|
+
"at" =>true,
|
47
|
+
"but" =>true,
|
48
|
+
"by" =>true,
|
49
|
+
"for" =>true,
|
50
|
+
"from" =>true,
|
51
|
+
"in" =>true,
|
52
|
+
"nor" =>true,
|
53
|
+
"of" =>true,
|
54
|
+
"on" =>true,
|
55
|
+
"or" =>true,
|
56
|
+
"the" =>true,
|
57
|
+
"to" =>true,
|
58
|
+
"up" =>true
|
59
|
+
}
|
60
|
+
|
61
|
+
def self.reset_bracket_pairs
|
62
|
+
@@gs_bracketing_pairs =
|
63
|
+
{
|
64
|
+
'[' => ']',
|
65
|
+
'(' => ')',
|
66
|
+
'{' => '}',
|
67
|
+
'<' => '>'
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.undefine_bracket_pair(str)
|
72
|
+
@@gs_bracketing_pairs.delete(str.first)
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.define_bracket_pair(str)
|
76
|
+
@@gs_bracketing_pairs[str.first] = str.last
|
77
|
+
end
|
78
|
+
|
79
|
+
#ary.sort &String::inside_int_cmp
|
80
|
+
def self.inside_int_cmp(mode=true)
|
81
|
+
return lambda do |a,b|
|
82
|
+
a,b = (mode) ? [a.to_s, b.to_s] : [b.to_s, a.to_s]
|
83
|
+
if(a==b)
|
84
|
+
0
|
85
|
+
else
|
86
|
+
ta = a.dup
|
87
|
+
tb = b.dup
|
88
|
+
rgx = /\d+/
|
89
|
+
if (ta.find(rgx).nil? || tb.find(rgx).nil?)
|
90
|
+
a <=> b # standard compare
|
91
|
+
else # int inside one or both
|
92
|
+
rtn=0
|
93
|
+
loop do
|
94
|
+
if(ta==tb)
|
95
|
+
rtn=0
|
96
|
+
break
|
97
|
+
end
|
98
|
+
if(ta.empty? || tb.empty?)
|
99
|
+
rtn = ta <=> tb
|
100
|
+
break
|
101
|
+
end
|
102
|
+
la = ta.parse(rgx, :no_skip, :no_strip)
|
103
|
+
lb = tb.parse(rgx, :no_skip, :no_strip)
|
104
|
+
if(la != lb)
|
105
|
+
rtn = la <=> lb
|
106
|
+
break
|
107
|
+
end
|
108
|
+
if(ta.parsed != tb.parsed)
|
109
|
+
rtn = ta.parsed.to_i <=> tb.parsed.to_i
|
110
|
+
break
|
111
|
+
end
|
112
|
+
end #loop
|
113
|
+
rtn
|
114
|
+
end # if
|
115
|
+
end # if
|
116
|
+
end # lambda
|
117
|
+
end #self.inside_int_cmp
|
118
|
+
|
119
|
+
def self.chr_uni_esc(num)
|
120
|
+
num = num.to_i
|
121
|
+
return nil if num < 0
|
122
|
+
if num < 256
|
123
|
+
rtn = "\\x" + num.to_s(16).padto(2,'0',:left)
|
124
|
+
elsif num < 0x10000
|
125
|
+
rtn = "\\u" + num.to_s(16).padto(4,'0',:left)
|
126
|
+
elsif num < 0x1000000
|
127
|
+
rtn = "\\u" + num.to_s(16).padto(6,'0',:left)
|
128
|
+
else
|
129
|
+
rtn = "\\u" + num.to_s(16).padto(8,'0',:left)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def upcase?
|
134
|
+
set = self.to_set
|
135
|
+
return false if String::SET_LOWERS ** set # may not have any lower
|
136
|
+
return nil unless String::SET_UPPERS ** set # must have at least one upper
|
137
|
+
return true
|
138
|
+
end
|
139
|
+
|
140
|
+
def downcase?
|
141
|
+
set = self.to_set
|
142
|
+
return false if String::SET_UPPERS ** set # may not have any lower
|
143
|
+
return nil unless String::SET_LOWERS ** set # must have at least one lower
|
144
|
+
return true
|
145
|
+
end
|
146
|
+
|
147
|
+
def mixedcase?
|
148
|
+
set = self.to_set
|
149
|
+
return nil unless set ** (String::SET_LOWERS | String::SET_UPPERS) # must have a letter
|
150
|
+
return (set ** String::SET_LOWERS) && (set ** String::SET_UPPERS)
|
151
|
+
end
|
152
|
+
|
153
|
+
# non-alpha chars must match too
|
154
|
+
def case_match(other) # true if string same size with same case at all positions
|
155
|
+
return false if (length != other.length)
|
156
|
+
length.times do |idx|
|
157
|
+
if (self[idx].upcase?)
|
158
|
+
return false unless other[idx].upcase?
|
159
|
+
elsif (self[idx].downcase?)
|
160
|
+
return false unless other[idx].downcase?
|
161
|
+
else # both must be letters at the position
|
162
|
+
return false if self[idx] != other[idx]
|
163
|
+
end
|
164
|
+
end
|
165
|
+
return true
|
166
|
+
end
|
167
|
+
|
168
|
+
def swapchar(pos, ch, *modes)
|
169
|
+
modes = modes.first if modes.first.class==Array
|
170
|
+
rtn = self[pos]
|
171
|
+
return nil if rtn.nil?
|
172
|
+
if ch.empty?
|
173
|
+
return nil unless modes.include? :empty_ok
|
174
|
+
end
|
175
|
+
unless ch.empty?
|
176
|
+
ch = ch.first unless modes.include? :substring
|
177
|
+
end
|
178
|
+
if modes.include? :casehold
|
179
|
+
if rtn.upcase?
|
180
|
+
self[pos]=ch.upcase
|
181
|
+
elsif rtn.downcase?
|
182
|
+
self[pos]=ch.downcase
|
183
|
+
else
|
184
|
+
self[pos]=ch.first
|
185
|
+
end
|
186
|
+
else
|
187
|
+
self[pos]=ch # standard mode
|
188
|
+
end
|
189
|
+
rtn
|
190
|
+
end
|
191
|
+
|
192
|
+
def find_all(obj, *modes) # returns an array of all positions of obj in self
|
193
|
+
pos = 0
|
194
|
+
ary = []
|
195
|
+
loop do
|
196
|
+
pos = self.index(obj, pos, modes)
|
197
|
+
break if pos.nil?
|
198
|
+
ary.push pos
|
199
|
+
pos +=1
|
200
|
+
end
|
201
|
+
return ary
|
202
|
+
end
|
203
|
+
|
204
|
+
def cryptogram(dat=nil) # nil==> encode, string==>test for match
|
205
|
+
if (dat.nil?)
|
206
|
+
rtn = dup
|
207
|
+
set = Set.lowercase_chars
|
208
|
+
off_limits = []
|
209
|
+
skey = nil
|
210
|
+
ary = (self.downcase.to_set & String::SET_LOWERS).to_a(false).shuffle
|
211
|
+
loop do
|
212
|
+
break if ary.empty?
|
213
|
+
skey = ary.pop
|
214
|
+
hits = find_all(skey,:ignore) - off_limits
|
215
|
+
off_limits = (off_limits | hits).sort
|
216
|
+
# edge case, set only has 1 element in it
|
217
|
+
rpw = (set.count==1) ? set.to_s : (set - skey).rand(1,:string)
|
218
|
+
set -= rpw #can only use an element once
|
219
|
+
hits.each do |pos|
|
220
|
+
rtn.swapchar(pos, rpw, :casehold)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
return rtn
|
224
|
+
elsif (dat.class==String)
|
225
|
+
return false if self.length != dat.length
|
226
|
+
s1 = self.downcase.to_set & Set.lowercase_chars
|
227
|
+
s2 = dat.downcase.to_set & Set.lowercase_chars
|
228
|
+
return false if s1.count != s2.count
|
229
|
+
ary1 = self.downcase.find_all(s1)
|
230
|
+
ary2 = dat.downcase.find_all(s2)
|
231
|
+
return false if ary1 != ary2
|
232
|
+
return false unless case_match(dat)
|
233
|
+
length.times do |idx| # ::TODO:: find faster way ...
|
234
|
+
ary1 = self.find_all(self[idx],:ignore)
|
235
|
+
ary2 = dat.find_all(dat[idx],:ignore)
|
236
|
+
return false if ary1 != ary2
|
237
|
+
end
|
238
|
+
return true
|
239
|
+
else
|
240
|
+
return nil
|
241
|
+
end
|
242
|
+
nil
|
243
|
+
end
|
244
|
+
|
245
|
+
def cross_match(pat)
|
246
|
+
s_ptr=0
|
247
|
+
p_ptr=0
|
248
|
+
ch = ''
|
249
|
+
return nil unless pat.class==String
|
250
|
+
return false if empty?
|
251
|
+
return false if pat.empty?
|
252
|
+
loop do
|
253
|
+
return true if s_ptr >= self.length
|
254
|
+
return false if p_ptr >= pat.length
|
255
|
+
if ch != ''
|
256
|
+
if ch==self[s_ptr]
|
257
|
+
ch=""
|
258
|
+
s_ptr +=1
|
259
|
+
p_ptr +=1
|
260
|
+
if s_ptr >= self.length
|
261
|
+
return false if pat[p_ptr]=='+'
|
262
|
+
end
|
263
|
+
else
|
264
|
+
s_ptr +=1
|
265
|
+
return false if s_ptr >= self.length # ran out before match
|
266
|
+
end
|
267
|
+
elsif (pat[p_ptr]=='_')
|
268
|
+
p_ptr+=1
|
269
|
+
s_ptr+=1
|
270
|
+
elsif (pat[p_ptr]=='+')
|
271
|
+
s_ptr+=1 # skip next letter
|
272
|
+
p_ptr+=1
|
273
|
+
ch = pat[p_ptr]
|
274
|
+
return true if (ch.nil? || ch=='') #end of the line
|
275
|
+
elsif (pat[p_ptr]=='*')
|
276
|
+
p_ptr+=1
|
277
|
+
ch = pat[p_ptr]
|
278
|
+
return true if (ch.nil? || ch=='') #end of the line
|
279
|
+
elsif (pat[p_ptr]=='`')
|
280
|
+
p_ptr+=1
|
281
|
+
return false unless pat[p_ptr]==self[s_ptr]
|
282
|
+
p_ptr+=1
|
283
|
+
s_ptr+=1
|
284
|
+
else # compare chars
|
285
|
+
return false unless pat[p_ptr]==self[s_ptr]
|
286
|
+
p_ptr+=1
|
287
|
+
s_ptr+=1
|
288
|
+
end
|
289
|
+
end
|
290
|
+
return true
|
291
|
+
end
|
292
|
+
|
293
|
+
def condense!
|
294
|
+
strip!
|
295
|
+
str = ""
|
296
|
+
sp = false
|
297
|
+
each_char do |ch|
|
298
|
+
if (ch <= ' ')
|
299
|
+
sp = true
|
300
|
+
else
|
301
|
+
str += sp ? ' ' + ch : ch
|
302
|
+
sp = false
|
303
|
+
end
|
304
|
+
end
|
305
|
+
replace str
|
306
|
+
self
|
307
|
+
end
|
308
|
+
|
309
|
+
def condense
|
310
|
+
dup.condense!
|
311
|
+
end
|
312
|
+
|
313
|
+
def sort
|
314
|
+
self.split('').sort.join('')
|
315
|
+
end
|
316
|
+
|
317
|
+
def sort!
|
318
|
+
replace self.split('').sort.join('')
|
319
|
+
end
|
320
|
+
|
321
|
+
def histogram
|
322
|
+
hash = {}
|
323
|
+
self.split('').sort.each do |ch|
|
324
|
+
hash[ch] = hash[ch].nil? ? 1 : 1 + hash[ch]
|
325
|
+
end
|
326
|
+
return hash
|
327
|
+
end
|
328
|
+
|
329
|
+
def duplicates?
|
330
|
+
set = self.to_set
|
331
|
+
return false if set.count == length
|
332
|
+
return true
|
333
|
+
end
|
334
|
+
|
335
|
+
# ... add new meaning to what would have raised an error ...
|
336
|
+
# now means case insensitive compare
|
337
|
+
alias_method :old_string_rgx_cmp, :=~
|
338
|
+
def =~(str)
|
339
|
+
return old_string_rgx_cmp(str) unless str.class==String
|
340
|
+
self.downcase == str.downcase
|
341
|
+
end
|
342
|
+
|
343
|
+
# generator does not need an instance
|
344
|
+
def self.random_password(chars=8, special="_-#!~@$%^*+=?:")
|
345
|
+
raise "password must be at least 8 characters" if chars < 8
|
346
|
+
low = Set.lowercase_chars
|
347
|
+
high = Set.uppercase_chars
|
348
|
+
digits = Set.digit_chars
|
349
|
+
special = special.to_set rescue Set.new
|
350
|
+
all = low | high | digits | special
|
351
|
+
a,b = low.rand(2,:array_chars)
|
352
|
+
c,d = high.rand(2, :array_chars)
|
353
|
+
e = digits.rand(1, :array_chars)
|
354
|
+
f = special.rand(1, :array_chars)
|
355
|
+
pswd = [a,b,c,d,e]
|
356
|
+
pswd.push f unless (f.nil? || f.empty?)
|
357
|
+
filler = all.rand(chars - pswd.length, :array_chars)
|
358
|
+
filler.each do |ch|
|
359
|
+
pswd.push ch
|
360
|
+
end
|
361
|
+
pswd.shuffle!
|
362
|
+
return pswd.join ''
|
363
|
+
end
|
364
|
+
|
365
|
+
def remove(prm)
|
366
|
+
set = prm.to_set
|
367
|
+
ns = ""
|
368
|
+
self.each_char do |ch|
|
369
|
+
ns += ch unless set.include? ch
|
370
|
+
end
|
371
|
+
return ns
|
372
|
+
end
|
373
|
+
|
374
|
+
def remove!(prm) # remove all characters
|
375
|
+
replace remove(prm)
|
376
|
+
end
|
377
|
+
|
378
|
+
|
379
|
+
def extract!(prm=1, fill_hole="")
|
380
|
+
return "" if empty?
|
381
|
+
if prm.kind_of? Integer # extract fron-end of string
|
382
|
+
return "" if prm < 1
|
383
|
+
prm = prm > length ? length : prm
|
384
|
+
rtn = self[0..(prm-1)]
|
385
|
+
# replace( fill_hole + self[prm..-1] ) # WRONG! ... we only want the same number of fill
|
386
|
+
replace( fill_hole[0..(prm-1)] + self[prm..-1] )
|
387
|
+
return rtn
|
388
|
+
elsif prm.class == Range
|
389
|
+
start = prm.first < 0 ? self.length + prm.first : prm.first
|
390
|
+
finish = prm.last < 0 ? self.length + prm.last : prm.last
|
391
|
+
if (start <= finish) # normal forward order
|
392
|
+
return "" if start >= length
|
393
|
+
finish = finish >= length ? length-1 : finish
|
394
|
+
return extract!(finish+1, fill_hole) if start==0 # was extract without the '!'
|
395
|
+
rtn = self[start..finish]
|
396
|
+
replace(self[0..(start-1)] + fill_hole + self[(finish+1)..-1])
|
397
|
+
return rtn
|
398
|
+
else # reverse order
|
399
|
+
return "" if finish >= length
|
400
|
+
start = start >= length ? length - 1 : start
|
401
|
+
rtn = self[finish..start].reverse
|
402
|
+
if(finish==0)
|
403
|
+
replace(fill_hole + self[(start+1)..-1])
|
404
|
+
else
|
405
|
+
replace(self[0..(finish-1)] + fill_hole + self[(start+1)..-1])
|
406
|
+
end
|
407
|
+
return rtn
|
408
|
+
end
|
409
|
+
elsif prm.class == Array
|
410
|
+
rtn = ""
|
411
|
+
# first count number of substitutions
|
412
|
+
cnt = 0
|
413
|
+
prm.each do |item|
|
414
|
+
if item.kind_of? Integer
|
415
|
+
cnt += 1
|
416
|
+
else
|
417
|
+
cnt += item.count
|
418
|
+
end
|
419
|
+
end
|
420
|
+
filler = fill_hole.padto(cnt, "\000", :right, :no_trunc) # use null as place holder
|
421
|
+
prm.each do |item|
|
422
|
+
if item.kind_of? Integer
|
423
|
+
pos = item < 0 ? 0 : item
|
424
|
+
if pos < length
|
425
|
+
rtn += self[pos]
|
426
|
+
self[pos]=filler.first!
|
427
|
+
end # ignore if out of range
|
428
|
+
else # use recursion
|
429
|
+
str = self.extract!(item, filler)
|
430
|
+
rtn += str
|
431
|
+
filler.extract! str.length # remove and discard
|
432
|
+
end
|
433
|
+
end
|
434
|
+
remove! "\000"
|
435
|
+
return rtn
|
436
|
+
else # convert to set
|
437
|
+
ary = (prm.to_set & (0..(self.length-1))).to_a #ignore everything out of range
|
438
|
+
fill=fill_hole.dup
|
439
|
+
rtn = ""
|
440
|
+
oft = 0
|
441
|
+
ary.each do |idx|
|
442
|
+
ch = fill.extract!
|
443
|
+
rtn += self[idx-oft]
|
444
|
+
self[idx-oft]=ch
|
445
|
+
oft+=1 if ch.empty?
|
446
|
+
end
|
447
|
+
return rtn
|
448
|
+
end
|
449
|
+
return ""
|
450
|
+
end
|
451
|
+
|
452
|
+
def extract(prm)
|
453
|
+
return dup.extract!(prm, '')
|
454
|
+
end
|
455
|
+
|
456
|
+
def enclose(pairs, escape=nil, set=String::STD_ESCAPE_SET_RUBY, hash=String::STD_ESCAPE_HASH)
|
457
|
+
return self if pairs.empty?
|
458
|
+
if escape.nil?
|
459
|
+
return pairs.first + self + pairs.last
|
460
|
+
else # look for pairs.first , and replace with {escape}{pairs.first}
|
461
|
+
str = pairs.first
|
462
|
+
self.each_char do |ch|
|
463
|
+
if set.include? ch
|
464
|
+
idx = (set & ch).to_a.first
|
465
|
+
if (ch ** (set-[0..31]))
|
466
|
+
#byebug
|
467
|
+
str += "\\" + ch
|
468
|
+
elsif hash.include? idx
|
469
|
+
#byebug
|
470
|
+
str += hash[idx]
|
471
|
+
else # use hex format
|
472
|
+
str += String.chr_uni_esc(idx)
|
473
|
+
end
|
474
|
+
else
|
475
|
+
str += ch
|
476
|
+
end
|
477
|
+
end
|
478
|
+
return str + pairs.last
|
479
|
+
#str = self.gsub(pairs.first) { escape + pairs.first }
|
480
|
+
#return pairs.first + str + pairs.last
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
def enclose!(pairs, escape=nil)
|
485
|
+
replace enclose(pairs, escape)
|
486
|
+
end
|
487
|
+
|
488
|
+
def unenclose(escape=nil) # unescape
|
489
|
+
return nil if length < 2
|
490
|
+
unless first==last
|
491
|
+
return nil unless last == @@gs_bracketing_pairs[first]
|
492
|
+
end
|
493
|
+
if @@gs_bracketing_pairs.include? first
|
494
|
+
return nil if first==last
|
495
|
+
end
|
496
|
+
str = self[1..(length-2)]
|
497
|
+
unless escape.nil?
|
498
|
+
str = str.gsub(escape) { '' } # blind removal? ... may need to rethink this a tad
|
499
|
+
end
|
500
|
+
return str
|
501
|
+
end
|
502
|
+
|
503
|
+
def unenclose!(escape=nil)
|
504
|
+
str = unenclose(escape)
|
505
|
+
replace str unless str.nil?
|
506
|
+
str.nil? ? nil : self
|
507
|
+
end
|
508
|
+
|
509
|
+
def indent
|
510
|
+
cnt = 0
|
511
|
+
each_char do |ch|
|
512
|
+
break if ch > ' '
|
513
|
+
cnt += 1
|
514
|
+
end
|
515
|
+
cnt
|
516
|
+
end
|
517
|
+
|
518
|
+
def indent!
|
519
|
+
rtn = indent
|
520
|
+
lstrip!
|
521
|
+
return rtn
|
522
|
+
end
|
523
|
+
|
524
|
+
# ::todo:: Add flags :rotate, :stick
|
525
|
+
|
526
|
+
def padto(tar, *prms)
|
527
|
+
return self if length == tar
|
528
|
+
prms = prms.first if prms.first.kind_of? Array
|
529
|
+
with = ' '
|
530
|
+
flags = []
|
531
|
+
how = :left
|
532
|
+
prms.each do |prm|
|
533
|
+
if prm.kind_of? String
|
534
|
+
with=prm
|
535
|
+
next
|
536
|
+
end
|
537
|
+
case prm
|
538
|
+
when :no_trunc, :rotate, :stick, :space, :swing
|
539
|
+
flags.push prm
|
540
|
+
when :left, :right, :both
|
541
|
+
how = prm
|
542
|
+
else
|
543
|
+
raise "padto unknown parameter"
|
544
|
+
end
|
545
|
+
end
|
546
|
+
if length > tar
|
547
|
+
return self if flags.include? :no_trunc
|
548
|
+
return "" if tar<=0
|
549
|
+
return '…' if tar==1
|
550
|
+
return self[0..(tar-2)]+'…'
|
551
|
+
end
|
552
|
+
with = ' ' if with.empty?
|
553
|
+
rtn = ""
|
554
|
+
flags -= [:no_trunc]
|
555
|
+
flag = flags.empty? ? :stick : flags.first
|
556
|
+
with.access_mode(flag==:space ? :default : flag)
|
557
|
+
case how
|
558
|
+
when :right
|
559
|
+
rtn = self
|
560
|
+
(tar-length).times do |xx|
|
561
|
+
rtn += with.next
|
562
|
+
end
|
563
|
+
when :both
|
564
|
+
lp = (tar-length) >> 1
|
565
|
+
rp = (tar-length) - lp
|
566
|
+
str = ""
|
567
|
+
(rp).times do |xx|
|
568
|
+
str += with.next
|
569
|
+
end
|
570
|
+
rtn = str[0..(lp-1)].reverse + self + str
|
571
|
+
else # :left
|
572
|
+
str = ""
|
573
|
+
(tar-length).times do |xx|
|
574
|
+
str += with.next
|
575
|
+
end
|
576
|
+
rtn += str.reverse + self
|
577
|
+
end
|
578
|
+
rtn
|
579
|
+
end
|
580
|
+
|
581
|
+
def padto!(tar, *flags)
|
582
|
+
replace padto(tar, flags)
|
583
|
+
end
|
584
|
+
|
585
|
+
# negative len means position from right to left
|
586
|
+
# positive len means number of chars to capture
|
587
|
+
# exception if pos and len are both negative and pos > len
|
588
|
+
# ... then starting point is the |sum| from the right, and length
|
589
|
+
# ... and the ending point is also the |sum| ??? need more examples
|
590
|
+
def php_substr(pos=0,len=nil)
|
591
|
+
return "" if len==0
|
592
|
+
len = len.nil? ? length : len
|
593
|
+
r_s = (pos >= 0) ? pos : pos + length
|
594
|
+
r_e = (len >= 0) ? r_s + len - 1 : len + length - 1
|
595
|
+
return self[(r_s)..(r_e)]
|
596
|
+
end
|
597
|
+
|
598
|
+
def ignore_me
|
599
|
+
self
|
600
|
+
end
|
601
|
+
|
602
|
+
def parse(search_key = String::SET_PARSE_CHARS, *options)
|
603
|
+
options = options.first if options.first.class==Array
|
604
|
+
meth = options.include?(:no_strip) ? :ignore_me : :strip
|
605
|
+
rtn=""
|
606
|
+
@found = nil
|
607
|
+
if(search_key.class==Set)
|
608
|
+
#skip over first char
|
609
|
+
idx = options.include?(:no_skip) ? 0 : 1
|
610
|
+
sk = options.include?(:ignore) ? search_key.add_opposing_case : search_key
|
611
|
+
sf = idx
|
612
|
+
self[sf...length].each_char do |ch|
|
613
|
+
if(sk.include? ch)
|
614
|
+
@found=ch
|
615
|
+
break
|
616
|
+
end
|
617
|
+
idx += 1
|
618
|
+
end
|
619
|
+
unless @found.nil?
|
620
|
+
rtn = (idx==0) ? "" : self[0..(idx-1)].send(meth)
|
621
|
+
replace self[(idx+1)..-1].send(meth)
|
622
|
+
return rtn
|
623
|
+
end
|
624
|
+
rtn = clone
|
625
|
+
clear
|
626
|
+
return rtn
|
627
|
+
elsif (search_key.class == Regexp)
|
628
|
+
sf = options.include?(:no_skip) ? 0 : 1
|
629
|
+
pos = index(search_key,sf)
|
630
|
+
@found = pos.nil? ? nil : self.match(search_key)[0]
|
631
|
+
if pos.nil?
|
632
|
+
rtn = clone
|
633
|
+
clear
|
634
|
+
return rtn
|
635
|
+
else # found
|
636
|
+
rtn = pos.zero? ? "" : self[0..(pos-1)].send(meth)
|
637
|
+
replace self[(pos+@found.length)..length].send(meth)
|
638
|
+
return rtn
|
639
|
+
end
|
640
|
+
elsif (search_key.class == String)
|
641
|
+
#skip over first char unless option specifies :no_skip
|
642
|
+
sf = options.include?(:no_skip) ? 0 : 1
|
643
|
+
idx = index(search_key,sf,options)
|
644
|
+
if idx.nil?
|
645
|
+
rtn = clone
|
646
|
+
clear
|
647
|
+
return rtn
|
648
|
+
else
|
649
|
+
rtn = idx.zero? ? "" : self[0..(idx-1)].send(meth)
|
650
|
+
@found = self[idx..(idx+search_key.length-1)]
|
651
|
+
replace self[(idx+search_key.length)..length].send(meth)
|
652
|
+
return rtn
|
653
|
+
end
|
654
|
+
elsif (search_key.class == Array)
|
655
|
+
shortest = nil
|
656
|
+
best = nil
|
657
|
+
start = options.include?(:no_skip) ? 0 : 1
|
658
|
+
search_key.each_with_index do |val,idx|
|
659
|
+
pos = self.index(val,start,options)
|
660
|
+
unless pos.nil?
|
661
|
+
best = idx if best.nil?
|
662
|
+
shortest ||= pos
|
663
|
+
if (pos < shortest)
|
664
|
+
shortest = pos
|
665
|
+
best = idx
|
666
|
+
end
|
667
|
+
end
|
668
|
+
end
|
669
|
+
if shortest.nil?
|
670
|
+
rtn = clone
|
671
|
+
clear
|
672
|
+
return rtn
|
673
|
+
else
|
674
|
+
#opt = options.dup
|
675
|
+
#opt[:no_skip]=true
|
676
|
+
#return parse(search_key[best],opt)
|
677
|
+
return parse(search_key[best],options)
|
678
|
+
end
|
679
|
+
else # we are passed something that should have been converted to a set
|
680
|
+
return parse([search_key].to_set,options)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
684
|
+
## ##
|
685
|
+
# Updates to existing index, rindex methods #
|
686
|
+
# now accepts search types of: Set, Array #
|
687
|
+
## ##
|
688
|
+
|
689
|
+
alias_method :old_string_index_method_4gstring, :index
|
690
|
+
alias_method :old_string_rindex_method_4gstring, :rindex
|
691
|
+
|
692
|
+
def index(search,from=0,*options)
|
693
|
+
while options.first.class==Array
|
694
|
+
options = options.first
|
695
|
+
end
|
696
|
+
if (search.class==String)
|
697
|
+
return self.downcase.index(search.downcase,from) if (options.include? :ignore)
|
698
|
+
return old_string_index_method_4gstring(search,from)
|
699
|
+
end
|
700
|
+
|
701
|
+
if search.class==Array
|
702
|
+
best = nil
|
703
|
+
search.each_with_index do |item,idx|
|
704
|
+
pos = index(item,from,options)
|
705
|
+
unless pos.nil?
|
706
|
+
@best ||= idx
|
707
|
+
best ||= pos
|
708
|
+
if pos < best
|
709
|
+
best = pos
|
710
|
+
@best = idx
|
711
|
+
end
|
712
|
+
end
|
713
|
+
end
|
714
|
+
return best # parse uses this to know which item was found
|
715
|
+
end
|
716
|
+
|
717
|
+
# call this for unrecognized options
|
718
|
+
return old_string_index_method_4gstring(search,from) unless search.class == Set
|
719
|
+
if options.include? :ignore
|
720
|
+
return self.downcase.index(search.add_opposing_case,from)
|
721
|
+
end
|
722
|
+
if (from < 0)
|
723
|
+
from = length + from
|
724
|
+
end
|
725
|
+
from = 0 if from < 0
|
726
|
+
((from)...(length)).each do |ptr|
|
727
|
+
return ptr if search.include? self[ptr]
|
728
|
+
end
|
729
|
+
return nil
|
730
|
+
end
|
731
|
+
|
732
|
+
def rindex(search,from=-1,*options)
|
733
|
+
options = options.first if options.first.class==Array
|
734
|
+
if (search.class==String)
|
735
|
+
return self.downcase.rindex(search.downcase,from) if (options.include? :ignore)
|
736
|
+
return old_string_rindex_method_4gstring(search,from)
|
737
|
+
end
|
738
|
+
|
739
|
+
if search.class==Array
|
740
|
+
best = nil
|
741
|
+
search.each_with_index do |item,idx|
|
742
|
+
pos = rindex(item,from,options)
|
743
|
+
unless pos.nil?
|
744
|
+
@best ||= idx
|
745
|
+
best ||= pos
|
746
|
+
if pos > best
|
747
|
+
best = pos
|
748
|
+
@best = idx
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
return best # parse uses this to know which item was found
|
753
|
+
end
|
754
|
+
|
755
|
+
# call this for unrecognized options
|
756
|
+
return old_string_rindex_method_4gstring(search,from) unless search.class == Set
|
757
|
+
if options.include? :ignore
|
758
|
+
return self.downcase.rindex(search.add_opposing_case,from)
|
759
|
+
end
|
760
|
+
idx = (from < 0) ? length + from : from
|
761
|
+
return nil if idx < 0
|
762
|
+
until (search.include? (self[idx])) do
|
763
|
+
idx -= 1
|
764
|
+
return nil if idx < 0
|
765
|
+
end
|
766
|
+
return idx
|
767
|
+
end
|
768
|
+
|
769
|
+
alias_method :find, :index
|
770
|
+
alias_method :rfind, :rindex
|
771
|
+
|
772
|
+
def first
|
773
|
+
return "" if empty?
|
774
|
+
self[0]
|
775
|
+
end
|
776
|
+
|
777
|
+
def last
|
778
|
+
return "" if empty?
|
779
|
+
self[length-1]
|
780
|
+
end
|
781
|
+
|
782
|
+
# removes first char
|
783
|
+
def first!
|
784
|
+
return "" if empty?
|
785
|
+
rtn = self[0]
|
786
|
+
replace self[1..(length-1)]
|
787
|
+
return rtn
|
788
|
+
end
|
789
|
+
|
790
|
+
# removes last char
|
791
|
+
def last!
|
792
|
+
if length==1
|
793
|
+
rtn = dup
|
794
|
+
self.clear
|
795
|
+
return rtn
|
796
|
+
end
|
797
|
+
return "" if empty?
|
798
|
+
rtn = self[length-1]
|
799
|
+
replace self[0..(length-2)]
|
800
|
+
return rtn
|
801
|
+
end
|
802
|
+
|
803
|
+
def extract_trailing_int!
|
804
|
+
return nil if empty?
|
805
|
+
return nil unless String::SET_INT_CHARS.include? last
|
806
|
+
str = ""
|
807
|
+
while String::SET_INT_CHARS.include? last
|
808
|
+
str += last!
|
809
|
+
break if empty?
|
810
|
+
end
|
811
|
+
return str.reverse.to_i
|
812
|
+
end
|
813
|
+
|
814
|
+
def dec!(val=nil)
|
815
|
+
num = extract_trailing_int!
|
816
|
+
return self if num.nil?
|
817
|
+
return self if 0==num
|
818
|
+
val ||= 1
|
819
|
+
return self if (num-val) < 0
|
820
|
+
append!(num-val)
|
821
|
+
dup
|
822
|
+
end
|
823
|
+
|
824
|
+
def dec(val=nil)
|
825
|
+
return dup.dec!(val)
|
826
|
+
end
|
827
|
+
|
828
|
+
# increments integer at end of string
|
829
|
+
def inc!(val=nil)
|
830
|
+
num = extract_trailing_int!
|
831
|
+
if num.nil?
|
832
|
+
if val.nil?
|
833
|
+
append!(0)
|
834
|
+
else
|
835
|
+
append!(val)
|
836
|
+
end
|
837
|
+
else
|
838
|
+
append! (val.nil? ? (num+1) : (num+val))
|
839
|
+
end
|
840
|
+
dup
|
841
|
+
end
|
842
|
+
|
843
|
+
def inc(val=nil)
|
844
|
+
return dup.inc!(val)
|
845
|
+
end
|
846
|
+
|
847
|
+
def append!(obj)
|
848
|
+
replace self + obj.to_s
|
849
|
+
end
|
850
|
+
|
851
|
+
def append(obj)
|
852
|
+
self + obj.to_s
|
853
|
+
end
|
854
|
+
|
855
|
+
def zap! # simple alias
|
856
|
+
clear
|
857
|
+
end
|
858
|
+
|
859
|
+
def parsed
|
860
|
+
@found ||= nil
|
861
|
+
end
|
862
|
+
|
863
|
+
def extract_leading_set!(set)
|
864
|
+
rtn = ""
|
865
|
+
set = set.to_set
|
866
|
+
while set.include? first do
|
867
|
+
rtn += first!
|
868
|
+
end
|
869
|
+
return rtn
|
870
|
+
end
|
871
|
+
|
872
|
+
def extract_trailing_set!(set)
|
873
|
+
rtn = ""
|
874
|
+
set = set.to_set
|
875
|
+
while set.include? last do
|
876
|
+
rtn += last!
|
877
|
+
end
|
878
|
+
return rtn.reverse
|
879
|
+
end
|
880
|
+
|
881
|
+
def split_on_set(set=' ')
|
882
|
+
str = dup
|
883
|
+
if (set.nil? || set.empty?)
|
884
|
+
set = ' '
|
885
|
+
end
|
886
|
+
set = set.to_set
|
887
|
+
prefx = str.extract_leading_set!(set)
|
888
|
+
pstfx = str.extract_trailing_set!(set)
|
889
|
+
dat = []
|
890
|
+
tok = []
|
891
|
+
loop do
|
892
|
+
break if str.empty?
|
893
|
+
dat.push str.parse(set, :no_strip)
|
894
|
+
tmp = str.parsed
|
895
|
+
break if tmp.nil?
|
896
|
+
tmp += str.extract_leading_set!(set)
|
897
|
+
tok.push tmp
|
898
|
+
end
|
899
|
+
dat.push str unless str.empty?
|
900
|
+
return [dat,tok,prefx,pstfx]
|
901
|
+
end
|
902
|
+
|
903
|
+
def gs_titlecase
|
904
|
+
downcase!
|
905
|
+
if index(GS_SENTENCE_TERM)
|
906
|
+
ary = self.split_on_set(GS_SENTENCE_TERM)
|
907
|
+
pa = []
|
908
|
+
ary.first.each do |sentence|
|
909
|
+
pa.push(sentence.gs_titlecase.strip)
|
910
|
+
end
|
911
|
+
ary[0] = pa.reverse!
|
912
|
+
ary[1].reverse!
|
913
|
+
rtn = ary.first.pop
|
914
|
+
until ary.first.empty?
|
915
|
+
rtn += "#{ary[1].pop} #{ary.first.pop}"
|
916
|
+
end
|
917
|
+
rtn += ary.last || ""
|
918
|
+
return rtn
|
919
|
+
end
|
920
|
+
ary = self.split(' ')
|
921
|
+
ary.first.capitalize!
|
922
|
+
ary.last.capitalize!
|
923
|
+
(1..(ary.size-2)).each do |idx|
|
924
|
+
ary[idx].capitalize! unless GS_TITLE_EXCEPTIONS.include?(ary[idx])
|
925
|
+
end
|
926
|
+
return ary.join ' '
|
927
|
+
end
|
928
|
+
|
929
|
+
def gs_titlecase!
|
930
|
+
replace gs_titlecase
|
931
|
+
end
|
932
|
+
|
933
|
+
def shuffle
|
934
|
+
return self.split('').shuffle.join
|
935
|
+
end
|
936
|
+
|
937
|
+
def shuffle!
|
938
|
+
replace shuffle
|
939
|
+
end
|
940
|
+
|
941
|
+
def sublist(*prms)
|
942
|
+
if (prms.first.class==Symbol)
|
943
|
+
if prms.first == :bang
|
944
|
+
prms = prms[1]
|
945
|
+
else
|
946
|
+
raise "illegal sublist parameter"
|
947
|
+
end
|
948
|
+
end
|
949
|
+
prms.reverse!
|
950
|
+
cls = prms.pop
|
951
|
+
flags = {}
|
952
|
+
dflt = nil
|
953
|
+
subh = nil # substitution hash
|
954
|
+
suba = nil # substitution array
|
955
|
+
cnt = nil
|
956
|
+
until prms.empty?
|
957
|
+
tmp = prms.pop
|
958
|
+
if(tmp.class==Array)
|
959
|
+
raise "parameter error" unless suba.nil?
|
960
|
+
suba = tmp.reverse # make it easy to pop, and makes a new instance
|
961
|
+
elsif (tmp.class==Hash)
|
962
|
+
raise "parameter error" unless subh.nil?
|
963
|
+
subh = {}
|
964
|
+
tmp.each do |key,val|
|
965
|
+
subh[key.to_s] = val.to_s # convert keys and values to strings
|
966
|
+
end
|
967
|
+
elsif (tmp.class==Symbol)
|
968
|
+
flags[tmp] = true
|
969
|
+
elsif (tmp.class==Fixnum)
|
970
|
+
raise "count parameter repeated" unless cnt.nil?
|
971
|
+
cnt = tmp
|
972
|
+
elsif (tmp.class==String)
|
973
|
+
raise "parameter error" unless dflt.nil?
|
974
|
+
dflt = tmp
|
975
|
+
else
|
976
|
+
raise "unknown parameter type"
|
977
|
+
end
|
978
|
+
end
|
979
|
+
|
980
|
+
str = self.dup
|
981
|
+
ptr = 0
|
982
|
+
rtn = ""
|
983
|
+
cls = [cls] if (String==cls.class)
|
984
|
+
fa = []
|
985
|
+
# add more as needed later ...
|
986
|
+
fa.push :ignore if flags.include? :ignore
|
987
|
+
fass = fa + [:no_strip] + [:no_skip]
|
988
|
+
cls = [cls] if (Set==cls.class) # push everything into an array even regx
|
989
|
+
cls = [cls] if (Regexp==cls.class)
|
990
|
+
flst = (flags.include? :first) ? {} : nil
|
991
|
+
|
992
|
+
# remove nil values
|
993
|
+
subh ||= {}
|
994
|
+
suba ||= []
|
995
|
+
dflt ||= ""
|
996
|
+
|
997
|
+
if flags.include? :ignore # update substitution hash keys to lower case if :ignore
|
998
|
+
unless subh.empty?
|
999
|
+
tmp = {}
|
1000
|
+
subh.each do |key,val|
|
1001
|
+
tmp[key.downcase] = val.to_s
|
1002
|
+
end
|
1003
|
+
subh = tmp
|
1004
|
+
end
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
unless (Hash==cls.class)
|
1008
|
+
loop do
|
1009
|
+
unless cnt.nil?
|
1010
|
+
if (cnt <= 0)
|
1011
|
+
rtn += str
|
1012
|
+
return rtn
|
1013
|
+
end
|
1014
|
+
end
|
1015
|
+
rtn += str.parse(cls, fass) # make sure we are working on arrays of a bunch of stuff including nested === add test case
|
1016
|
+
break if str.parsed.nil?
|
1017
|
+
|
1018
|
+
unless flst.nil? # process first flag exceptions
|
1019
|
+
key = (flags.include? :ignore) ? str.parsed.downcase : str.parsed
|
1020
|
+
if flst.include? key
|
1021
|
+
rtn += str.parsed # make no substitution
|
1022
|
+
cnt -= 1 unless cnt.nil?
|
1023
|
+
next
|
1024
|
+
else
|
1025
|
+
flst[key] = true # mark as taboo on future substitutions
|
1026
|
+
end
|
1027
|
+
end
|
1028
|
+
|
1029
|
+
# first check to see if we have a hash match
|
1030
|
+
unless subh.empty?
|
1031
|
+
key = (flags.include? :ignore) ? str.parsed.downcase : str.parsed
|
1032
|
+
dat = subh[key]
|
1033
|
+
unless dat.nil?
|
1034
|
+
rtn += dat
|
1035
|
+
return rtn if str.empty?
|
1036
|
+
cnt -= 1 unless cnt.nil?
|
1037
|
+
next
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# next check to see if we have substitution data in the array
|
1042
|
+
unless suba.empty?
|
1043
|
+
rtn += suba.pop
|
1044
|
+
return rtn if str.empty?
|
1045
|
+
cnt -= 1 unless cnt.nil?
|
1046
|
+
next
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
# at last use the default
|
1050
|
+
if flags.include? :stop # do not make substitutions with default
|
1051
|
+
rtn += str.parsed
|
1052
|
+
rtn += str
|
1053
|
+
return rtn
|
1054
|
+
elsif flags.include? :skip # skip over, then keep going
|
1055
|
+
rtn += str.parsed
|
1056
|
+
else
|
1057
|
+
rtn += dflt
|
1058
|
+
end
|
1059
|
+
return rtn if str.empty?
|
1060
|
+
cnt -= 1 unless cnt.nil?
|
1061
|
+
end # loop
|
1062
|
+
return rtn
|
1063
|
+
else # (Hash==cls.class)
|
1064
|
+
cls = cls.dup
|
1065
|
+
loop do
|
1066
|
+
unless cnt.nil?
|
1067
|
+
if (cnt <= 0)
|
1068
|
+
rtn += str
|
1069
|
+
return rtn
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
pos = nil
|
1073
|
+
best = nil
|
1074
|
+
bval = nil
|
1075
|
+
cls.each do |key,val|
|
1076
|
+
idx = str.index(key.to_s, 0 , fa)
|
1077
|
+
unless idx.nil?
|
1078
|
+
pos ||= idx
|
1079
|
+
best ||= key.to_s
|
1080
|
+
bval ||= val.to_s
|
1081
|
+
if(idx < pos)
|
1082
|
+
pos = idx
|
1083
|
+
best = key.to_s
|
1084
|
+
bval = val.to_s
|
1085
|
+
end
|
1086
|
+
break if idx.zero? # can't get better than zero, so stop looking
|
1087
|
+
end
|
1088
|
+
end
|
1089
|
+
if best.nil?
|
1090
|
+
rtn += str
|
1091
|
+
return rtn
|
1092
|
+
end
|
1093
|
+
# byebug if flags.include? :ignore
|
1094
|
+
rtn += str.parse(best, fass)
|
1095
|
+
break if str.parsed.nil?
|
1096
|
+
if flags.include? :first
|
1097
|
+
cls.delete best
|
1098
|
+
cls.delete best.to_sym # just in case
|
1099
|
+
end
|
1100
|
+
rtn += bval
|
1101
|
+
cnt -= 1 unless cnt.nil?
|
1102
|
+
end #loop
|
1103
|
+
return rtn
|
1104
|
+
end
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
def sublist!(*prms)
|
1108
|
+
replace sublist(:bang, prms)
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
def to_num # convert to integer or float depending on format
|
1112
|
+
self.dup.extract_num!
|
1113
|
+
end
|
1114
|
+
|
1115
|
+
def extract_num!
|
1116
|
+
dat = parse(String::RGX_FLOAT, :no_skip)
|
1117
|
+
if parsed.nil? # no number found
|
1118
|
+
num = dat.extract_leading_set!(Set.digit_chars)
|
1119
|
+
replace dat
|
1120
|
+
return 0 if num.empty?
|
1121
|
+
return num.to_i
|
1122
|
+
else
|
1123
|
+
return parsed.to_f
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
def limit_to(size, *flags)
|
1128
|
+
return self if length <= size
|
1129
|
+
if flags.include? :no_break
|
1130
|
+
pos = self.rfind(SET_SPLIT_CHARS)
|
1131
|
+
return self[0..(size-2)] + '…' if pos.nil?
|
1132
|
+
return self[0..(pos-1)] + '…'
|
1133
|
+
else
|
1134
|
+
return self[0..(size-2)] + '…'
|
1135
|
+
end
|
1136
|
+
end
|
1137
|
+
|
1138
|
+
def to_eng(pa=6, unit=nil)
|
1139
|
+
return self.to_f.to_eng(pa, unit)
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
def to_scd(dp=nil, delim = ',.')
|
1143
|
+
num = to_num
|
1144
|
+
if(dp.nil?)
|
1145
|
+
if num.class==Float
|
1146
|
+
return num.to_scd(2,delim)
|
1147
|
+
else
|
1148
|
+
return num.to_scd(0,delim)
|
1149
|
+
end
|
1150
|
+
end
|
1151
|
+
return num.to_scd(dp,delim)
|
1152
|
+
end
|
1153
|
+
|
1154
|
+
def rand(cnt=1, *prms)
|
1155
|
+
return "" if empty?
|
1156
|
+
return "" if cnt < 1
|
1157
|
+
if length==1
|
1158
|
+
return self if prms.include? :set
|
1159
|
+
return self * cnt
|
1160
|
+
end
|
1161
|
+
rnd = Random.new
|
1162
|
+
rtn = ""
|
1163
|
+
str = prms.include?(:set) ? self.to_set.to_s : self.dup
|
1164
|
+
cnt.times do
|
1165
|
+
break if str.empty?
|
1166
|
+
ch = str[rnd.rand(str.length)]
|
1167
|
+
rtn += ch
|
1168
|
+
str[str.find(ch)] = "" if prms.include? :once
|
1169
|
+
end
|
1170
|
+
return rtn
|
1171
|
+
end
|
1172
|
+
|
1173
|
+
def rand!(cnt=1, *prms) # once is implied
|
1174
|
+
return "" if empty?
|
1175
|
+
return "" if cnt < 1
|
1176
|
+
if length==1
|
1177
|
+
return first!
|
1178
|
+
end
|
1179
|
+
rnd = Random.new
|
1180
|
+
rtn = ""
|
1181
|
+
cnt.times do
|
1182
|
+
break if self.empty?
|
1183
|
+
ch = self[rnd.rand(self.length)]
|
1184
|
+
rtn += ch
|
1185
|
+
if prms.include? :set
|
1186
|
+
remove! ch
|
1187
|
+
else
|
1188
|
+
self[self.find(ch)] = ""
|
1189
|
+
end
|
1190
|
+
end
|
1191
|
+
return rtn
|
1192
|
+
end
|
1193
|
+
|
1194
|
+
=begin
|
1195
|
+
def rand(cnt=1, unique=true)
|
1196
|
+
return "" if empty?
|
1197
|
+
return "" if cnt < 1
|
1198
|
+
return self if length==1
|
1199
|
+
cnt = cnt >= length ? length : cnt
|
1200
|
+
if unique
|
1201
|
+
sh = shuffle
|
1202
|
+
return sh[0..(cnt-1)]
|
1203
|
+
else
|
1204
|
+
rr = Random.new
|
1205
|
+
rtn = ""
|
1206
|
+
cnt.times do
|
1207
|
+
rtn += self[rr.rand(length)]
|
1208
|
+
end
|
1209
|
+
return rtn
|
1210
|
+
end
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
def rand!(cnt=1)
|
1214
|
+
return "" if empty?
|
1215
|
+
return "" if cnt < 1
|
1216
|
+
return first! if length==1
|
1217
|
+
cnt = cnt >= length ? length : cnt
|
1218
|
+
if length==cnt
|
1219
|
+
rtn = shuffle
|
1220
|
+
zap!
|
1221
|
+
return rtn
|
1222
|
+
end
|
1223
|
+
ary = (0..(length-1)).to_a.shuffle
|
1224
|
+
return extract! ary[0..(cnt-1)] # return is not randomized ... why ???
|
1225
|
+
end
|
1226
|
+
=end
|
1227
|
+
|
1228
|
+
# default char for blank? or new mode
|
1229
|
+
def access_mode(md=:stop, dflt=' ') # :swing :stick :default :rotate
|
1230
|
+
@gs_access_mode = md
|
1231
|
+
@gs_default_str = dflt
|
1232
|
+
@gs_np_pos = nil
|
1233
|
+
@gs_dir = :up
|
1234
|
+
self
|
1235
|
+
end
|
1236
|
+
|
1237
|
+
def next
|
1238
|
+
@gs_np_pos ||= nil
|
1239
|
+
@gs_access_mode ||= :stop
|
1240
|
+
@gs_dir ||= :up
|
1241
|
+
return "" if empty?
|
1242
|
+
if @gs_np_pos.nil?
|
1243
|
+
ch = first
|
1244
|
+
@gs_np_pos = 0
|
1245
|
+
return ch
|
1246
|
+
else
|
1247
|
+
case @gs_access_mode
|
1248
|
+
when :stop
|
1249
|
+
@gs_np_pos = @gs_np_pos >= length ? length : @gs_np_pos + 1
|
1250
|
+
ch = self[@gs_np_pos]
|
1251
|
+
return ch.nil? ? "" : ch
|
1252
|
+
when :rotate
|
1253
|
+
@gs_np_pos += 1
|
1254
|
+
@gs_np_pos = @gs_np_pos >= length ? 0 : @gs_np_pos
|
1255
|
+
return self[@gs_np_pos]
|
1256
|
+
when :stick
|
1257
|
+
@gs_np_pos = @gs_np_pos >= length ? length : @gs_np_pos + 1
|
1258
|
+
ch = self[@gs_np_pos]
|
1259
|
+
return ch.nil? ? last : ch
|
1260
|
+
when :default
|
1261
|
+
@gs_np_pos = @gs_np_pos >= length ? length : @gs_np_pos + 1
|
1262
|
+
ch = self[@gs_np_pos]
|
1263
|
+
return ch.nil? ? @gs_default_str : ch
|
1264
|
+
when :swing
|
1265
|
+
return first if length==1
|
1266
|
+
if @gs_dir== :up
|
1267
|
+
@gs_np_pos += 1
|
1268
|
+
if @gs_np_pos >= length
|
1269
|
+
@gs_dir= :down
|
1270
|
+
@gs_np_pos = length - 2
|
1271
|
+
end
|
1272
|
+
return self[@gs_np_pos]
|
1273
|
+
else # :down
|
1274
|
+
@gs_np_pos -= 1
|
1275
|
+
if @gs_np_pos < 0
|
1276
|
+
@gs_dir= :up
|
1277
|
+
@gs_np_pos = 1
|
1278
|
+
end
|
1279
|
+
return self[@gs_np_pos]
|
1280
|
+
end
|
1281
|
+
else
|
1282
|
+
raise "unsupported access mode"
|
1283
|
+
end
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
def prev
|
1288
|
+
@gs_np_pos ||= nil
|
1289
|
+
@gs_access_mode ||= :stop
|
1290
|
+
@gs_dir ||= :up
|
1291
|
+
return "" if empty?
|
1292
|
+
if @gs_np_pos.nil?
|
1293
|
+
ch = last
|
1294
|
+
@gs_np_pos = length - 1
|
1295
|
+
return ch
|
1296
|
+
else
|
1297
|
+
case @gs_access_mode
|
1298
|
+
when :stop
|
1299
|
+
@gs_np_pos = @gs_np_pos <= 0 ? -1 : @gs_np_pos - 1
|
1300
|
+
return "" if @gs_np_pos < 0
|
1301
|
+
return self[@gs_np_pos]
|
1302
|
+
when :rotate
|
1303
|
+
@gs_np_pos -= 1
|
1304
|
+
@gs_np_pos = @gs_np_pos < 0 ? length-1 : @gs_np_pos
|
1305
|
+
return self[@gs_np_pos]
|
1306
|
+
when :stick
|
1307
|
+
@gs_np_pos = @gs_np_pos <= 0 ? -1 : @gs_np_pos - 1
|
1308
|
+
return first if @gs_np_pos < 0
|
1309
|
+
return self[@gs_np_pos]
|
1310
|
+
when :default
|
1311
|
+
@gs_np_pos = @gs_np_pos <= 0 ? -1 : @gs_np_pos - 1
|
1312
|
+
return @gs_default_str if @gs_np_pos < 0
|
1313
|
+
return self[@gs_np_pos]
|
1314
|
+
when :swing
|
1315
|
+
return first if length==1
|
1316
|
+
if @gs_dir== :down
|
1317
|
+
@gs_np_pos += 1
|
1318
|
+
if @gs_np_pos >= length
|
1319
|
+
@gs_dir= :up
|
1320
|
+
@gs_np_pos = length - 2
|
1321
|
+
end
|
1322
|
+
return self[@gs_np_pos]
|
1323
|
+
else # :up
|
1324
|
+
@gs_np_pos -= 1
|
1325
|
+
if @gs_np_pos < 0
|
1326
|
+
@gs_dir= :down
|
1327
|
+
@gs_np_pos = 1
|
1328
|
+
end
|
1329
|
+
return self[@gs_np_pos]
|
1330
|
+
end
|
1331
|
+
else
|
1332
|
+
raise "unsupported access mode"
|
1333
|
+
end
|
1334
|
+
end
|
1335
|
+
end
|
1336
|
+
|
1337
|
+
end # String
|
1338
|
+
|
1339
|
+
class Integer
|
1340
|
+
def to_num
|
1341
|
+
self
|
1342
|
+
end
|
1343
|
+
def to_scd(dp=0, delim = ',.')
|
1344
|
+
return self.to_f.to_scd(dp, delim) unless dp.zero?
|
1345
|
+
str = self.to_s.reverse.scan(/\d{1,3}/).join(delim.first).reverse
|
1346
|
+
(self < 0) ? "-" + str : str
|
1347
|
+
end
|
1348
|
+
def to_eng(pa=6, unit=nil)
|
1349
|
+
return self.to_f.to_eng(pa, unit)
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
class Float
|
1354
|
+
def to_num
|
1355
|
+
self
|
1356
|
+
end
|
1357
|
+
def to_scd(dp=2, delim = ',.')
|
1358
|
+
num = ((10 ** dp) * self).round.to_s
|
1359
|
+
man = num[0..(-dp-1)]
|
1360
|
+
man = man.empty? ? '0' : man
|
1361
|
+
frt = (num[(-dp)..(-1)] || "").padto(dp,'0',:right)
|
1362
|
+
return man.reverse.scan(/\d{1,3}/).join(delim.first).reverse + delim.last + frt
|
1363
|
+
end
|
1364
|
+
def to_eng(pa=6, unit=nil)
|
1365
|
+
pa = pa.to_i
|
1366
|
+
pa = (pa<1) ? 1 : (pa>15) ? 15 : pa
|
1367
|
+
if self < 0.0
|
1368
|
+
num = -self
|
1369
|
+
sgn = "-"
|
1370
|
+
else
|
1371
|
+
num = self
|
1372
|
+
sgn = ""
|
1373
|
+
end
|
1374
|
+
str = "%.16e" % num
|
1375
|
+
str.extract!(1..1) # remove decimal point
|
1376
|
+
num = str.parse('e')
|
1377
|
+
exp = str.to_i
|
1378
|
+
pos = (exp%3)+1
|
1379
|
+
if (exp < 0)
|
1380
|
+
esgn = "-"
|
1381
|
+
ee = -(exp/3)*3
|
1382
|
+
else
|
1383
|
+
esgn = ""
|
1384
|
+
ee = (exp/3)*3
|
1385
|
+
end
|
1386
|
+
pd = pos > pa ? pos : pa
|
1387
|
+
num = (num[0..pa].to_f/10).round.to_s[0..(pa-1)].padto(pd,'0',:right) # round to target size
|
1388
|
+
num.insert(pos, '.') unless pos >= (num.length)
|
1389
|
+
pfx = String::SI_UNIT_PREFIXES["#{esgn}#{ee}".to_i]
|
1390
|
+
unless unit.nil?
|
1391
|
+
unless pfx.nil?
|
1392
|
+
num += pfx
|
1393
|
+
ee=0 # disable 'e' thing
|
1394
|
+
end
|
1395
|
+
end
|
1396
|
+
unit ||= ""
|
1397
|
+
if (ee>0)
|
1398
|
+
num += "e#{esgn}#{ee}"
|
1399
|
+
end
|
1400
|
+
return sgn+num+unit
|
1401
|
+
end
|
1402
|
+
end
|
1403
|
+
|