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.
@@ -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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in gstring.gemspec
4
+ gemspec
@@ -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.
@@ -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!
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -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
@@ -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
+