bases 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 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: