base_convert 3.0.191221 → 6.0.210201

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f291f3baa22819d9fc33426500a93f8807d099c30bf326ae55287450d31f24f
4
- data.tar.gz: 28a6ee75eae21c11a6bc31dd3a82b992b2840bb7e14030398947a6a9c1de5716
3
+ metadata.gz: 9e29e85eacc7c4fb82ae72be4dec517de21f2e0c1be03a6dc2670fa1296a9b12
4
+ data.tar.gz: 999da6376a8025b1c5dedd696b5e5c97fb9423b9dfc6f0c59dbefe270dc72dea
5
5
  SHA512:
6
- metadata.gz: d526f406b53f4fdc66341c78ff927ed45f76c6f61237b10fb1c65c93fa2a015045c9f5175feefe2773843e771deb4cf58dfac3c9daa3a96fdd62698b6fd094fb
7
- data.tar.gz: bb753fe647787556bba5ce5708554e3a7ecad88525c11a5be69c43976c29de3ff777b0e050ca3edafeda27e226a6b1451c5f53afb91e4b1f9c60877c7322640f
6
+ metadata.gz: 52b687612311825f4bc0877d9e5b4483d58006749dd12ec35ae954864a7b091c5c4a20e796a89aff383a1fe7db331446a13fd575924a1129fe29e78db037c24e
7
+ data.tar.gz: 768e66ca83303a4d4e36c2310291c480923a8a7758a1d18afc67a0c2dee88c8175b65180cb6dbde46cab71095f9a54302cec170da544f65657cc58f1f2ff0a27
data/README.md CHANGED
@@ -1,159 +1,240 @@
1
1
  # BaseConvert
2
2
 
3
+ * [VERSION 6.0.210201](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
data/lib/base_convert.rb CHANGED
@@ -1,30 +1,11 @@
1
- # http://rosettacode.org/wiki/Non-decimal_radices/Convert#Ruby
2
1
  module BaseConvert
3
- VERSION = '3.0.191221'
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 = '6.0.210201'
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,140 +1,98 @@
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
- raise "digits in string must be under base." unless @base > @digits.index(@string.chars.max)
94
75
  end
95
- unless @integer.nil?
96
- raise "integer can't be negative." if @integer < 0
97
- end
98
- end
99
76
 
100
- def _integer!
101
- _base!
102
- @string.upcase! if @base <= INDEXa and @digits == WORD
103
- _validate if @validate
104
- @integer = toi
77
+ @integer = toi(string) if @integer.nil?
105
78
  end
106
79
 
107
- def _string!
108
- _base!
109
- _validate if @validate
110
- @string = tob
80
+ def inspect
81
+ d = DIGITS.label(@digits)
82
+ "#{to_s} #{@base}:#{d}"
111
83
  end
112
84
 
113
- attr_reader :base, :digits
114
- def initialize(counter, base: nil, digits: nil, validate: true)
115
- @base, @digits, @validate = base, digits, validate
116
- @string, @integer = nil, nil
117
- case counter
118
- when String
119
- @string = counter
120
- _integer!
121
- when Integer
122
- @integer = counter
123
- _string!
124
- else
125
- raise "Need counter String|Integer."
126
- end
85
+ def validate?
86
+ @validate
127
87
  end
128
88
 
129
- def to_s
130
- @string
131
- end
89
+ alias to_s tos
132
90
 
133
91
  def to_i
134
92
  @integer
135
93
  end
136
94
 
137
- def to_base(base, digits=@digits, validate=@validate)
95
+ def to_base(base, digits=(base.is_a?Symbol)? DIGITS[base] : @digits, validate=@validate)
138
96
  Number.new @integer, base: base, digits: digits, validate: validate
139
97
  end
140
98
 
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.191221
4
+ version: 6.0.210201
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-21 00:00:00.000000000 Z
11
+ date: 2021-02-01 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: []