base_x 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 230c8a8b317282e89b30d188a1801e81201e31f8
4
+ data.tar.gz: ae6bc2a2bcd543b4f8be6e8116d5db2520d7aecc
5
+ SHA512:
6
+ metadata.gz: 1bf9a6ad14849a82fa486d205a6e25d4d0c8b80cf4741d8f5cee3435c29daba82ab0095278443e38aa47bccb2c9f0ec8a1f5a058f64bfcd08d1427c9ae0b3ff6
7
+ data.tar.gz: 61fb7c1933f3b53bf55df57fb4e9dcaff89ec0445ef6a16ef772099017e40b202adc0bd2404a0e625bb701c4772e874fccb12ca9a0476ca8d1312e14a5cdbe2e
@@ -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
@@ -0,0 +1 @@
1
+ 2.1.1
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.1
4
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem "minitest"
4
+ gem "guard-minitest", require: false
5
+ gem "terminal-notifier-guard", require: false
6
+ gem "minitest-reporters"
7
+
8
+ gemspec
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard :minitest do
5
+ # with Minitest::Unit
6
+ watch(%r{^test/(.*)\/?test_(.*)\.rb$})
7
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
8
+ watch(%r{^test/test_helper\.rb$}) { 'test' }
9
+
10
+ # with Minitest::Spec
11
+ # watch(%r{^spec/(.*)_spec\.rb$})
12
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
13
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
14
+ end
@@ -0,0 +1,3 @@
1
+ BaseX is dedicated to the public domain by its author, Brian Hempel. No rights are reserved. No restrictions are placed on the use of BaseX. That freedom also means, of course, that no warrenty of fitness is claimed; use BaseX at your own risk.
2
+
3
+ Public domain dedication is explained by the CC0 1.0 summary (and only the summary) at https://creativecommons.org/publicdomain/zero/1.0/
@@ -0,0 +1,197 @@
1
+ # BaseX
2
+
3
+ BaseX is a arbitrary base conversion library that, in addition to converting to/from integers, also supports encoding and decoding arbitrary binary data into and out of any base.
4
+
5
+ Many known bases are included, such as [Bitcoin Base58](https://en.bitcoin.it/wiki/Base58Check_encoding).
6
+
7
+ BaseX is useful for generating human-friendly cryptographic tokens and ID's, and could even be usde for encoding, transmitting, and decoding binary data over binary-unsafe mediums.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'base_x'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ ```
20
+ $ bundle
21
+ ```
22
+
23
+ Or install it yourself as:
24
+
25
+ ```
26
+ $ gem install base_x
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ Let's convert some numbers.
32
+
33
+ ```ruby
34
+ # Emulate hex, just for fun.
35
+ BaseX.integer_to_string(241, numerals: "0123456789abcdef")
36
+ # => "f1"
37
+ BaseX.string_to_integer("f1", numerals: "0123456789abcdef")
38
+ # => 241
39
+
40
+ # Bitcoin Base58 numerals. No O, 0, I, or l
41
+ BaseX.integer_to_string(456724510, numerals: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
42
+ # => "hMqHX"
43
+ BaseX.string_to_integer("hMqHX", numerals: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
44
+ # => 456724510
45
+
46
+ # That was rather clumsy, so let's use the provided Base58 object
47
+ BaseX::Base58.integer_to_string(456724510)
48
+ # => "hMqHX"
49
+ ```
50
+
51
+ If you use a base more than once, make it a constant.
52
+
53
+ ```ruby
54
+ Base24Greek = BaseX.new("αβγδεζηθικλμνξοπρστυφχψω")
55
+
56
+ Base24Greek.integer_to_string(456724510)
57
+ # => "ιψια"
58
+ Base24Greek.string_to_integer("ιψια")
59
+ # => 456724510
60
+ ```
61
+
62
+ ## Encoding
63
+
64
+ Use the `encode` and `decode` methods to convert arbitary binary data to and from the desired base.
65
+
66
+ ```ruby
67
+ BaseX::Base30L.encode("Hello World!")
68
+ # => "3xtzc85paptyrbdjgd27"
69
+ BaseX::Base30L.decode("3xtzc85paptyrbdjgd27")
70
+ # => "Hello World!"
71
+
72
+ BaseX.encode("SOS", numerals: "01")
73
+ # => "010100110100111101010011"
74
+ BaseX.decode("010100110100111101010011", numerals: "01")
75
+ # => "SOS"
76
+ ```
77
+
78
+ The encoding scheme is simple: BaseX treats the whole binary blob as one large big-endian binary number and then converts that number into the desired base. If the original binary had leading 0 bytes, leading "0"'s are added to the converted number.
79
+
80
+ ```ruby
81
+ Base10 = BaseX.new("0123456789")
82
+
83
+ Base10.encode("Hi") # => "18537"
84
+ Base10.encode("\x00Hi") # => "00018537"
85
+ Base10.encode("\x00\x00Hi") # => "0000018537"
86
+ ```
87
+
88
+ ## Token Generation
89
+
90
+ BaseX is great for generating tokens for identification or cryptographic purposes.
91
+
92
+ ```ruby
93
+ require 'securerandom'
94
+
95
+ bytes = SecureRandom.random_bytes(16) # 128 bits
96
+
97
+ BaseX::Base30L.encode(bytes)
98
+ # => "347wbrazxkvj59atq5zh2fdk55e"
99
+ BaseX::Base58.encode(bytes)
100
+ # => "SLrA71VABcvExTht6KLr89"
101
+ ```
102
+
103
+ ## Provided Bases
104
+
105
+ BaseX provides some bases you can use right away.
106
+
107
+ | Constant(s) | Example (64-bit token) | Numerals and Explanation |
108
+ |--------------------------------------------------------------------|--------------------------|-----------------------------------------------------------------------------------|
109
+ | `BaseX::Binary` | `"1111110010001110001…"` | `0-1`<br>Binary! |
110
+ | `BaseX::Base16` `BaseX::Base16L` `BaseX::Hex` `BaseX::Hexadecimal` | `"fc8e3c917d58368b"` | `0-9a-f`<br>Lowercase [hexadecimal](http://en.wikipedia.org/wiki/Hexadecimal). |
111
+ | `BaseX::Base16U` | `"FC8E3C917D58368B"` | `0-9A-F`<br>Uppercase [hexadecimal](http://en.wikipedia.org/wiki/Hexadecimal). |
112
+ | `BaseX::Base30L` | `"369be5e68tfqth"` | `2-9a-hj-km-np-tv-z`<br>(`0 1 i l o u` omitted.)<br>A BaseX special. All characters that could be confused in uppercase or lowercase are removed, so it is suitable for case-insensitive tokens. See `BaseX::CrockfordBase32` for why "u" is removed. |
113
+ | `BaseX::Base30U` | `"369BE5E68TFQTH"` | `2-9A-HJ-KM-NP-TV-Z`<br>(`0 1 I L O U` omitted.)<br>Uppercase version of above. I think lowercase is easier to read. |
114
+ | `BaseX::Base31L` | `"s59ez2tk5bep7"` | `0-9a-hj-km-np-z`<br>(`0 1 i l o` omitted.)<br>A BaseX special. All characters that could be confused in uppercase or lowercase are removed, so it is suitable for case-insensitive tokens. The "u" is retained. |
115
+ | `BaseX::Base31U` | `"S59EZ2TK5BEP7"` | `0-9A-HJ-KM-NP-Z`<br>(`0 1 I L O` omitted.)<br>Uppercase version of above. I think lowercase is easier to read. |
116
+ | `BaseX::RFC4648Base32` | `"PZDR4SF6VQNUL"` | `A-Z2-7`<br>(`0 1 8 9` omitted.)<br>The base 32 numerals of [RFC 4648](http://tools.ietf.org/html/rfc4648#page-8). |
117
+ | `BaseX::CrockfordBase32` | `"FS3HWJ5YNGDMB"` | `0-9A-HJ-KM-NP-TV-Z`<br>(`I L O U` omitted.)<br>The numerals of Douglas Crockford's [Base32 proposal](http://www.crockford.com/wrmg/base32.html). Crockford fears that allowing "u" may result in "accidental obscenity". What's the actually probability of a problem? I calculate that, with "u" as a possible numeral, a random 3-numeral string has about a 1/8000 chance of phonetically dropping the f-bomb on a viewer; a 128-bit token encoded in base32 has 26 numerals and thus 24 3-numeral strings, consequently about 1 in every 340 such tokens would be vulgar. |
118
+ | `BaseX::Base58` `BaseX::BitcoinBase58` | `"jF78uMwAKKg"` | `1-9A-HJ-NP-Za-km-z`<br>(`0 I O l` omitted.)<br>The numeral scheme used in [Bitcoin addresses](https://en.bitcoin.it/wiki/Base58Check_encoding). |
119
+ | `BaseX::FlickrBase58` | `"Jf78UmWajjF"` | `1-9a-km-zA-HJ-NP-Z`<br>(`0 I O l` omitted.)<br>The numeral scheme in [Flickr short URLs](https://www.flickr.com/groups/api/discuss/72157616713786392/). Same as Bitcoin, but lowercase preceeds uppercase. |
120
+ | `BaseX::GMPBase58` | `"gE67qKs9IId"` | `0-9A-Za-v`<br>(`w x y z` omitted.)<br>The numeral scheme used for [base conversions in the GMP arbitary-precision math library](https://gmplib.org/manual/Converting-Integers.html). |
121
+ | `BaseX::NewBase60` | `"W5pTz7hmhxF"` | `0-9A-HJ-NP-Z_a-km-z`<br>(`I O l` omitted, `_` added.)<br>A [scheme by Tantek Çelik](http://tantek.pbworks.com/w/page/19402946/NewBase60), originally for use in a URL shortener. The "_" still allows the whole text to be selected when double-clicking. |
122
+ | `BaseX::Base62` `BaseX::Base62DUL` | `"LgLY9aNSIf5"` | `0-9A-Za-z`<br>All alphanumeric characters: digits then uppercase then lowercase. |
123
+ | `BaseX::Base62DLU` | `"lGly9AnsiF5"` | `0-9a-zA-Z`<br>All alphanumeric characters: digits then lowercase then uppercase. |
124
+ | `BaseX::Base62LDU` | `"vGv8jAx2sFf"` | `a-z0-9A-Z`<br>All alphanumeric characters: lowercase then digits then uppercase. |
125
+ | `BaseX::Base62LUD` | `"vQvIjKxCsPf"` | `a-zA-Z0-9`<br>All alphanumeric characters: lowercase then uppercase then digits. |
126
+ | `BaseX::Base62UDL` | `"VgV8JaX2SfF"` | `A-Z0-9a-z`<br>All alphanumeric characters: uppercase then digits then lowercase. |
127
+ | `BaseX::Base62ULD` | `"VqViJkXcSpF"` | `A-Za-z0-9`<br>All alphanumeric characters: uppercase then lowercase then digits. |
128
+ | `BaseX::URLBase64` | `"PyOPJF9WDaL"` | `A-Za-z0-9-_`<br>(`- _` added.)<br>Alphanumerics plus `-` and `_`. Intended as a base 64 that can be used in URLs; part of [RFC 4648](http://tools.ietf.org/html/rfc4648#page-7). I find URL base 64 annoying because double-clicking won't select through the `-`. |
129
+ | `BaseX::Z85` | `"]MO]Dt%j>*"` | `0-9a-zA-Z.-:+=^!/*?&<>()[]{}@%$#`<br>(`. - : + = ^ ! / * ? & < > ( ) [ ] { } @ % $ #` added.)<br>The base 85 numerals used for [ZeroMQ Z85](http://rfc.zeromq.org/spec:32), an encoding standard optimized for 4-byte words and for pasting into single-quoted strings. |
130
+ | `BaseX::Base256` | `"\xFC\x8E<\x91}X6\x8B"` | `"\x00"-"\xFF"`<br>Byte 0 through byte 255; useful for convert binary strings into a number and back. Used internally by BaseX. |
131
+
132
+ Note that although the number schemes from various standards are represented here, BaseX is a number converter only: it does not do padding or other standard-specific details. BaseX is not, for example, a Z85 compliant encoder/decoder. You could, however, easily build one with BaseX.
133
+
134
+ In Ruby, you can uses `BaseX.bases` and `BaseX.print_bases` to get information similar to the above table.
135
+
136
+ ```
137
+ > BaseX.print_bases
138
+ Binary 2 111111001000111000111… 01
139
+ Base16 16 fc8e3c917d58368b 0123456789abcdef
140
+ Base16L 16 fc8e3c917d58368b 0123456789abcdef
141
+ Base16U 16 FC8E3C917D58368B 0123456789ABCDEF
142
+ Hex 16 fc8e3c917d58368b 0123456789abcdef
143
+ Hexadecimal 16 fc8e3c917d58368b 0123456789abcdef
144
+ Base30L 30 369be5e68tfqth 23456789abcdefghjkmnpqrstvwxyz
145
+ ...
146
+ ```
147
+
148
+ ### BaseX.base(n)
149
+
150
+ In addition to the named constants above, you can also quickly generate any alphanumeric base between 2 and 62. This could be handy becuase Ruby's `to_i` and `to_s` methods only support bases 2 to 36.
151
+
152
+ ```ruby
153
+ BaseX.base(49).integer_to_string(456724510)
154
+ # => "1UB4UI"
155
+ BaseX.base(49).string_to_integer("1UB4UI")
156
+ # => 456724510
157
+
158
+ # The order of numerals is digits then uppercase then lowercase
159
+ BaseX.base(49).numerals
160
+ # => "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm"
161
+ ```
162
+
163
+ ## Use Base30L
164
+
165
+ If you want your text to be transferred by any means other than copy-paste, I recommend `BaseX::Base30L`. Tokens have a bad habit of landing in places where they cannot be copied and pasted. Anecdotal evidence from my recent past:
166
+
167
+ - There was an infographic that had cited URLs baked into the image. I wanted to check the sources but could not copy the URLs, so I had to retype them. Also, some of the URLs had "O"'s in them.
168
+ - I recently transferred a shared secret from one computer to another "off the wire". I had my mother read the secret out loud from the other computer. Happily, the secret did not have mixed case: saying "capital L, lowercase u" would have doubled the tedium.
169
+
170
+ You can't predict what people are going to do with your encoded text, so it makes sense to be prepared.
171
+
172
+ ## Edge Cases
173
+
174
+ Even though `"".to_i` is `0` in Ruby, BaseX's `string_to_integer` will reject empty strings with a `BaseX::EmptyString` error. An empty string here is probably a bug in your code. (In contrast, the `encode` and `decode` functions do accept empty strings.)
175
+
176
+ If you try to convert a string that uses a character not in the base, a `BaseX::InvalidNumeral` error will be raised.
177
+
178
+ ## Limitations
179
+
180
+ If you try to encode/decode in a numeral system larger than base 256 (why would you do this?!), leading 0 bytes may not be properly preserved. Integer conversion will still work as expected.
181
+
182
+ ## License
183
+
184
+ Public Domain; no rights reserved.
185
+
186
+ No restrictions are placed on the use of BaseX. That freedom also means, of course, that no warrenty of fitness is claimed; use BaseX at your own risk.
187
+
188
+ Public domain dedication is explained by the CC0 1.0 summary (and only the summary) at https://creativecommons.org/publicdomain/zero/1.0/
189
+
190
+ ## Contributing
191
+
192
+ 1. Fork it ( http://github.com/brianhempel/base_x/fork )
193
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
194
+ 3. Run the tests with either `guard` or `minitest`
195
+ 4. Commit your changes (`git commit -am 'Add some feature'`)
196
+ 5. Push to the branch (`git push -u origin my-new-feature`)
197
+ 6. Create a new Pull Request
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ task :default => :test
4
+
5
+ desc "Run the tests"
6
+ task :test do
7
+ Dir.glob(File.expand_path("../test/*.rb", __FILE__)).each do |file|
8
+ require file
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'base_x/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "base_x"
8
+ spec.version = BaseX::VERSION
9
+ spec.authors = ["Brian Hempel"]
10
+ spec.email = ["plasticchicken@gmail.com"]
11
+ spec.summary = %q{Convert numbers into and out of any base, also allows encoding and decoding binary data. Many bases included.}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/brianhempel/base_x"
14
+ spec.license = "Public Domain"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "rake"
23
+ end
@@ -0,0 +1,201 @@
1
+ require "base_x/version"
2
+
3
+ class BaseX
4
+ class InvalidNumeral < RuntimeError
5
+ def initialize(invalid_char)
6
+ super "Can't convert to integer, '#{invalid_char}' is not in the numeral list for this base"
7
+ end
8
+ end
9
+
10
+ class EmptyString < RuntimeError
11
+ def initialize
12
+ super "Can't convert empty string into integer"
13
+ end
14
+ end
15
+
16
+ EXAMPLE_TOKEN = "\xFC\x8E\x3C\x91\x7D\x58\x36\x8B" # Random 64-bit token
17
+
18
+ def self.string_to_integer(string, opts)
19
+ opts[:numerals].respond_to?(:index) or
20
+ raise ArgumentError.new("A string of numerals must be provided, e.g. BaseX.string_to_integer(\"bcde\", numerals: \"abcdefg\")")
21
+ new(opts[:numerals]).string_to_integer(string)
22
+ end
23
+
24
+ def self.integer_to_string(int, opts)
25
+ opts[:numerals].respond_to?(:index) or
26
+ raise ArgumentError.new("A string of numerals must be provided, e.g. BaseX.integer_to_string(123, numerals: \"abcdefg\")")
27
+ new(opts[:numerals]).integer_to_string(int)
28
+ end
29
+
30
+ def self.encode(string, opts)
31
+ opts[:numerals].respond_to?(:index) or
32
+ raise ArgumentError.new("A string of numerals must be provided, e.g. BaseX.encode(\"Hello World\", numerals: \"abcdefg\")")
33
+ new(opts[:numerals]).encode(string)
34
+ end
35
+
36
+ def self.decode(encoded, opts)
37
+ opts[:numerals].respond_to?(:index) or
38
+ raise ArgumentError.new("A string of numerals must be provided, e.g. BaseX.encode(\"bcaffag\", numerals: \"abcdefg\")")
39
+ new(opts[:numerals]).decode(encoded)
40
+ end
41
+
42
+ def self.base(n)
43
+ n.between?(2, 62) or raise ArgumentError.new("Base #{n} is not valid; base must be at least 2 and at most 62")
44
+ digits_uppercase_lowercase = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
45
+ new(digits_uppercase_lowercase[0...n])
46
+ end
47
+
48
+ # Outputs an array of [base name, base size, example, and numerals] for all built-in bases
49
+ def self.bases
50
+ constants
51
+ .map { |const_name| [const_name, const_get(const_name)] }
52
+ .select { |const_name, base| base.is_a?(BaseX) }
53
+ .sort_by { |const_name, base| [base.base, const_name.to_s] }
54
+ .map do |const_name, base|
55
+ [const_name.to_s, base.base, base.encode(EXAMPLE_TOKEN), base.numerals]
56
+ end
57
+ end
58
+
59
+ def self.bases_table
60
+ bases.map do |name, base_size, example, numerals|
61
+ example = example[0...21] + "…" if example.size > 22
62
+ [
63
+ name,
64
+ base_size,
65
+ example.inspect.include?("\\") ? example.inspect : example,
66
+ numerals.inspect.include?("\\") ? numerals.inspect : numerals,
67
+ ]
68
+ end.map do |array|
69
+ "%-15s %-3s %-22s %s" % array
70
+ end.join("\n")
71
+ end
72
+
73
+ def self.print_bases
74
+ puts bases_table
75
+ end
76
+
77
+ attr_reader :numerals
78
+ attr_reader :base
79
+
80
+ def initialize(numerals)
81
+ numerals.chars.size > 1 or
82
+ raise ArgumentError.new("Need at least two numerals to express numbers! Numeral given: #{numerals.inspect}")
83
+ numerals.chars.size == numerals.chars.uniq.size or
84
+ raise ArgumentError.new("Duplicate characters found in numerals definition: #{numerals.inspect}")
85
+ @numerals = numerals
86
+ @base = numerals.size
87
+ end
88
+
89
+ def string_to_integer(string)
90
+ raise EmptyString.new unless string.size > 0
91
+ integer = 0
92
+ string.each_char do |char|
93
+ integer *= base
94
+ numeral_value = @numerals.index(char) or raise InvalidNumeral.new(char)
95
+ integer += numeral_value
96
+ end
97
+ integer
98
+ end
99
+
100
+ def integer_to_string(int)
101
+ return @numerals[0] if int == 0
102
+ string = ""
103
+ while int > 0
104
+ char = @numerals[int % base]
105
+ string << char
106
+ int /= base
107
+ end
108
+ string.reverse
109
+ end
110
+
111
+ def encode(string)
112
+ return "" if string.size == 0
113
+ int = string.each_byte.reduce(0) { |int, byte| int *= 256; int + byte }
114
+ encoded = integer_to_string(int)
115
+ string_number_size = 256**(string.size)
116
+ encoded_number_size = base**(encoded.size)
117
+
118
+ while encoded_number_size < string_number_size
119
+ encoded = @numerals[0] + encoded
120
+ encoded_number_size *= base
121
+ end
122
+
123
+ encoded
124
+ end
125
+
126
+ def decode(encoded)
127
+ return "" if encoded.size == 0
128
+ int = string_to_integer(encoded)
129
+ decoded = Base256.integer_to_string(int)
130
+ decoded_number_size = 256**(decoded.size)
131
+ encoded_number_size = base**(encoded.size)
132
+
133
+ # encoded_number_size / base < decoded_number_size <= encoded_number_size
134
+ while decoded_number_size <= encoded_number_size / base
135
+ decoded = "\x00" + decoded
136
+ decoded_number_size *= 256
137
+ end
138
+
139
+ decoded
140
+ end
141
+ end
142
+
143
+ digits = ("0".."9").to_a
144
+ uppercase = ("A".."Z").to_a
145
+ lowercase = ("a".."z").to_a
146
+
147
+ # Binary
148
+ BaseX::Binary = BaseX.new("01")
149
+
150
+ # Base 16
151
+ BaseX::Base16L = BaseX.new((digits + %w[a b c d e f]).join)
152
+ BaseX::Base16U = BaseX.new((digits + %w[A B C D E F]).join)
153
+ BaseX::Base16 = BaseX::Base16L
154
+ BaseX::Hex = BaseX::Base16
155
+ BaseX::Hexadecimal = BaseX::Base16
156
+
157
+ # Base 30, in case the number could change case
158
+ # no "u" following Crockford's probabilistic fear of accidental obscenity
159
+ # (I caluclate the probability of obscenity, with "u", is about 1 in 2^13
160
+ # for any given random 3-letter string (about 1 in 8000); when encoding 128bit
161
+ # tokens, after generating about 225 random tokens you have a 50% chance of
162
+ # having produced an "obscene token".)
163
+ BaseX::Base30L = BaseX.new((digits + lowercase - %w[0 1 i l o u]).join)
164
+ BaseX::Base30U = BaseX.new((digits + uppercase - %w[0 1 I L O U]).join)
165
+
166
+ # Base 31, in case the number could change case
167
+ BaseX::Base31L = BaseX.new((digits + lowercase - %w[0 1 i l o]).join)
168
+ BaseX::Base31U = BaseX.new((digits + uppercase - %w[0 1 I L O]).join)
169
+
170
+ # Base 32 schemes
171
+ BaseX::RFC4648Base32 = BaseX.new((uppercase + digits - %w[0 1 8 9]).join)
172
+ BaseX::CrockfordBase32 = BaseX.new((digits + uppercase - %w[I L O U]).join)
173
+
174
+ # Base58 schemes
175
+ BaseX::BitcoinBase58 = BaseX.new((digits + uppercase + lowercase - %w[0 O I l]).join)
176
+ BaseX::FlickrBase58 = BaseX.new((digits + lowercase + uppercase - %w[0 O I l]).join)
177
+ BaseX::GMPBase58 = BaseX.new((digits + uppercase + lowercase - %w[w x y z]).join)
178
+ BaseX::Base58 = BaseX::BitcoinBase58
179
+
180
+ # NewBase60, has an underscore as per http://tantek.pbworks.com/w/page/19402946/NewBase60
181
+ BaseX::NewBase60 = BaseX.new((digits + uppercase + %w[_] + lowercase - %w[O I l]).join)
182
+
183
+ # Base62; digits, upper, lower
184
+ BaseX::Base62DUL = BaseX.new((digits + uppercase + lowercase).join)
185
+ BaseX::Base62DLU = BaseX.new((digits + lowercase + uppercase).join)
186
+ BaseX::Base62LDU = BaseX.new((lowercase + digits + uppercase).join)
187
+ BaseX::Base62LUD = BaseX.new((lowercase + uppercase + digits).join)
188
+ BaseX::Base62UDL = BaseX.new((uppercase + digits + lowercase).join)
189
+ BaseX::Base62ULD = BaseX.new((uppercase + lowercase + digits).join)
190
+ BaseX::Base62 = BaseX::Base62DUL
191
+
192
+ # URL Base 64
193
+ BaseX::URLBase64 = BaseX.new((uppercase + lowercase + digits + %w[- _]).join)
194
+
195
+ # ZeroMQ Base 85
196
+ # Process your data back and forth between 4-byte and 5-byte chunks and you'll be compatible with the Z85 standard
197
+ BaseX::Z85 = BaseX.new((digits + lowercase + uppercase + %w|. - : + = ^ ! / * ? & < > ( ) [ ] { } @ % $ #|).join)
198
+
199
+ # Binary string encoding; turn binary strings into Bignums and back
200
+ BaseX::Base256 = BaseX.new((0..255).map(&:chr).join)
201
+
@@ -0,0 +1,3 @@
1
+ class BaseX
2
+ VERSION = "0.8.0"
3
+ end
@@ -0,0 +1,280 @@
1
+ require 'bundler/setup'
2
+ Bundler.require(:test)
3
+
4
+ require 'base_x'
5
+ require 'minitest/autorun'
6
+ require 'minitest/reporters'
7
+ MiniTest::Reporters.use! Minitest::Reporters::DefaultReporter.new
8
+
9
+ class TestBaseX < Minitest::Test
10
+ def setup
11
+ @base_x = BaseX.new("012")
12
+ end
13
+
14
+ attr_reader :base_x
15
+
16
+
17
+ ### Instance Methods ###
18
+
19
+
20
+ def test_complains_if_numerals_has_duplicate_chars
21
+ BaseX.new("∫©¶çΩ≈") # no error
22
+ assert_raises(ArgumentError) { BaseX.new("∫©¶çΩ≈Ω") }
23
+ end
24
+
25
+ def test_complains_if_only_one_numeral
26
+ BaseX.new("∫©") # no error
27
+ assert_raises(ArgumentError) { BaseX.new("") }
28
+ assert_raises(ArgumentError) { BaseX.new("∫") }
29
+ end
30
+
31
+ def test_string_to_integer
32
+ assert_equal 48, base_x.string_to_integer("01210")
33
+ end
34
+
35
+ def test_string_to_integer_errors_with_invalid_chars
36
+ assert_raises(BaseX::InvalidNumeral) do
37
+ base_x.string_to_integer("asdf")
38
+ end
39
+ end
40
+
41
+ def test_string_to_integer_errors_with_empty_string
42
+ assert_raises(BaseX::EmptyString) do
43
+ base_x.string_to_integer("")
44
+ end
45
+ end
46
+
47
+ def test_integer_to_string
48
+ assert_equal "1210", base_x.integer_to_string(48)
49
+ end
50
+
51
+ def test_0_to_string
52
+ assert_equal "0", base_x.integer_to_string(0)
53
+ end
54
+
55
+ def test_encode_decode_empty_string
56
+ assert_equal "", base_x.decode(base_x.encode(""))
57
+ end
58
+
59
+ def test_encode_decode_zeros
60
+ assert_equal "\x00\x00\x00\x00", base_x.decode(base_x.encode("\x00\x00\x00\x00"))
61
+ end
62
+
63
+ def test_encode_decode_stuff
64
+ assert_equal "stuff", base_x.decode(base_x.encode("stuff"))
65
+ end
66
+
67
+ def test_a_bunch_of_zero_strings_in_a_bunch_of_bases
68
+ (2..10).each do |base|
69
+ base_x = BaseX.new(('a'..'z').take(base).join)
70
+ (1..257).to_a.sample(20).each do |size|
71
+ assert_equal "\x00"*size, base_x.decode(base_x.encode("\x00"*size)), "problem with #{size} length zero string in base #{base}"
72
+ end
73
+ end
74
+ end
75
+
76
+ def test_a_bunch_of_random_strings_in_a_bunch_of_bases
77
+ (2..10).each do |base|
78
+ base_x = BaseX.new(('a'..'z').take(base).join)
79
+ (1..257).to_a.sample(20).each do |size|
80
+ string = SecureRandom.random_bytes(size)
81
+ assert_equal string.dup, base_x.decode(base_x.encode(string)), "problem encoding/decoding #{string.inspect} in base #{base}"
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ ### Class Methods ###
88
+
89
+
90
+ def test_integer_to_string_class_method
91
+ assert_equal "1210", BaseX.integer_to_string(48, numerals: "012")
92
+ end
93
+
94
+ def test_integer_to_string_claass_methods_complains_if_no_numerals_provided
95
+ assert_raises(ArgumentError) { BaseX.integer_to_string(48, numerals: Object.new) }
96
+ end
97
+
98
+ def test_string_to_integer_class_method
99
+ assert_equal 48, BaseX.string_to_integer("01210", numerals: "012")
100
+ end
101
+
102
+ def test_string_to_integer_claass_methods_complains_if_no_numerals_provided
103
+ assert_raises(ArgumentError) { BaseX.string_to_integer("01210", numerals: Object.new) }
104
+ end
105
+
106
+ def test_encode_decode_class_methods
107
+ assert_equal "stuff", BaseX.decode(BaseX.encode("stuff", numerals: "012"), numerals: "012")
108
+ end
109
+
110
+ def test_encode_class_method_complains_if_no_numerals_provided
111
+ assert_raises(ArgumentError) { BaseX.encode("stuff", numerals: Object.new) }
112
+ end
113
+
114
+ def test_decode_class_method_complains_if_no_numerals_provided
115
+ assert_raises(ArgumentError) { BaseX.decode("stuff", numerals: Object.new) }
116
+ end
117
+
118
+ def test_base_class_method
119
+ assert_equal "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZab", BaseX.base(38).numerals
120
+ end
121
+
122
+ def test_base_class_method_errors
123
+ BaseX.base(2) # no error
124
+ BaseX.base(62) # no error
125
+ assert_raises(ArgumentError) { BaseX.base(1) }
126
+ assert_raises(ArgumentError) { BaseX.base(63) }
127
+ end
128
+
129
+ def test_bases_class_method
130
+ assert_equal ["Binary", 2, "1111110010001110001111001001000101111101010110000011011010001011", "01"], BaseX.bases.first
131
+ expected_order = %w[
132
+ Binary
133
+ Base16
134
+ Base16L
135
+ Base16U
136
+ Hex
137
+ Hexadecimal
138
+ Base30L
139
+ Base30U
140
+ Base31L
141
+ Base31U
142
+ CrockfordBase32
143
+ RFC4648Base32
144
+ Base58
145
+ BitcoinBase58
146
+ FlickrBase58
147
+ GMPBase58
148
+ NewBase60
149
+ Base62
150
+ Base62DLU
151
+ Base62DUL
152
+ Base62LDU
153
+ Base62LUD
154
+ Base62UDL
155
+ Base62ULD
156
+ URLBase64
157
+ Z85
158
+ Base256
159
+ ]
160
+ assert_equal expected_order, BaseX.bases.map(&:first)
161
+ end
162
+
163
+ def test_bases_table_class_method
164
+ assert_equal String, BaseX.bases_table.class # it doesn't blow up
165
+ end
166
+
167
+
168
+ ### Base Constants ###
169
+
170
+
171
+ def test_binary
172
+ assert_equal "111010110111100110100010101", BaseX::Binary.integer_to_string(123456789)
173
+ assert_equal "111010110111100110100010110001", BaseX::Binary.integer_to_string(987654321)
174
+ end
175
+
176
+ def test_base16L
177
+ assert_equal "75bcd15", BaseX::Base16L.integer_to_string(123456789)
178
+ assert_equal "3ade68b1", BaseX::Base16L.integer_to_string(987654321)
179
+ end
180
+
181
+ def test_base16U
182
+ assert_equal "75BCD15", BaseX::Base16U.integer_to_string(123456789)
183
+ assert_equal "3ADE68B1", BaseX::Base16U.integer_to_string(987654321)
184
+ end
185
+
186
+ def test_base30L
187
+ assert_equal "74eg8b", BaseX::Base30L.integer_to_string(123456789)
188
+ assert_equal "3cnbspq", BaseX::Base30L.integer_to_string(987654321)
189
+ end
190
+
191
+ def test_base30U
192
+ assert_equal "74EG8B", BaseX::Base30U.integer_to_string(123456789)
193
+ assert_equal "3CNBSPQ", BaseX::Base30U.integer_to_string(987654321)
194
+ end
195
+
196
+ def test_base31L
197
+ assert_equal "6bq524", BaseX::Base31L.integer_to_string(123456789)
198
+ assert_equal "35hft2u", BaseX::Base31L.integer_to_string(987654321)
199
+ end
200
+
201
+ def test_base31U
202
+ assert_equal "6BQ524", BaseX::Base31U.integer_to_string(123456789)
203
+ assert_equal "35HFT2U", BaseX::Base31U.integer_to_string(987654321)
204
+ end
205
+
206
+ def test_rfc4648_base32
207
+ assert_equal "DVXTIV", BaseX::RFC4648Base32.integer_to_string(123456789)
208
+ assert_equal "5N42FR", BaseX::RFC4648Base32.integer_to_string(987654321)
209
+ end
210
+
211
+ def test_crockford_base32
212
+ assert_equal "3NQK8N", BaseX::CrockfordBase32.integer_to_string(123456789)
213
+ assert_equal "XDWT5H", BaseX::CrockfordBase32.integer_to_string(987654321)
214
+ end
215
+
216
+ def test_bitcoin_base58
217
+ assert_equal "BukQL", BaseX::BitcoinBase58.integer_to_string(123456789)
218
+ assert_equal "2WGzDn", BaseX::BitcoinBase58.integer_to_string(987654321)
219
+ end
220
+
221
+ def test_flickr_base58
222
+ assert_equal "bUKpk", BaseX::FlickrBase58.integer_to_string(123456789)
223
+ assert_equal "2vgZdM", BaseX::FlickrBase58.integer_to_string(987654321)
224
+ end
225
+
226
+ def test_gmp_base58
227
+ assert_equal "AqhNJ", BaseX::GMPBase58.integer_to_string(123456789)
228
+ assert_equal "1TFvCj", BaseX::GMPBase58.integer_to_string(987654321)
229
+ end
230
+
231
+ def test_new_base60
232
+ assert_equal "9XZZ9", BaseX::NewBase60.integer_to_string(123456789)
233
+ assert_equal "1GCURM", BaseX::NewBase60.integer_to_string(987654321)
234
+ end
235
+
236
+ def test_base62_dul
237
+ assert_equal "8M0kX", BaseX::Base62DUL.integer_to_string(123456789)
238
+ assert_equal "14q60P", BaseX::Base62DUL.integer_to_string(987654321)
239
+ end
240
+
241
+ def test_base62_dlu
242
+ assert_equal "8m0Kx", BaseX::Base62DLU.integer_to_string(123456789)
243
+ assert_equal "14Q60p", BaseX::Base62DLU.integer_to_string(987654321)
244
+ end
245
+
246
+ def test_base62_ldu
247
+ assert_equal "iwaK7", BaseX::Base62LDU.integer_to_string(123456789)
248
+ assert_equal "beQgaz", BaseX::Base62LDU.integer_to_string(987654321)
249
+ end
250
+
251
+ def test_base62_lud
252
+ assert_equal "iwaUH", BaseX::Base62LUD.integer_to_string(123456789)
253
+ assert_equal "be0gaz", BaseX::Base62LUD.integer_to_string(987654321)
254
+ end
255
+
256
+ def test_base62_udl
257
+ assert_equal "IWAk7", BaseX::Base62UDL.integer_to_string(123456789)
258
+ assert_equal "BEqGAZ", BaseX::Base62UDL.integer_to_string(987654321)
259
+ end
260
+
261
+ def test_base62_uld
262
+ assert_equal "IWAuh", BaseX::Base62ULD.integer_to_string(123456789)
263
+ assert_equal "BE0GAZ", BaseX::Base62ULD.integer_to_string(987654321)
264
+ end
265
+
266
+ def test_url_base64
267
+ assert_equal "HW80V", BaseX::URLBase64.integer_to_string(123456789)
268
+ assert_equal "63mix", BaseX::URLBase64.integer_to_string(987654321)
269
+ end
270
+
271
+ def test_z85
272
+ assert_equal "2v2B/", BaseX::Z85.integer_to_string(123456789)
273
+ assert_equal "i]jLP", BaseX::Z85.integer_to_string(987654321)
274
+ end
275
+
276
+ def test_base256
277
+ assert_equal "\x07\x5B\xCD\x15".force_encoding("ASCII-8BIT"), BaseX::Base256.integer_to_string(123456789)
278
+ assert_equal "\x3A\xDE\x68\xB1".force_encoding("ASCII-8BIT"), BaseX::Base256.integer_to_string(987654321)
279
+ end
280
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: base_x
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian Hempel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Convert numbers into and out of any base, also allows encoding and decoding
42
+ binary data. Many bases included.
43
+ email:
44
+ - plasticchicken@gmail.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".ruby_version"
51
+ - ".travis.yml"
52
+ - Gemfile
53
+ - Guardfile
54
+ - LICENSE.txt
55
+ - README.md
56
+ - Rakefile
57
+ - base_x.gemspec
58
+ - lib/base_x.rb
59
+ - lib/base_x/version.rb
60
+ - test/test_base_x.rb
61
+ homepage: https://github.com/brianhempel/base_x
62
+ licenses:
63
+ - Public Domain
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.2.2
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Convert numbers into and out of any base, also allows encoding and decoding
85
+ binary data. Many bases included.
86
+ test_files:
87
+ - test/test_base_x.rb