bases 1.0.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: 902a4986a5074750b2a81f17eaccb1e8bd944e3d
4
+ data.tar.gz: d639c6bb2b3b3f71f1a42dd009f046d2fe5bf721
5
+ SHA512:
6
+ metadata.gz: 5377cb3c5ef10f7a28854c42f373cb3afbb43ad9b9c1e09a4f337607bec6c6d6c769e3979300b0eab1f7a8427cb87674637418da8ed02c65aac1f984e6d3db32
7
+ data.tar.gz: 9ec434e817b7563702b584c64f619a06000c410c5343744f8460786bf20f719b447346f482263e5277ea5e69f2b4299b30b577dbff75fba4915a9620512b7ca0
@@ -0,0 +1 @@
1
+ service_name: travis-ci
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.3
4
+ - 2.1.1
5
+ - 2.0.0
6
+ - 1.9.3
@@ -0,0 +1 @@
1
+ --markup markdown --private
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Andrea Leopardi
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,174 @@
1
+ # Bases
2
+
3
+ [![Build Status](https://travis-ci.org/whatyouhide/bases.svg?branch=master)](https://travis-ci.org/whatyouhide/bases)
4
+ [![Gem Version](https://badge.fury.io/rb/bases.svg)](http://badge.fury.io/rb/bases)
5
+ [![Coverage Status](https://img.shields.io/coveralls/whatyouhide/bases.svg)](https://coveralls.io/r/whatyouhide/bases)
6
+ [![Code Climate](https://codeclimate.com/github/whatyouhide/bases/badges/gpa.svg)](https://codeclimate.com/github/whatyouhide/bases)
7
+ [![Dependency Status](https://gemnasium.com/whatyouhide/bases.svg)](https://gemnasium.com/whatyouhide/bases)
8
+ [![Inline docs](http://inch-ci.org/github/whatyouhide/bases.svg?branch=master&style=flat)](http://inch-ci.org/github/whatyouhide/bases)
9
+
10
+ Convert **from** and **to** any base you can think of.
11
+
12
+ A bunch of features:
13
+
14
+ * Convert to bases up to **whatever you want!**
15
+ * Use custom bases defined as arrays, like this binary base: `['↑', '↓']`.
16
+ * Use multicharacter digits.
17
+ * Use **emojis** as digits!
18
+ * Fall back to Ruby's `Integer#to_s` and `String#to_i` when the base is less
19
+ than 36.
20
+ * Superdocumented, tested like shuttle launches were depending on it (this may
21
+ not be true).
22
+ * Supports MRI Ruby (yeah, just Ruby) from version 1.9.3.
23
+
24
+
25
+ ## Why
26
+
27
+ Ruby can convert bases, but only with bases up to 36. But converting to bigger
28
+ basis is just as fun (if not even more!), since you can easily reduce the number
29
+ of character used to represent a number.
30
+
31
+ I only know of gem that does this, [radix][radix]. Radix isn't bad, but I don't
32
+ like it because it monkeypatches everything. It provides the `b`
33
+ method on strings, which on recent versions of Ruby is also a [default
34
+ method][ruby-string-b].
35
+
36
+
37
+ ## Installation
38
+
39
+ Add this line to your application's Gemfile:
40
+
41
+ ``` ruby
42
+ gem 'bases'
43
+ ```
44
+
45
+ And then execute:
46
+
47
+ ```
48
+ $ bundle
49
+ ```
50
+
51
+ Or install it yourself as:
52
+
53
+ ``` bash
54
+ $ gem install bases
55
+ ```
56
+
57
+
58
+ ## Usage
59
+
60
+ The main hub for the usage of this gem is the `Bases.val` method.
61
+ It takes a parameter that can be a bunch of things.
62
+
63
+ * An integer: in this case, the source base is assumed to be the decimal base.
64
+ * An array: no base is assumed, you have to manually specify it (keep reading!).
65
+ The array is assumed to be a list of digits (each element is a digit) from
66
+ the most significant one to the least significant one, like you'd expect.
67
+ * A string: no base is assumed. If the string doesn't contain whitespace, each
68
+ character is assumed to be a digit; otherwise, whitespace is assumed to
69
+ separate multicharacter digits (wow, multicharacter digits!).
70
+
71
+ The return value of `Bases.val` is junk (sort of), so you want to
72
+ call some methods to specify a *source base* and a *destination base*.
73
+
74
+ Those methods are the `in_base` and `to_base` methods:
75
+
76
+ ``` ruby
77
+ Bases.val('100').in_base(2).to_base(10) #=> '4'
78
+ Bases.val('1111').in_base(2).to_base(16) #=> 'f'
79
+ Bases.val('A').in_base(16).to_base(10) #=> '10'
80
+ ```
81
+
82
+ The `to_base` method always returns a `String`, even with `to_base(10)`. To
83
+ overcome that, just call `to_i` on the string.
84
+
85
+ When you pass an integer to `val`, base 10 is assumed:
86
+
87
+ ``` ruby
88
+ Bases.val(10).to_base(Bases::HEX) #=> 'A'
89
+ Bases.val(0b1011).to_base(2) #=> '1011'
90
+ ```
91
+
92
+ #### Bracket syntax
93
+
94
+ `Bases.val` is aliased to `Bases.[]`, so that you can
95
+ easily create values with a clean syntax:
96
+
97
+ ``` ruby
98
+ Bases[5].to_base(2) #=> '101'
99
+ ```
100
+
101
+ #### Array bases
102
+
103
+ You can use arrays everywhere you can use a base. The elements of the array will
104
+ be the digits of the new base, from left to right. Defining a base through an
105
+ array is easy:
106
+
107
+ ``` ruby
108
+ # An alternative way of defining base 2:
109
+ base2 = [0, 1]
110
+
111
+ # A very cool alternative binary base:
112
+ christmas_star_base = %w(+ ≈)
113
+
114
+ # A (contrived) example of base64:
115
+ base64 = ('A'..'Z').to_a + ('a'..'z').to_a + (0..9).to_a + %w(+ /)
116
+ ```
117
+
118
+ #### Predefined bases
119
+
120
+ Some default (common) bases are offered as constants:
121
+
122
+ ``` ruby
123
+ Bases::B62 #=> base 62 (alphanumeric)
124
+ Bases::B64 #=> base64
125
+ ```
126
+
127
+ #### Common bases
128
+
129
+ The gem provides a bunch of methods for dealing with common bases. These methods
130
+ should be used in place of the `in_base` and `to_base` methods.
131
+
132
+ They are:
133
+
134
+ - `in_binary`/`to_binary`
135
+ - `in_hex`/`to_hex` (`in_hex` solves the issue noted in the [hexadecimal base
136
+ section](#hex))
137
+
138
+ Since the decimal is also common, a `to_i` method is included. This method
139
+ returns an integer, not a string, in order to conform with the Ruby standard
140
+ library.
141
+
142
+ ``` ruby
143
+ Bases.val('1010').in_binary.to_i #=> 10
144
+ ```
145
+
146
+ ### Monkeypatching
147
+
148
+ I can see the appeal of monkeypatching (can I?). So, you can specifically
149
+ require to monkeypatch the `Integer`, `Array` and `String` Ruby classes:
150
+
151
+ ``` ruby
152
+ # Instead of just 'bases':
153
+ require 'bases/monkeypatches'
154
+
155
+ 2.to_base [:a, :b] #=> 'ba'
156
+ 10.to_binary #=> '1010'
157
+ 15.to_hex #=> 'f'
158
+
159
+ 'A'.in_hex.to_i #=> 10
160
+ 'baba'.in_base([:a, :b]).to_base(2) #=> '1010'
161
+
162
+ ['foo', 'bar'].in_base(['foo', 'bar', 'baz']).to_i #=> 1
163
+ ```
164
+
165
+
166
+ ## Contributing
167
+
168
+ Fork, make changes, commit those changes, push to your fork, create a new Pull
169
+ Request here. Thanks!
170
+
171
+
172
+
173
+ [radix]: https://github.com/rubyworks/radix
174
+ [ruby-string-b]: http://www.ruby-doc.org/core-2.1.3/String.html#method-i-b
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.test_files = FileList['test/*_test.rb']
6
+ end
7
+
8
+ # Default task is 'test', as used by Travis CI.
9
+ task default: :test
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bases/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'bases'
8
+ spec.version = Bases::VERSION
9
+ spec.authors = ['Andrea Leopardi']
10
+ spec.email = 'an.leopardi@gmail.com'
11
+ spec.summary = 'Convert bases like a mofo.'
12
+ spec.homepage = 'https://github.com/whatyouhide/bases'
13
+ spec.license = 'MIT'
14
+ spec.description = <<-DESC
15
+ This gem lets you convert integers from and to whatever base you like. You
16
+ can use array bases where you specify all the digits in the base,
17
+ multicharacter digits and other niceties. By default, this gem avoids
18
+ monkeypatching core Ruby classes, but it can be configured to monkeypatch
19
+ too.
20
+ DESC
21
+
22
+ spec.files = `git ls-files -z`.split("\x0")
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ['lib']
25
+
26
+ spec.add_development_dependency 'bundler', '~> 1.6'
27
+ spec.add_development_dependency 'rake', '~> 10'
28
+ spec.add_development_dependency 'minitest', '~> 5'
29
+ spec.add_development_dependency 'minitest-reporters', '~> 1.0'
30
+ spec.add_development_dependency 'coveralls', '~> 0.7'
31
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: UTF-8
2
+
3
+ # Constants
4
+ require 'bases/version'
5
+ require 'bases/constants'
6
+ # Modules
7
+ require 'bases/helpers'
8
+ require 'bases/algorithms'
9
+ # Classes
10
+ require 'bases/number'
11
+
12
+ # The main module.
13
+ module Bases
14
+ # Create a new instance of `Number` from `value`.
15
+ # @param [String|Integer] value A value as in `Number.new`
16
+ # @return [Number]
17
+ def self.val(value)
18
+ Bases::Number.new(value)
19
+ end
20
+
21
+ class << self
22
+ alias_method :[], :val
23
+ end
24
+ end
@@ -0,0 +1,43 @@
1
+ # encoding: UTF-8
2
+
3
+ # This module encapsulates the practical algorithms used to change bases.
4
+ module Bases::Algorithms
5
+ # The common base conversion algorithm, which converts a number in base 10
6
+ # (`value`) to its value in base `new_base`.
7
+ # @param [Integer] value The value in base 10
8
+ # @param [Array<String>] new_base
9
+ # @return [Array<String>] An array of digits (as strings) from the most
10
+ # significant to the least significant one
11
+ def self.convert_to_base(value, new_base)
12
+ # Return early if the is 0, as it is the first digit of the base array.
13
+ return [new_base.first] if value == 0
14
+
15
+ result = []
16
+ numeric_base = new_base.count
17
+
18
+ while value > 0
19
+ remainder = value % numeric_base
20
+ value /= numeric_base
21
+ result.unshift(new_base[remainder])
22
+ end
23
+
24
+ result
25
+ end
26
+
27
+ # Convert a value in a source base to an integer in base 10.
28
+ # @param [Array<String>] digits_ary An array of digits, from the most
29
+ # significant to the least significant.
30
+ # @param [Array<String>] source_base A base expressed as a list of digits
31
+ # @return [Integer]
32
+ def self.convert_from_base(digits_ary, source_base)
33
+ # The numeric base is the number of digits in the source base.
34
+ numeric_base = source_base.count
35
+
36
+ digits_ary.reverse.each.with_index.reduce(0) do |value, (digit, position)|
37
+ # The value of `digit` in the source base is simply the index of `digit`
38
+ # in the `@source_base` array.
39
+ digit_value_in_base10 = source_base.find_index(digit)
40
+ value + (digit_value_in_base10 * (numeric_base**position))
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ module Bases
4
+ # Base 62, the alphanumeric characters.
5
+ B62 = (
6
+ ('A'..'Z').to_a +
7
+ ('a'..'z').to_a +
8
+ (0..9).to_a
9
+ ).map(&:to_s)
10
+
11
+ # Base 64.
12
+ # @see http://en.wikipedia.org/wiki/Base64.
13
+ B64 = B62 + %w(+ /)
14
+
15
+
16
+ # This error is thrown when an invalid value is passed to `Number`'s
17
+ # constructor.
18
+ InvalidValueError = Class.new(StandardError)
19
+
20
+ # This error is thrown when you try to convert a number without a specified
21
+ # base to another base.
22
+ NoBaseSpecifiedError = Class.new(StandardError)
23
+
24
+ # This error is thrown when there are digits in a value which aren't in the
25
+ # specified source base.
26
+ WrongDigitsError = Class.new(StandardError)
27
+
28
+ # This error is thrown when there are duplicates digits in a base.
29
+ # @example
30
+ # bad_base_3 = [0, 1, 0]
31
+ DuplicateDigitsError = Class.new(StandardError)
32
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: UTF-8
2
+
3
+ # Some miscellaneous helper functions.
4
+ module Bases::Helpers
5
+ # Return `true` if there are duplicate elements in `ary`.
6
+ # @param [Array] ary
7
+ # @return [bool]
8
+ def self.are_there_duplicates?(ary)
9
+ ary.uniq.size != ary.size
10
+ end
11
+
12
+ # Return an array version of `base`. An *array version* of `base` means an
13
+ # array of **strings**. If `base` is already an array, a copy of that array
14
+ # but with all elements converted to strings is returned. If `base` is an
15
+ # integer (in base 10 :D) a range-like array going from 0 to `base` is
16
+ # returned.
17
+ # @param [Integer|Array] base
18
+ # @return [Array<String>]
19
+ # @raise [DuplicateDigitsError] if there are duplicate digits in the base (if
20
+ # it was passed as an array).
21
+ def self.base_to_array(base)
22
+ base = (base.is_a?(Array) ? base : (0...base)).map(&:to_s)
23
+
24
+ if are_there_duplicates?(base)
25
+ fail Bases::DuplicateDigitsError,
26
+ 'There are duplicate digits in the base'
27
+ else
28
+ base
29
+ end
30
+ end
31
+
32
+ # Check whether `digits` contains some digits that are not in `base`.
33
+ # @param [Array<String>] digits An array of digits
34
+ # @param [Array] base The base expressed as an array as everywhere else
35
+ # @return [boolean]
36
+ def self.only_valid_digits?(digits, base)
37
+ digits.all? { |digit| base.include?(digit) }
38
+ end
39
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'bases'
4
+ require 'bases/monkeypatches/integer'
5
+ require 'bases/monkeypatches/string'
6
+ require 'bases/monkeypatches/array'
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ # Some monkeypatches to the `Array` class.
4
+ class Array
5
+ # Return a `Bases::Number` instance with the current array as the
6
+ # value and the source base set to `base`.
7
+ # @param (see Bases::Number#in_base)
8
+ # @return [Number]
9
+ # @see Bases::Number#in_base
10
+ def in_base(base)
11
+ Bases.val(self).in_base(base)
12
+ end
13
+
14
+ # Return a `Bases::Number` instance with the current array as the
15
+ # value and the source base set to the binary base.
16
+ # @return [Number]
17
+ # @see Bases::Number#in_binary
18
+ def in_binary
19
+ Bases.val(self).in_binary
20
+ end
21
+
22
+ # Return a `Bases::Number` instance with the current array as the
23
+ # value and the source base set to the hexadecimale base.
24
+ # @return [Number]
25
+ # @see Bases::Number#in_hex
26
+ def in_hex
27
+ Bases.val(self).in_hex
28
+ end
29
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: UTF-8
2
+
3
+ # Some monkeypatches to the `Integer` class.
4
+ class Integer
5
+ # Convert this number to a given `base`.
6
+ # @param (see Bases::Number#to_base)
7
+ # @return [String]
8
+ # @see Bases::Number#to_base
9
+ def to_base(base, opts = {})
10
+ Bases.val(self).to_base(base, opts)
11
+ end
12
+
13
+ # Convert this number in binary form.
14
+ # @return [String]
15
+ # @see Bases::Number#to_binary
16
+ def to_binary
17
+ to_base(2)
18
+ end
19
+
20
+ # Convert this number in hexadecimal form.
21
+ # @return [String]
22
+ # @see Bases::Number#to_hex
23
+ def to_hex
24
+ to_base(16)
25
+ end
26
+ end
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ # Some monkeypatches to the `String` class.
4
+ class String
5
+ # Return a `Bases::Number` instance with the current string as the
6
+ # value and the source base set to `base`.
7
+ # @param (see Bases::Number#in_base)
8
+ # @return [Number]
9
+ # @see Bases::Number#in_base
10
+ def in_base(base)
11
+ Bases.val(self).in_base(base)
12
+ end
13
+
14
+ # Return a `Bases::Number` instance with the current string as the
15
+ # value and the source base set to the binary base.
16
+ # @return [Number]
17
+ # @see Bases::Number#in_binary
18
+ def in_binary
19
+ Bases.val(self).in_binary
20
+ end
21
+
22
+ # Return a `Bases::Number` instance with the current string as the
23
+ # value and the source base set to the hexadecimale base.
24
+ # @return [Number]
25
+ # @see Bases::Number#in_hex
26
+ def in_hex
27
+ Bases.val(self).in_hex
28
+ end
29
+ end
@@ -0,0 +1,164 @@
1
+ # encoding: UTF-8
2
+
3
+ # The main class provided by Bases. It represents a base-agnostic
4
+ # number which can be forced to be interpreted in any base and then converted in
5
+ # any base.
6
+ class Bases::Number
7
+ # Create a new instance from a given `value`. If that value is an intance of a
8
+ # subclass of `Integer` (a `Bignum` or a `Fixnum`), it will be assumed to be
9
+ # in base 10 and any call to `in_base` on the new instance will result in an
10
+ # exception.
11
+ #
12
+ # If it is a `String`, no base will be assumed; if there are spaces
13
+ # in the string, they're used to separate the *multicharacter* digits,
14
+ # otherwise each character is assumed to be a digit.
15
+ #
16
+ # It `value` is an `Array`, each element in the array is assumed to be a
17
+ # digit. The array is read like a normal number, where digits on the left are
18
+ # more significative than digits on the right.
19
+ #
20
+ # @param [Integer|String|Array] value
21
+ # @raise [InvalidValueError] if `value` isn't a `String`, an `Integer` or an
22
+ # `Array`
23
+ def initialize(value)
24
+ case value
25
+ when Integer
26
+ @source_base = helpers.base_to_array(10)
27
+ @value = value
28
+ when String
29
+ @digits = (value =~ /\s/) ? value.split : value.split('')
30
+ when Array
31
+ @digits = value.map(&:to_s)
32
+ else
33
+ fail Bases::InvalidValueError, "#{value} isn't a valid value"
34
+ end
35
+ end
36
+
37
+ # Set the source base of the current number. The `base` can be either a number
38
+ # (like 2 for binary or 16 for hexadecimal) or an array. In the latter case,
39
+ # the array is considered the whole base. For example, the binary base would
40
+ # be represented as `[0, 1]`; another binary base could be `[:a, :b]` and so
41
+ # on.
42
+ #
43
+ # **Note** that when the base is an integer up to 36, the native Ruby
44
+ # `Integer#to_i(base)` method is used for efficiency and clarity. However,
45
+ # this means that digits in a base 36 are numbers *and* letters, while digits
46
+ # in base 37 and more are only numbers (interpreted as multichar digits).
47
+ #
48
+ # `self` is returned in order to allow nice-looking chaining.
49
+ #
50
+ # @param [Integer|Array] base
51
+ # @return self
52
+ # @raise [WrongDigitsError] if there are digits in the previously specified
53
+ # value that are not present in `base`.
54
+ def in_base(base)
55
+ @source_base = helpers.base_to_array(base)
56
+
57
+ if native_ruby_base?(base)
58
+ @value = @digits.join('').to_i(base)
59
+ return self
60
+ end
61
+
62
+ # Make sure `@digits` contains only valid digits.
63
+ unless helpers.only_valid_digits?(@digits, @source_base)
64
+ fail Bases::WrongDigitsError,
65
+ "Some digits weren't in base #{base}"
66
+ end
67
+
68
+ @value = algorithms.convert_from_base(@digits, @source_base)
69
+ self
70
+ end
71
+
72
+ # Return a string representation of the current number in a `new_base`.
73
+ # A **string** representation is always returned, even if `new_base` is 10. If
74
+ # you're using base 10 and want an integer, just call `to_i` on the resulting
75
+ # string.
76
+ #
77
+ # @example With the default separator
78
+ # Number.new(3).to_base(2) #=> '11'
79
+ # @example With a different separator
80
+ # Number.new(3).to_base(2, separator: ' ~ ') #=> '1 ~ 1'
81
+ #
82
+ # @param [Integer|Array] new_base The same as in `in_base`
83
+ # @param [Hash] opts A small hash of options
84
+ # @option opts [bool] :array If true, return the result as an array of digits;
85
+ # otherwise, return a string. This defaults to `false`.
86
+ # @return [Array<String>|String]
87
+ # @raise [NoBaseSpecifiedError] if no source base was specified (either by
88
+ # passing an integer to the constructor or by using the `in_base` method)
89
+ def to_base(new_base, opts = {})
90
+ opts[:array] = false if opts[:array].nil?
91
+
92
+ unless defined?(@source_base)
93
+ fail Bases::NoBaseSpecifiedError, 'No base was specified'
94
+ end
95
+
96
+ # Let's apply the base conversion algorithm, which returns an array of
97
+ # digits.
98
+ res = if native_ruby_base?(new_base)
99
+ @value.to_s(new_base).split('')
100
+ else
101
+ algorithms.convert_to_base(@value, helpers.base_to_array(new_base))
102
+ end
103
+
104
+ opts[:array] ? res : res.join('')
105
+ end
106
+
107
+ # This function assumes you want the output in base 10 and returns an integer
108
+ # instead of a string (which would be returned after a call to `to_base(10)`).
109
+ # This was introduced so that `to_i` is adapted to the standard of returning
110
+ # an integer (in base 10, as Ruby represents integers).
111
+ # @return [Integer]
112
+ def to_i
113
+ to_base(10).to_i
114
+ end
115
+
116
+ # Specify that the current number is in hexadecimal representation.
117
+ # @note If you want to parse an hexadecimal number ignoring case-sensitivity,
118
+ # you **can't** use `in_base(Bases::HEX)` since that assumes
119
+ # upper case digits. You **have** to use `in_hex`, which internally just
120
+ # calls `String#hex`.
121
+ # @return [self]
122
+ def in_hex
123
+ @source_base = helpers.base_to_array(16)
124
+ @value = @digits.join('').hex
125
+ self
126
+ end
127
+
128
+ # Specify that the current number is in binary representation. This is just a
129
+ # shortcut for `in_base(2)` or `in_base([0, 1])`.
130
+ # @return [self]
131
+ def in_binary
132
+ in_base(2)
133
+ end
134
+
135
+ # Just an alias to `to_base(2)`.
136
+ # @see Number#to_base
137
+ # @return [String]
138
+ def to_binary
139
+ to_base(2)
140
+ end
141
+
142
+ # Just an alias to `to_base(Bases::HEX)`.
143
+ # @see Numer#to_base
144
+ # @return [String]
145
+ def to_hex
146
+ to_base(16)
147
+ end
148
+
149
+ private
150
+
151
+ def native_ruby_base?(base)
152
+ base.is_a?(Fixnum) && base.between?(2, 36)
153
+ end
154
+
155
+ # A facility method for accessing the `Algorithms` module.
156
+ def algorithms
157
+ Bases::Algorithms
158
+ end
159
+
160
+ # A facility method for accessing the `Helpers` module.
161
+ def helpers
162
+ Bases::Helpers
163
+ end
164
+ end
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+
3
+ module Bases
4
+ # The version of the Bases gem.
5
+ VERSION = '1.0.0'
6
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'test_helper'
4
+
5
+ class HelpersTest < Minitest::Test
6
+ def test_are_there_duplicates?
7
+ assert helpers.are_there_duplicates?([0, 1, 0])
8
+ assert helpers.are_there_duplicates?([2, 2, 2])
9
+ assert helpers.are_there_duplicates?([nil, nil])
10
+ refute helpers.are_there_duplicates?([])
11
+ refute helpers.are_there_duplicates?([1, '1'])
12
+ end
13
+
14
+ def test_base_to_array
15
+ assert_equal %w(0 1), helpers.base_to_array(2)
16
+ assert_equal %w(0 1), helpers.base_to_array([0, 1])
17
+ assert_equal (0...10).map(&:to_s), helpers.base_to_array(10)
18
+ assert_equal %w(foo bar), helpers.base_to_array(%w(foo bar))
19
+ end
20
+
21
+ def test_only_valid_digits
22
+ assert helpers.only_valid_digits?(%w(f o o), %(f o))
23
+ assert helpers.only_valid_digits?(%w(f o o), %(foo bar))
24
+ assert helpers.only_valid_digits?(%w(foo bar foo), %(foo bar))
25
+ refute helpers.only_valid_digits?(%w(foo bar baz), %(foo bar))
26
+ refute helpers.only_valid_digits?(%w(012), %(0 1))
27
+ end
28
+
29
+ private
30
+
31
+ def helpers
32
+ Bases::Helpers
33
+ end
34
+ end
@@ -0,0 +1,32 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'test_helper'
4
+
5
+ class MiscTest < Minitest::Test
6
+ def test_some_basic_bases_are_defined
7
+ assert_const_defined :B62
8
+ assert_const_defined :B64
9
+ end
10
+
11
+ def test_val
12
+ assert_respond_to mod, :val
13
+ assert_instance_of mod::Number, mod.val(33)
14
+ end
15
+
16
+ def test_brackets
17
+ assert_respond_to mod, :[]
18
+ assert_instance_of mod::Number, mod[0xba]
19
+ assert_equal 044, mod['44'].in_base(8).to_i
20
+ end
21
+
22
+ private
23
+
24
+ def assert_const_defined(const)
25
+ assert mod.const_defined?(const),
26
+ "#{const} is not defined in module #mod"
27
+ end
28
+
29
+ def mod
30
+ Bases
31
+ end
32
+ end
@@ -0,0 +1,46 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'test_helper'
4
+ require_relative '../lib/bases/monkeypatches'
5
+
6
+ module MonkeypatchesTests
7
+ class IntegerTest < Minitest::Test
8
+ def test_to_base
9
+ assert_equal '10', 10.to_base(10)
10
+ assert_equal '10', 0b1010.to_base(10)
11
+ assert_equal 'A', 0xa.to_base(16).upcase
12
+
13
+ assert_equal %w(1 0), 2.to_base(2, array: true)
14
+ end
15
+
16
+ def test_common_methods
17
+ assert_equal '1010', 10.to_binary
18
+ assert_equal 'A', 10.to_hex.upcase
19
+ end
20
+ end
21
+
22
+ class StringTest < Minitest::Test
23
+ def test_in_base
24
+ assert_equal '2', '10'.in_base(2).to_hex
25
+ assert_equal '1010', 'A'.in_base(16).to_base(2)
26
+ assert_equal '1010', 'baba'.in_base([:a, :b]).to_base(2)
27
+ end
28
+
29
+ def test_common_methods
30
+ assert_equal 2, '10'.in_binary.to_i
31
+ assert_equal 2, 'bar foo'.in_base(%w(foo bar)).to_i
32
+ assert_equal 10, 'a'.in_hex.to_i
33
+ end
34
+ end
35
+
36
+ class ArrayTest < Minitest::Test
37
+ def test_in_base
38
+ assert_equal 11, [1, 0, 1, 1].in_base(2).to_i
39
+ end
40
+
41
+ def test_common_methods
42
+ assert_equal 2, [1, 0].in_binary.to_i
43
+ assert_equal 171, ['a', 'b'].in_hex.to_i
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,138 @@
1
+ # encoding: UTF-8
2
+
3
+ require_relative 'test_helper'
4
+ require 'digest'
5
+
6
+ class NumberTest < Minitest::Test
7
+ BASE64 = Bases::B64
8
+
9
+ NUMBERS_FROM_BASE10 = [
10
+ { base10: 0, base: 2, value: '0' },
11
+ { base10: 1, base: 2, value: '1' },
12
+ { base10: 2, base: 2, value: '10' },
13
+ { base10: 4, base: 2, value: '100' },
14
+ { base10: 9, base: 2, value: '1001' },
15
+ { base10: 0, base: 16, value: '0' },
16
+ { base10: 15, base: 16, value: 'f' },
17
+ { base10: 16, base: 16, value: '10' },
18
+ { base10: 3, base: 3, value: '10' },
19
+ { base10: 0, base: %w(≈ y), value: '≈' },
20
+ { base10: 8, base: %w(≈ +), value: '+≈≈≈' },
21
+ { base10: 63, base: BASE64, value: '/' }
22
+ ]
23
+
24
+ def test_from_base10_integer_to_any_base
25
+ NUMBERS_FROM_BASE10.each do |num_data|
26
+ result = n(num_data[:base10]).to_base(num_data[:base])
27
+ assert_equal num_data[:value], result
28
+ end
29
+ end
30
+
31
+ def test_from_any_base_to_base10
32
+ NUMBERS_FROM_BASE10.each do |num_data|
33
+ result = n(num_data[:value]).in_base(num_data[:base]).to_i
34
+ assert_equal num_data[:base10], result
35
+ end
36
+ end
37
+
38
+ def test_easy_to_see_numbers_and_identities
39
+ assert_equal 'fade', n(0xfade).to_base(16).downcase
40
+ assert_equal '71', n(071).to_base(8)
41
+ assert_equal '1010110', n(0b1010110).to_base(2)
42
+
43
+ assert_equal '10', n('10').in_base(2).to_base(2)
44
+ assert_equal 10, n(10).to_i
45
+
46
+ val = 'fwrfwasgefrfqf1r3f43131'
47
+ assert_equal val, n(val).in_base(BASE64).to_base(BASE64)
48
+ end
49
+
50
+ def test_facility_methods
51
+ assert_equal 10, n('a').in_hex.to_base(10).to_i
52
+ assert_equal 10, n('1010').in_binary.to_base(10).to_i
53
+ assert_equal '1010', n(10).to_binary
54
+ assert_equal 'A', n(10).to_hex.upcase
55
+ end
56
+
57
+ def test_multichar_digits
58
+ assert_equal 2, n(%w(bar foo)).in_base(%w(foo bar)).to_i
59
+ assert_equal 1, n('hello world').in_base(%w(hello world)).to_i
60
+ end
61
+
62
+ def test_duplicate_digits_are_checked
63
+ ex = Bases::DuplicateDigitsError
64
+ assert_raises(ex) { n(2).to_base([0, 1, 0]) }
65
+ assert_raises(ex) { n('foo bar').in_base(%w(foo foo)) }
66
+ end
67
+
68
+ def test_initial_value_type_is_checked
69
+ ex = Bases::InvalidValueError
70
+
71
+ assert_raises(ex) { n({}) }
72
+ assert_raises(ex) { n(nil) }
73
+ assert_raises(ex) { n(0..10) }
74
+ assert_raises(ex) { n(Object.new) }
75
+ end
76
+
77
+ def test_digits_are_checked_for_belonging_to_the_base
78
+ ex = Bases::WrongDigitsError
79
+ assert_raises(ex) { n('foo bar baz').in_base(%w(foo bar)) }
80
+ end
81
+
82
+ def test_an_exception_is_thrown_if_no_base_is_specified
83
+ ex = Bases::NoBaseSpecifiedError
84
+ assert_raises(ex) { n('10').to_base(10) }
85
+ assert_raises(ex) { n('A').to_i }
86
+ assert_raises(ex) { n(%w(foo bar)).to_base(3) }
87
+ assert_raises(ex) { n([0, 2]).to_i }
88
+ end
89
+
90
+ def test_leading_zeros_are_harmless
91
+ assert_equal 2, n('00000010').in_base(2).to_i
92
+ assert_equal 10, n('0a').in_hex.to_i
93
+ assert_equal 1, n(%w(foo bar)).in_base(%w(foo bar)).to_i
94
+ end
95
+
96
+ def test_to_i
97
+ assert_equal 10, n('A').in_base(16).to_i
98
+ assert_equal 10, n('a').in_base(16).to_i
99
+ assert_equal 10, n('1010').in_base(2).to_i
100
+ end
101
+
102
+ def test_edge_cases
103
+ assert_equal 0, n([]).in_base(2).to_i
104
+ assert_equal 0, n('').in_base(10).to_i
105
+ end
106
+
107
+ def test_bignums_are_supported
108
+ value = n('//////////////').in_base(BASE64).to_i
109
+ assert_instance_of Bignum, value
110
+
111
+ # MD5 are hex numbers, very big ones too!
112
+ hex = Digest::MD5.hexdigest('foo bar')
113
+
114
+ # They convert easily to bignums...
115
+ assert_instance_of Bignum, n(hex).in_base(16).to_i
116
+
117
+ # ...and to base 3 numbers (check that the resulting number is composed of
118
+ # only the 0, 1 and 2 digits.
119
+ binary_digits = n(hex).in_base(16).to_base(3)
120
+ .split('').uniq.map(&:to_i).sort
121
+ assert_equal [0, 1, 2], binary_digits
122
+ end
123
+
124
+ def test_to_base_outputting_an_array
125
+ assert_equal %w(b a b a), n(10).to_base([:a, :b], array: true)
126
+ assert_equal %w(1 0), n(2).to_base(2, array: true)
127
+ end
128
+
129
+ def test_emojis_are_fun_and_💙
130
+ assert_equal '💙💚', n(2).to_base(['💚', '💙'])
131
+ end
132
+
133
+ private
134
+
135
+ def n(val)
136
+ Bases::Number.new(val)
137
+ end
138
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: UTF-8
2
+
3
+ # Coveralls coverage metrics.
4
+ require 'coveralls'
5
+ Coveralls.wear!
6
+
7
+ require 'minitest/autorun'
8
+ require 'minitest/pride'
9
+ require 'minitest/reporters'
10
+
11
+ require_relative '../lib/bases'
12
+
13
+ Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bases
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrea Leopardi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-21 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.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: coveralls
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.7'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.7'
83
+ description: |2
84
+ This gem lets you convert integers from and to whatever base you like. You
85
+ can use array bases where you specify all the digits in the base,
86
+ multicharacter digits and other niceties. By default, this gem avoids
87
+ monkeypatching core Ruby classes, but it can be configured to monkeypatch
88
+ too.
89
+ email: an.leopardi@gmail.com
90
+ executables: []
91
+ extensions: []
92
+ extra_rdoc_files: []
93
+ files:
94
+ - ".coveralls.yml"
95
+ - ".gitignore"
96
+ - ".travis.yml"
97
+ - ".yardopts"
98
+ - Gemfile
99
+ - LICENSE.txt
100
+ - README.md
101
+ - Rakefile
102
+ - bases.gemspec
103
+ - lib/bases.rb
104
+ - lib/bases/algorithms.rb
105
+ - lib/bases/constants.rb
106
+ - lib/bases/helpers.rb
107
+ - lib/bases/monkeypatches.rb
108
+ - lib/bases/monkeypatches/array.rb
109
+ - lib/bases/monkeypatches/integer.rb
110
+ - lib/bases/monkeypatches/string.rb
111
+ - lib/bases/number.rb
112
+ - lib/bases/version.rb
113
+ - test/helpers_test.rb
114
+ - test/misc_test.rb
115
+ - test/monkeypatches_test.rb
116
+ - test/number_test.rb
117
+ - test/test_helper.rb
118
+ homepage: https://github.com/whatyouhide/bases
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.2.2
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Convert bases like a mofo.
142
+ test_files:
143
+ - test/helpers_test.rb
144
+ - test/misc_test.rb
145
+ - test/monkeypatches_test.rb
146
+ - test/number_test.rb
147
+ - test/test_helper.rb
148
+ has_rdoc: