base_convert 3.0.191214 → 5.0.210126

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 143b58ce952648fedb71d60c1b428438820b9b39b47c2cb58a54efa06695b6f3
4
- data.tar.gz: 78917a3080a0cdb82dfd2cc626e958b7f988b38da047dba56ca7ba306279238b
3
+ metadata.gz: a70b9639fe64e9243968dfd0b74efe7656c529bccde17ceb2fead9c3165e1c68
4
+ data.tar.gz: 4b4e8bf5f5560b2c2c638db7f925f9cd69a5637311225c5a272e63bc6dd66a53
5
5
  SHA512:
6
- metadata.gz: bb733ffdba99c28b82cefca3150f7c08768b1e11f0385cdcfc8be9e9f2865546baf956a97b9da9fa4685599ab449990ccee485414f22f81580ba2ef6049807ee
7
- data.tar.gz: 95d9a9dc8764a6ce432facbc7917760ee2d94df31ea066408ec6c45b978abd5e3a6cfefd3a43ca193f05ea2dbea802814f95ad6b6701c5e4001acce6122abe7e
6
+ metadata.gz: '00827b732310b9117da09b021619895b93b476e1b4818ce9d8a6acfe0b65647d83b3bd6512e4e2c6a22f75299bde139d4448aa3c467ce2e60b390c37124fbdb9'
7
+ data.tar.gz: 14de39dc7f6fb4df71ab111b2ea8266383ed69e7787d87881047ed93eb0ba0f9a930721cfa13b8b0e40b3c47091ec4d0f67779f30df58699c7739ad4eb6f7e9c
data/README.md CHANGED
@@ -1,159 +1,240 @@
1
1
  # BaseConvert
2
2
 
3
+ * [VERSION 5.0.210126](https://github.com/carlosjhr64/base_convert/releases)
3
4
  * [github](https://www.github.com/carlosjhr64/base_convert)
4
5
  * [rubygems](https://rubygems.org/gems/base_convert)
5
6
 
7
+ ## INSTALL:
8
+
9
+ ```shell
10
+ $ gem install base_convert
11
+ ```
12
+
6
13
  ## DESCRIPTION:
7
14
 
8
15
  BaseConvert - Number base conversion.
9
16
 
10
17
  Converts positive integers to different bases:
11
18
  Binary, octal, hexadecimal, decimal, or any arbitrary base.
12
- "Out of the box" handling of up to base 94.
19
+ "Out of the box" handling of up to base 95(:print: characters).
13
20
  Allows for arbitrary choice of alphabet(digits).
14
21
 
15
22
  See also rosettacode.org's [Non-decimal radices convert](http://rosettacode.org/wiki/Non-decimal_radices/Convert).
16
23
 
17
24
  ## SYNOPSIS:
18
25
 
19
- require 'base_convert'
26
+ ```ruby
27
+ require 'base_convert'
20
28
 
21
- #toi string, base, digits #=> integer
22
- BaseConvert.toi 'FF', 16, '0123456789ABCDEF' #=> 255
29
+ #toi string, base, digits => integer
30
+ BaseConvert.toi 'FF', 16, '0123456789ABCDEF' #=> 255
23
31
 
24
- #tob integer, base, digits #=> string
25
- BaseConvert.tob 255, 16, '0123456789ABCDEF' #=> "FF"
32
+ #tos integer, base, digits => string
33
+ BaseConvert.tos 255, 16, '0123456789ABCDEF' #=> "FF"
26
34
 
27
- # FromTo
28
- c = BaseConvert::FromTo.new base: 16, digits: '0123456789ABCDEF', to_base: 7, to_digits: 'abcdefg'
29
- c['FFF'] #=> "begea"
30
-
31
- # Number
32
- n = BaseConvert::Number.new 'FF', base: 16, digits: '0123456789ABCDEF'
33
- n.to_i #=> 255
34
- n.to_s #=> "FF"
35
- #
36
- n = n.to_base 64, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
37
- n.to_s #=> "D/"
38
- n.to_i #=> 255
39
-
40
- ## INSTALL:
35
+ # FromTo
36
+ c = BaseConvert::FromTo.new base: 16, digits: '0123456789ABCDEF', to_base: 7, to_digits: 'abcdefg'
37
+ c['FFF'] #=> "begea"
38
+ c.inspect #=> "16:P95,7:abfg"
41
39
 
42
- $ gem install base_convert
40
+ # Number
41
+ n = BaseConvert::Number.new 'FF', base: 16, digits: '0123456789ABCDEF'
42
+ n.to_i #=> 255
43
+ n.to_s #=> "FF"
44
+ n.inspect #=> FF 16:P95
45
+ #
46
+ n = n.to_base 64, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
47
+ n.to_s #=> "D/"
48
+ n.to_i #=> 255
49
+ n.inspect #=> D/ 64:B64
50
+ ```
43
51
 
44
52
  ## BUT WAIT, THERE'S MORE:
45
53
 
46
- Using `irb` to demonstrate the features.
47
- The components are scoped under `BaseConvert`:
48
-
49
- > irb
50
- Welcome to IRB...
51
- >> require 'base_convert' #=> true
52
- >> include BaseConvert #=> Object
53
-
54
- `base_convert` provides three ways to convert a string representation of a number.
55
- The first is functional. One can extend(import) the functions that do the conversions.
56
- The conversion functions are `toi` and `tob`.
57
- For example, the octal number "7777":
58
-
59
- extend BaseConvert #=> main
60
- digits = '01234567'
61
- base = digits.length #=> 8
62
- toi('7777', base, digits) #=> 4095
63
- tob(4095, base, digits) #=> "7777"
64
-
65
- You can work with arbitrary digits:
66
-
67
- digits = ')!@#$%^&'
68
- base = digits.length #=> 8
69
- toi('&&&&', base, digits) #=> 4095
70
- tob(4095, base, digits) #=> "&&&&"
71
-
72
- For convenience, `base_convert` provides under `module BaseConvert::Configuration` some predefined sets of digits.
73
-
74
- * `GRAPH` are the ASCII graph characters.
75
- * `QGRAPH` are the ASCII graph characters except quotes: double-quote, single-quote, and back-tick.
76
- * `BASE64` is the standard base 64 digits from people with no sense of order.
77
- * `WORD_` are the ASCII word characters including underscore(`_`).
78
- * `WORD` are the ASCII word characters except underscore(`_`).
79
- * `UNAMBIGUOUS` are the characters in `WORD` without the `AMBIGUOUS` characters(B8G6I1l0OQDS5Z2).
80
-
81
- Some examples:
82
-
83
- Configuration::UNAMBIGUOUS #=> "3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz"
84
- # etc...
85
- tob 255, 16, Configuration::WORD #=> "FF"
86
- include Configuration
87
- tob 255, 64, BASE64 #=> "D/"
88
-
89
- The second way to convert is via a conversion object of `BaseConvert::FromTo`.
90
- For example, to convert from hexadecimal to octal, and back:
91
-
92
- h2o = FromTo.new base: 16, to_base: 8
93
- o2h = FromTo.new base: 8, to_base: 16
94
- h2o['FFFF'] #=> "177777"
95
- o2h['177777'] #=> "FFFF"
96
-
97
- The third way to work with variant base and digits numbers is via the `BaseConvert::Number`:
98
-
99
- hexadecimal = Number.new('FFFF', base: 16, digits: WORD)
100
- hexadecimal.to_s #=> "FFFF"
101
- hexadecimal.to_i #=> 65535
102
-
103
- # Number will infer your most likely meaning:
104
- Number.new('FF', 16).to_i #=> 255
105
-
106
- # And given a string of at least length 8,
107
- # it'll go ahead and guess at your meaning:
108
- Number.new('FFFFFFFF').to_i #=> 4294967295
109
-
110
- # But best practice is to fully specify,
111
- # which is easy to do with keys:
112
- n = Number.new 'F', base: :hex, digits: :word
113
- n.to_i #=> 15
114
- n.to_s #=> "F"
115
-
116
- # One can make a change of digits:
117
- n = n.to_digits '0123456789abcdef'
118
- n.to_s #=> "f"
119
- n.to_i #=> 15
120
-
121
- # One can make of change of base:
122
- n = n.to_base 8
123
- n.to_s #=> "17"
124
-
125
- # One can make of change of base and digits:
126
- n = n.to_base 32, :base64
127
- # or vice-versa
128
- n = n.to_digits :base64, 32
129
- n.to_s #=> "P"
130
-
131
- ## Keys (Symbols)
132
-
133
- Instead of giving the base number or the digits' string,
134
- one can use a mnemonic key:
135
-
136
- | long key | short key | DIGITS | BASE NUMBER |
137
- | -------------- | --------- | ------------- | ----------- |
138
- | `:graph` | `:g` | `GRAPH` | 94 |
139
- | `:qgraph` | `:q` | `QGRAPH` | 91 |
140
- | `:base64` | `:b64` | `BASE64` | 64 |
141
- | `:word_` | `:w_` | `WORD_` | 63 |
142
- | `:word` | `:w` | `WORD` | 62 |
143
- | `:unambiguous` | `:u` | `UNAMBIGUOUS` | 47 |
144
-
145
- | long key | short keys | BASE NUMBER |
146
- | -------------- | ---------- | ----------- |
147
- | `:hexadecimal` | `:hex, :h` | 16 |
148
- | `:decimal` | `:dec, :d` | 10 |
149
- | `:octal` | `:oct, :o` | 8 |
150
- | `:binary` | `:bin, :b` | 2 |
54
+ ### module BaseConvert
55
+
56
+ * `#toi(string=to_s String, base=@base Integer, digits=@digits String) #=> Integer`
57
+ * `#tos(integer=to_i Integer, base=@base Integer, digits=@digits String) #=> String`
58
+ * `#ascii_ordered?(digits=@digits String) #=> TrueClass|FalseClass`
59
+
60
+ Exemplar:
61
+
62
+ ```ruby
63
+ class MyClass
64
+ include BaseConvert
65
+ attr_accessor :to_s, :to_i, :base, :digits
66
+ end
67
+
68
+ obj = MyClass.new
69
+ obj.digits = '!@#$%^&*()'
70
+ obj.base = 10
71
+
72
+ obj.to_s = '@'
73
+ obj.toi #=> 1
74
+
75
+ obj.to_i = 3
76
+ obj.tos #=> "$"
77
+
78
+ obj.ascii_ordered? #=> false
79
+ obj.digits = 'ABCDEFGHIJKLMNOP'
80
+ obj.ascii_ordered? #=> true
81
+ ```
82
+
83
+ ### Hash DIGITS
84
+
85
+ #### DIGITS methods
86
+
87
+ * `DIGITS.get(key Symbol) #=> String|Symbol|NilClass`
88
+ * `DIGITS.registry(digits=nil NilClass|String) #=> Array(Symbol)|Symbol`
89
+ * `DIGITS.label(digits String) #=> String`
90
+ * `DIGITS.memoize!(key=registry Symbol|Array(Symbol))`
91
+ * `DIGITS.forget!(key=registry Symbol|Array(Symbol))`
92
+
93
+
94
+ Exemplar:
95
+
96
+ ```ruby
97
+ include BaseConvert
98
+ DIGITS.get(:P95) #=> :alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers
99
+ DIGITS[:P95]
100
+ #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%\"'`_ "
101
+ DIGITS.registry #=> [:P95, :B64, :U47, :G94, :Q91, :W63]
102
+ DIGITS.registry('347') #=> :U47
103
+ DIGITS.registry('0') #=> :P95
104
+ DIGITS.registry('AB') #=> :B64
105
+ DIGITS.registry('Cukoe') #=> nil
106
+ DIGITS.label('Cukoe') #=> :Cuoe
107
+ DIGITS.label('AaBbCcXxYyZz') #=> :AaZz
108
+ DIGITS[:N] #=> "0123456789"
109
+ DIGITS.get(:N) #=> nil
110
+ DIGITS.memoize!(:N)
111
+ DIGITS.get(:N) #=> "0123456789"
112
+ DIGITS.forget!(:N)
113
+ DIGITS.get(:N) #=> nil
114
+ ```
115
+
116
+
117
+ #### DIGITS constructions
118
+
119
+ `BaseConvert::DIGITS` will take a `Symbol` representation of `Regexp` patterns.
120
+ See [Ruby-Doc's Regexp](https://ruby-doc.org/core-2.7.0/Regexp.html) documentation
121
+ for a full list of keys. The following provides an exemplar survey:
122
+
123
+ ```ruby
124
+ # Character Classes
125
+ # Selected from ASCII 32..126
126
+ DIGITS[:w] #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"
127
+ DIGITS[:d] #=> "0123456789"
128
+ # Note: :h was overridden, see :xdigit.
129
+ DIGITS[:h] #=> "0123456789ABCDEF"
130
+ DIGITS[:alpha] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
131
+ DIGITS[:graph]
132
+ #=> "!\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
133
+ DIGITS[:lower] #=> "abcdefghijklmnopqrstuvwxyz"
134
+ DIGITS[:punct] #=> "!\"\#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
135
+ DIGITS[:upper] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
136
+ DIGITS[:xdigit] #=> "0123456789ABCDEFabcdef"
137
+
138
+ # Character Properties
139
+ # Selected from ASCII 32..126
140
+ DIGITS[:Alnum] #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
141
+ DIGITS[:Any]
142
+ #=> " !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
143
+
144
+ # General Category
145
+ # Selected from ASCII 32..126
146
+ DIGITS[:Ps] #=> "([{"
147
+ DIGITS[:Pe] #=> ")]}"
148
+ DIGITS[:S] #=> "$+<=>^`|~"
149
+
150
+ # Ranged Selections
151
+ # v<hex>w<hex>_<filter>
152
+ DIGITS[:v1d7d8w1d7e1_Any] #=> "𝟘𝟙𝟚𝟛𝟜𝟝𝟞𝟟𝟠𝟡"
153
+ # i<dec>j<dec>_<filter>
154
+ DIGITS[:i120488j120513_Any] #=> "𝚨𝚩𝚪𝚫𝚬𝚭𝚮𝚯𝚰𝚱𝚲𝚳𝚴𝚵𝚶𝚷𝚸𝚹𝚺𝚻𝚼𝚽𝚾𝚿𝛀𝛁"
155
+
156
+ # Specified Characters
157
+ # u<hex>
158
+ DIGITS[:u61u62] #=> "ab"
159
+ # k<dec>
160
+ DIGITS[:k97k98] #=> "ab"
161
+
162
+ # BaseConvert's Custom Sets
163
+ DIGITS[:bangs] #=> "!?"
164
+ DIGITS[:typers] #=> "$&@"
165
+ DIGITS[:operators] #=> "*+-/<=>^~"
166
+ DIGITS[:separators] #=> ",.:;|"
167
+ DIGITS[:scapes] #=> "#\\"
168
+ DIGITS[:groupers] #=> "()[]{}"
169
+ DIGITS[:quotes] #=> "\"'`"
170
+ DIGITS[:quoters] #=> "%\"'`"
171
+ DIGITS[:spacers] #=> "_ "
172
+ DIGITS[:ambiguous] #=> "012568BDGIOQSZl"
173
+
174
+ # Composition, add merge:
175
+ DIGITS[:d_ambiguous] #=> "0123456789BDGIOQSZl"
176
+ # Composition, add top:
177
+ DIGITS[:'d+ambiguous'] #=> "3479012568BDGIOQSZl"
178
+ # Composition, subtract:
179
+ DIGITS[:'d-ambiguous'] #=> "3479"
180
+
181
+ # Compositions used in BaseConvert
182
+ # :P95 is:
183
+ DIGITS[:alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers]
184
+ #=> "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%\"'`_ "
185
+ # :B64 is:
186
+ DIGITS[:LN_k43k47] #=> "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
187
+ # :U47 is:
188
+ DIGITS[:'alnum-ambiguous'] #=> "3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz"
189
+ ```
190
+
191
+ ### class FromTo
192
+
193
+ * `new(base: 10 Integer|Symbol|String, to_base: base, digits: :P95 String|Symbol|Integer, to_digits: digits) #=> FromTo`
194
+ * `#inspect #=> String`
195
+ * `#convert(counter String|Integer) #=> String`
196
+
197
+ Example:
198
+
199
+ ```ruby
200
+ h2b = BaseConvert::FromTo.new(base: 16, digits: :P95, to_base: 64, to_digits: :B64)
201
+ h2b #=> 16:P95,64:B64
202
+ h2b['FFF'] #=> "//"
203
+ b2h = BaseConvert::FromTo.new(base: 64, digits: :B64, to_base: 16, to_digits: :P95)
204
+ b2h #=> 64:B64,16:P95
205
+ b2h['//'] #=> "FFF"
206
+ ```
207
+
208
+ ### class Number
209
+
210
+ * `new(counter= 0 Integer|String, base: nil Integer|Symbol|String, digits: nil String|Symbol|Integer, validate: true TrueClass|FalseClass) #=> Number`
211
+ * `#base #=> Integer`
212
+ * `#digits #=> String`
213
+ * `#inspect #=> String`
214
+ * `#to_s #=> String`
215
+ * `#to_i #=> Integer`
216
+ * `#to_base(base Integer|Symbol|String, digits=@digits String|Symbol|Integer, validate=@validate TrueClass|FalseClass) #=> Number`
217
+ * `#to_digits(digits String|Symbol|Integer, base=@base Integer|Symbol|String, validate=@validate TrueClass|FalseClass) #=> Number`
218
+
219
+ Example:
220
+
221
+ ```ruby
222
+ a = BaseConvert::Number.new('FFF', base: 16, digits: :P95)
223
+ a #=> FFF 16:P95
224
+ a.to_i #=> 4095
225
+ b = a.to_digits(:U47)
226
+ b #=> RRR 16:U47
227
+ b.to_i #=> 4095
228
+ c = b.to_base(64, :B64)
229
+ c #=> // 64:B64
230
+ c.to_i #=> 4095
231
+ ```
151
232
 
152
233
  ## LICENSE:
153
234
 
154
235
  (The MIT License)
155
236
 
156
- Copyright (c) 2014 CarlosJHR64
237
+ Copyright (c) 2021 CarlosJHR64
157
238
 
158
239
  Permission is hereby granted, free of charge, to any person obtaining
159
240
  a copy of this software and associated documentation files (the
@@ -1,30 +1,11 @@
1
- # http://rosettacode.org/wiki/Non-decimal_radices/Convert#Ruby
2
1
  module BaseConvert
3
- VERSION = '3.0.191214'
4
-
5
- def toi(string=@string, base=@base, digits=@digits)
6
- integer = 0
7
- string.each_char do |c|
8
- index = digits.index(c)
9
- integer = integer * base + index
10
- end
11
- integer
12
- end
13
-
14
- def tob(integer=@integer, base=@base, digits=@digits)
15
- return digits[0] if integer == 0
16
- string = ''
17
- while integer > 0
18
- integer, index = integer.divmod(base)
19
- string = string.insert(0, digits[index])
20
- end
21
- string
22
- end
23
-
24
- extend self
25
-
26
- autoload :Configuration, 'base_convert/configuration'
27
- autoload :FromTo, 'base_convert/from_to'
28
- autoload :Number, 'base_convert/number'
2
+ VERSION = '5.0.210126'
3
+ require 'base_convert/base_convert'
4
+ require 'base_convert/chars'
5
+ require 'base_convert/digits'
6
+ require 'base_convert/base'
7
+ require 'base_convert/configuration'
8
+ require 'base_convert/from_to'
9
+ require 'base_convert/number'
29
10
  end
30
11
  #`ruby`
@@ -0,0 +1,24 @@
1
+ module BaseConvert
2
+ class Base < Hash
3
+ alias :get :[]
4
+ def [](key)
5
+ base = super and return base
6
+ case key
7
+ when String
8
+ base = key.length
9
+ when Integer
10
+ base = key
11
+ when /^\D+(\d+)$/
12
+ base = $1.to_i
13
+ else
14
+ begin
15
+ base = DIGITS[key].length
16
+ rescue
17
+ raise 'unrecognized base key'
18
+ end
19
+ end
20
+ raise 'base must be greater than 1' unless base > 1
21
+ base
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ # http://rosettacode.org/wiki/Non-decimal_radices/Convert#Ruby
2
+ module BaseConvert
3
+ def toi(string=to_s, base=@base, digits=@digits)
4
+ return nil if string.empty?
5
+ integer = 0
6
+ string.each_char do |c|
7
+ index = digits.index(c)
8
+ integer = integer * base + index
9
+ end
10
+ integer
11
+ end
12
+
13
+ def tos(integer=to_i, base=@base, digits=@digits)
14
+ return '' if integer.nil?
15
+ return digits[0] if integer == 0
16
+ string = ''
17
+ while integer > 0
18
+ integer, index = integer.divmod(base)
19
+ string = string.prepend digits[index]
20
+ end
21
+ string
22
+ end
23
+
24
+ def ascii_ordered?(digits=@digits)
25
+ (1..(digits.length-1)).all?{|i|digits[i-1]<digits[i]}
26
+ end
27
+
28
+ extend self
29
+ end
@@ -0,0 +1,59 @@
1
+ module BaseConvert
2
+ class Chars < Array
3
+ attr_accessor :start,:stop
4
+ def initialize(start=32, stop=126)
5
+ @start,@stop = start,stop
6
+ end
7
+
8
+ # i<n>: @start=n.to_i
9
+ # v<n>: @start=n.to_i(16)
10
+ # j<n>: @stop=n.to_i
11
+ # w<n>: @stop=n.to_i(16)
12
+ def set(s)
13
+ t,n = s[0],s[1..-1]
14
+ case t
15
+ when 'i','v'
16
+ @start = n.to_i((t=='v')? 16 : 10)
17
+ when 'j','w'
18
+ @stop = n.to_i((t=='w')? 16 : 10)
19
+ else
20
+ raise 'expected /^([ij]\d+)|([vw]\h+)$/'
21
+ end
22
+ end
23
+
24
+ def chars_in(x)
25
+ case x
26
+ when Regexp
27
+ @start.upto(@stop).each do |l|
28
+ c = l.chr(Encoding::UTF_8)
29
+ yield c if x.match? c
30
+ end
31
+ when Symbol
32
+ yield x[1..-1].to_i((x[0]=='u')? 16: 10).chr(Encoding::UTF_8)
33
+ when String
34
+ x.chars.each{|c| yield c}
35
+ when Integer
36
+ yield x.chr(Encoding::UTF_8)
37
+ else
38
+ raise "expected Regexp|Symbol|String|Integer, got #{x.class}"
39
+ end
40
+ end
41
+
42
+ def add(x)
43
+ chars_in(x) do |c|
44
+ self.push(c) unless self.include?(c)
45
+ end
46
+ end
47
+
48
+ def top(x)
49
+ chars_in(x) do |c|
50
+ self.delete(c)
51
+ self.push(c)
52
+ end
53
+ end
54
+
55
+ def remove(x)
56
+ chars_in(x){|c| self.delete(c)}
57
+ end
58
+ end
59
+ end
@@ -1,60 +1,88 @@
1
1
  module BaseConvert
2
- module Configuration
3
-
4
- GRAPH = 0.upto(255).map{|i| i.chr}.select{|c| c=~/[[:graph:]]/}.join.freeze
5
- QGRAPH = GRAPH.delete(%('"`)).freeze
6
-
7
- WORD_ = 0.upto(255).map{|i| i.chr}.select{|c| c=~/\w/}.join.freeze
8
- WORD = WORD_.delete('_').freeze
9
- INDEXa = WORD.index('a')
10
-
11
- AMBIGUOUS = 'B8G6I1l0OQDS5Z2'.freeze
12
- UNAMBIGUOUS = WORD.delete(AMBIGUOUS).freeze
13
-
14
- BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
15
-
16
- BASE = {
17
- :graph => GRAPH.length,
18
- :qgraph => QGRAPH.length,
19
- :word_ => WORD_.length,
20
- :word => WORD.length,
21
- :unambiguous => UNAMBIGUOUS.length,
22
- :base64 => 64,
23
- :b64 => 64,
24
- :hexadecimal => 16,
25
- :hex => 16,
26
- :h => 16,
27
- :decimal => 10,
28
- :dec => 10,
29
- :d => 10,
30
- :octal => 8,
31
- :oct => 8,
32
- :o => 8,
33
- :binary => 2,
34
- :bin => 2,
35
- :b => 2,
36
- }
37
-
38
- BASE[:g] = BASE[:graph]
39
- BASE[:q] = BASE[:qgraph]
40
- BASE[:w_] = BASE[:word_]
41
- BASE[:w] = BASE[:word]
42
- BASE[:u] = BASE[:unambiguous]
43
-
44
- DIGITS = {
45
- :graph => GRAPH,
46
- :g => GRAPH,
47
- :qgraph => QGRAPH,
48
- :q => QGRAPH,
49
- :word_ => WORD_,
50
- :w_ => WORD_,
51
- :word => WORD,
52
- :w => WORD,
53
- :unambiguous => UNAMBIGUOUS,
54
- :u => UNAMBIGUOUS,
55
- :base64 => BASE64,
56
- :b64 => BASE64,
57
- }
2
+ DIGITS = Digits.new
58
3
 
59
- end
4
+ # Naming these letter sequences is inpired by
5
+ # (but not the same as)
6
+ # Unicode character’s General Category.
7
+ DIGITS[:bangs] = '!?'.freeze # Used as method name suffix
8
+ DIGITS[:typers] = '$&@'.freeze # Used as variable name prefix
9
+ DIGITS[:operators] = '*+-/<=>^~'.freeze # Used as mathematical operators
10
+ DIGITS[:separators] = ',.:;|'.freeze # Used to separated items
11
+ DIGITS[:scapes] = '#\\'.freeze # Used to escape what's next
12
+ DIGITS[:groupers] = '()[]{}'.freeze # Used to group items
13
+ DIGITS[:quotes] = %("'`).freeze # Quotes
14
+ DIGITS[:quoters] = %(\%"'`).freeze # Used to quote strings('%' not ASCII ordered)
15
+ DIGITS[:spacers] = '_ '.freeze # 1_000 == 1000 #=> true (Not ASCII ordered)
16
+ DIGITS[:ambiguous] = '012568BDGIOQSZl'.freeze # ASCII ordered ambiguous characters
17
+
18
+ ### Recursive string constructors ###
19
+ # 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?$&@*+-/<=>^~,.:;|#\\()[]{}%"'`_
20
+ DIGITS[:P95] = :alnum_bangs_typers_operators_separators_scapes_groupers_quoters_spacers
21
+ INDEXa = DIGITS[:P95].index('a')
22
+ # 0123456789ABCDEF
23
+ DIGITS[:hexadecimal] = DIGITS[:hex] = DIGITS[:h] = :P16
24
+ # 0123456789
25
+ DIGITS[:decimal] = DIGITS[:dec] = :P10
26
+ # 01234567
27
+ DIGITS[:octal] = DIGITS[:oct] = DIGITS[:o] = :P8
28
+ # 01
29
+ DIGITS[:b] = DIGITS[:bin] = DIGITS[:binary] = :P2
30
+
31
+ # !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
32
+ DIGITS[:G94] = DIGITS[:g] = :graph
33
+
34
+ # !#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_abcdefghijklmnopqrstuvwxyz{|}~
35
+ DIGITS[:Q91] = DIGITS[:qgraph] = DIGITS[:q] = :'graph-quotes'
36
+
37
+ # ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
38
+ DIGITS[:base64] = DIGITS[:b64] = DIGITS[:B64] = :LN_k43k47
39
+ DIGITS[:letters] = DIGITS[:l] = :B52 # subset of B64
40
+
41
+ # 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz
42
+ DIGITS[:word] = DIGITS[:W63] = :w
43
+
44
+ # 3479ACEFHJKLMNPRTUVWXYabcdefghijkmnopqrstuvwxyz
45
+ DIGITS[:unambiguous] = DIGITS[:U47] = DIGITS[:u] = :'alnum-ambiguous'
46
+
47
+ BASE = Base[
48
+ # 95
49
+ P95: 95,
50
+ print: 95,
51
+ # 94
52
+ G94: 94, g: 94, graph: 94,
53
+ # 91
54
+ Q91: 91, qgraph: 91, q: 91,
55
+ # 64
56
+ B64: 64, base64: 64, b64: 64,
57
+ # 63
58
+ W63: 63, word: 63, w: 63,
59
+ # 52
60
+ letters: 52, l: 52, L: 52,
61
+ # 47
62
+ U47: 47, unambiguous: 47, u: 47,
63
+ # 16
64
+ hexadecimal: 16, hex: 16, h: 16,
65
+ # 15
66
+ ambiguous: 15,
67
+ # 10
68
+ decimal: 10, dec: 10, d: 10,
69
+ # 9
70
+ operators: 9,
71
+ # 8
72
+ octal: 8, oct: 8, o: 8,
73
+ # 6
74
+ groupers: 6,
75
+ # 5
76
+ separators: 5,
77
+ # 4
78
+ quoters: 4,
79
+ # 3
80
+ typers: 3,
81
+ quotes: 3,
82
+ # 2
83
+ binary: 2, bin: 2, b: 2,
84
+ bangs: 2,
85
+ scapes: 2,
86
+ spacers: 2,
87
+ ]
60
88
  end
@@ -0,0 +1,96 @@
1
+ module BaseConvert
2
+ class Digits < Hash
3
+ alias :get :[]
4
+ def [](key)
5
+ if self.has_key?(key)
6
+ d = super(key)
7
+ return d.is_a?(Symbol)? self[d]: d
8
+ end
9
+ case key
10
+ when Symbol
11
+ chars = Chars.new
12
+ key.to_s.scan(/[+-]?[[:alnum:]]+/).each do |type|
13
+ if self.has_key?(_=type.to_sym)
14
+ chars.add super(_)
15
+ next
16
+ end
17
+ case type
18
+ when /^((u\h+)|(k\d+))+$/
19
+ type.scan(/[uk]\h+/).each{|s| chars.top s.to_sym}
20
+ when /^(([ij]\d+)|([vw]\h+))+$/
21
+ type.scan(/[ijvw]\h+/).each{|s| chars.set s}
22
+ when /^[a-z][a-z]+$/
23
+ chars.add Regexp.new "[[:#{type}:]]"
24
+ when /^[a-z]$/
25
+ chars.add Regexp.new "\\#{type}"
26
+ when /^[A-Z]+$/i
27
+ type.scan(/[A-Z][a-z]*/).each{|property| chars.add /\p{#{property}}/}
28
+ when /^([+-])(\w+)/
29
+ d = self[$2.to_sym]
30
+ case $1
31
+ when '+'
32
+ chars.top d
33
+ when '-'
34
+ chars.remove d
35
+ end
36
+ when /^(\p{L}+)(\d+)$/
37
+ l,m = $1,$2.to_i-1
38
+ n = self.keys.select{|_|_=~/^#{l}\d+$/}.map{|_|_.to_s.sub(l,'').to_i}.max
39
+ raise "no #{l}<n> digits defined" if n.nil?
40
+ raise "out of range of #{l}#{n}" unless m<n
41
+ chars.add self[:"#{l}#{n}"][0..m]
42
+ else
43
+ raise "unrecognized digits key: #{type}"
44
+ end
45
+ end
46
+ return chars.uniq.join.freeze
47
+ when String
48
+ digits = nil # set as a side effect...
49
+ unless registry.detect{|_|(digits=self[_]).start_with? key}
50
+ # ...here -------------->^^^^^
51
+ raise 'need at least 2 digits' unless key.length > 1
52
+ raise 'digits must not have duplicates' if key.length > key.chars.uniq.length
53
+ return key
54
+ end
55
+ return digits
56
+ when Integer
57
+ raise 'need digits to cover base' if key > 95
58
+ return self[:P95] # Defined in configuration.rb
59
+ end
60
+ raise 'digits must be String|Symbol|Integer'
61
+ end
62
+
63
+ def registry(d=nil)
64
+ # BaseConvert::Number memoizes and uses specifically :P95, :B64, and :U47;
65
+ # giving these precedence above the rest. Defined in configuration.rb.
66
+ @registry ||= [:P95, :B64, :U47, :G94, :Q91, :W63]
67
+ d ? @registry.detect{|_|self[_].start_with? d}: @registry
68
+ end
69
+
70
+ def label(d)
71
+ registry(d) or (d[0]+d[1]+d[-2]+d[-1]).to_sym
72
+ end
73
+
74
+ def memoize!(keys=registry)
75
+ [*keys].each do |k|
76
+ while s = get(k)
77
+ break if s.is_a? String # links to a constructed String
78
+ raise 'expected Symbol' unless s.is_a? Symbol
79
+ k = s
80
+ end
81
+ self[k]=self[k] if s.nil? # if not memoized, memoize!
82
+ end
83
+ end
84
+
85
+ def forget!(keys=registry)
86
+ [*keys].each do |k|
87
+ while s = get(k)
88
+ break if s.is_a? String # links to a constructed String
89
+ raise 'expected Symbol' unless s.is_a? Symbol
90
+ k = s
91
+ end
92
+ self.delete(k) if s.is_a? String
93
+ end
94
+ end
95
+ end
96
+ end
@@ -1,25 +1,30 @@
1
1
  module BaseConvert
2
2
  class FromTo
3
- include Configuration
4
3
  include BaseConvert
5
4
 
6
- def initialize(base: 10, to_base: base, digits: WORD, to_digits: digits)
7
- base = BASE[base] if base.is_a? Symbol
8
- to_base = BASE[to_base] if to_base.is_a? Symbol
9
- digits = DIGITS[digits] if digits.is_a? Symbol
10
- to_digits = DIGITS[to_digits] if to_digits.is_a? Symbol
11
- raise "base must cover digits." if base > digits.length or to_base > to_digits.length
5
+ def initialize(base: 10, to_base: base, digits: :P95, to_digits: digits)
6
+ base = BASE[base]
7
+ to_base = BASE[to_base]
8
+ digits = DIGITS[digits]
9
+ to_digits = DIGITS[to_digits]
10
+ raise 'base must cover digits' if base > digits.length or to_base > to_digits.length
12
11
  @base, @to_base, @digits, @to_digits = base, to_base, digits, to_digits
13
12
  end
13
+
14
+ def inspect
15
+ d0 = DIGITS.label(@digits)
16
+ d1 = DIGITS.label(@to_digits)
17
+ "#{@base}:#{d0},#{@to_base}:#{d1}"
18
+ end
14
19
 
15
20
  def convert(counter)
16
21
  case counter
17
22
  when Integer
18
- tob(counter, @to_base, @to_digits)
23
+ tos(counter, @to_base, @to_digits)
19
24
  when String
20
- tob(toi(counter), @to_base, @to_digits)
25
+ tos(toi(counter), @to_base, @to_digits)
21
26
  else
22
- raise "counter must be String|Integer."
27
+ raise 'counter must be String|Integer'
23
28
  end
24
29
  end
25
30
  alias :[] :convert
@@ -1,133 +1,92 @@
1
1
  module BaseConvert
2
2
  class Number
3
- include Configuration
4
3
  include BaseConvert
4
+ DIGITS.memoize!
5
5
 
6
- def _infer_digits_from_string
7
- if @string.chars.all?{|_|WORD_.include?_}
8
- @string.include?('_')? WORD_ : WORD
9
- elsif @string.chars.all?{|_|GRAPH.include?_}
10
- @string.match?(/['"`]/)? GRAPH : QGRAPH
11
- else
12
- raise "Need digits."
13
- end
6
+ def self.infer(string)
7
+ p95 = DIGITS[:P95]
8
+ return 2, p95 if string.empty?
9
+ chars = string.chars
10
+ raise 'need digits to cover string' unless chars.all?{|_|p95.include?_}
11
+ max = chars.map{|_|p95.index(_)}.max
12
+ return 95, p95 if max == 94 # string has a space digit.
13
+ return 2, p95 if max < 2
14
+ return 4, p95 if max < 4
15
+ return 8, p95 if max < 8
16
+ return 10, p95 if max < 10
17
+ return 16, p95 if max < 16
18
+ return 32, p95 if max < 32
19
+ u47 = DIGITS[:U47]
20
+ return 47, u47 if chars.all?{|_|u47.include?_}
21
+ return 64, p95 if max < 64
22
+ b64 = DIGITS[:B64]
23
+ return 64, b64 if chars.all?{|_|b64.include?_}
24
+ return 94, p95
14
25
  end
15
26
 
16
- def _infer_base_from_string
17
- min = 1 + @digits.index(@string.chars.max)
18
- max = @digits.length
19
- return max if max==min
20
- case @digits
21
- when WORD, WORD_
22
- if min <= 32
23
- raise "Need base for WORD or WORD_." if @string.length < 8
24
- return 8 if min <= 8
25
- return 16 if min <= 16
26
- return 32
27
- end
28
- when UNAMBIGUOUS
29
- if min <= 22
30
- raise "Need base for UNAMBIGUOUS." if @string.length < 8
31
- return 22
32
- end
33
- when QGRAPH, GRAPH
34
- n = @digits.index 'a'
35
- if min <= n
36
- raise "Need base for QGRAPH or GRAPH." if @string.length < 8
37
- return n
38
- end
27
+ attr_reader :base, :digits
28
+ def initialize(counter=0, base: nil, digits: nil, validate: true)
29
+ # validate
30
+ case validate
31
+ when true, false
32
+ @validate = validate
33
+ else
34
+ raise 'validate must be either true or false'
39
35
  end
40
- return max
41
- end
42
36
 
43
- def _digits!
44
- if @digits.nil?
45
- @digits = @integer.nil? ? _infer_digits_from_string : WORD
46
- if @base and @base > @digits.length
47
- if @base == 64
48
- @digits = BASE64
49
- elsif @base <= QGRAPH.length
50
- @digits = QGRAPH
51
- elsif @base <= GRAPH.length
52
- @digits = GRAPH
53
- else
54
- raise "Need digits that can cover base #{@base}."
55
- end
56
- end
37
+ # counter
38
+ string = nil
39
+ case counter
40
+ when String
41
+ string = counter
42
+ base, digits = Number.infer(counter) if base.nil? and digits.nil?
43
+ when Integer
44
+ @integer = counter
45
+ base, digits = 10, DIGITS[:P95] if base.nil? and digits.nil?
57
46
  else
58
- if @digits.is_a? Symbol
59
- digits = DIGITS[@digits]
60
- raise "Unrecognized digits #{@digits}." if digits.nil?
61
- @digits = digits
62
- else
63
- raise "digits must be a String of at least length 2." unless @digits.is_a?(String) and @digits.length > 2
64
- end
47
+ raise 'need counter String|Integer'
65
48
  end
66
- end
67
49
 
68
- def _base!
69
- if @base.nil?
70
- _digits!
71
- if @integer.nil?
72
- @base = _infer_base_from_string
73
- else
74
- @base = @digits.length
50
+ # digits
51
+ @digits = DIGITS[digits || base]
52
+
53
+ # base
54
+ base = digits if base.nil? and digits.is_a? Symbol
55
+ @base = BASE[base || @digits.length]
56
+
57
+ # validate
58
+ if @validate
59
+ raise 'digits must cover base' if @base > @digits.length
60
+ unless string.nil? or string.empty?
61
+ indeces = string.chars.map{|_|@digits.index(_)}
62
+ if missing = indeces.any?{|_|_.nil?} or exceeding = indeces.any?{|_|_>=@base}
63
+ if @base <= INDEXa and DIGITS[:P95].start_with?(@digits)
64
+ string = string.upcase
65
+ indeces = string.chars.map{|_|@digits.index(_)}
66
+ missing = indeces.any?{|_|_.nil?} or exceeding = indeces.any?{|_|_>=@base}
67
+ end
68
+ raise 'digits must cover string' if missing
69
+ raise 'digits in string must be under base' if exceeding
70
+ end
75
71
  end
76
- else
77
- if @base.is_a? Symbol
78
- base = BASE[@base]
79
- raise "Unrecognized base #{@base}." if base.nil?
80
- @base = base
81
- else
82
- raise "base must be an Integer greater than 1." unless @base.is_a?(Integer) and @base > 1
72
+ unless @integer.nil?
73
+ raise 'integer can not be negative' if @integer < 0
83
74
  end
84
- _digits!
85
- end
86
- end
87
-
88
- def _validate
89
- raise "digits must cover base." if @base > @digits.length
90
- raise "digits must not have duplicates." if @digits.length > @digits.chars.uniq.length
91
- unless @string.nil?
92
- raise "digits must cover string." unless @string.chars.all?{|_|@digits.include?_}
93
75
  end
94
- unless @integer.nil?
95
- raise "integer can't be negative." if @integer < 0
96
- end
97
- end
98
76
 
99
- def _integer!
100
- _base!
101
- _validate if @validate
102
- @string.upcase! if @base <= INDEXa and @digits == WORD
103
- @integer = toi
77
+ @integer = toi(string) if @integer.nil?
104
78
  end
105
79
 
106
- def _string!
107
- _base!
108
- _validate if @validate
109
- @string = tob
80
+ def inspect
81
+ d = DIGITS.label(@digits)
82
+ "#{to_s} #{@base}:#{d}"
110
83
  end
111
84
 
112
- attr_reader :base, :digits
113
- def initialize(counter, base: nil, digits: nil, validate: true)
114
- @base, @digits, @validate = base, digits, validate
115
- @string, @integer = nil, nil
116
- case counter
117
- when String
118
- @string = counter
119
- _integer!
120
- when Integer
121
- @integer = counter
122
- _string!
123
- else
124
- raise "Need counter String|Integer."
125
- end
85
+ def validate?
86
+ @validate
126
87
  end
127
88
 
128
- def to_s
129
- @string
130
- end
89
+ alias to_s tos
131
90
 
132
91
  def to_i
133
92
  @integer
metadata CHANGED
@@ -1,21 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: base_convert
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.191214
4
+ version: 5.0.210126
5
5
  platform: ruby
6
6
  authors:
7
7
  - carlosjhr64
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-15 00:00:00.000000000 Z
11
+ date: 2021-01-26 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  BaseConvert - Number base conversion.
15
15
 
16
16
  Converts positive integers to different bases:
17
17
  Binary, octal, hexadecimal, decimal, or any arbitrary base.
18
- "Out of the box" handling of up to base 94.
18
+ "Out of the box" handling of up to base 95(:print: characters).
19
19
  Allows for arbitrary choice of alphabet(digits).
20
20
  email: carlosjhr64@gmail.com
21
21
  executables: []
@@ -24,14 +24,18 @@ extra_rdoc_files: []
24
24
  files:
25
25
  - README.md
26
26
  - lib/base_convert.rb
27
+ - lib/base_convert/base.rb
28
+ - lib/base_convert/base_convert.rb
29
+ - lib/base_convert/chars.rb
27
30
  - lib/base_convert/configuration.rb
31
+ - lib/base_convert/digits.rb
28
32
  - lib/base_convert/from_to.rb
29
33
  - lib/base_convert/number.rb
30
34
  homepage: https://github.com/carlosjhr64/base_convert
31
35
  licenses:
32
36
  - MIT
33
37
  metadata: {}
34
- post_install_message:
38
+ post_install_message:
35
39
  rdoc_options: []
36
40
  require_paths:
37
41
  - lib
@@ -46,9 +50,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
46
50
  - !ruby/object:Gem::Version
47
51
  version: '0'
48
52
  requirements:
49
- - 'ruby: ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux]'
50
- rubygems_version: 3.0.3
51
- signing_key:
53
+ - 'ruby: ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]'
54
+ rubygems_version: 3.2.3
55
+ signing_key:
52
56
  specification_version: 4
53
57
  summary: BaseConvert - Number base conversion.
54
58
  test_files: []