├── .gitignore ├── Gemfile ├── .travis.yml ├── Rakefile ├── README.markdown ├── base32-crockford.gemspec ├── test └── test_base32_crockford.rb └── lib └── base32 └── crockford.rb /.gitignore: -------------------------------------------------------------------------------- 1 | Gemfile.lock 2 | doc 3 | pkg 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source :rubygems 2 | gemspec 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | rvm: 2 | - 2.0.0 3 | - 2.1.2 4 | - ree 5 | - jruby 6 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift './lib' 2 | 3 | require 'bundler' 4 | Bundler::GemHelper.install_tasks 5 | 6 | require 'rake/testtask' 7 | Rake::TestTask.new(:test) 8 | 9 | task :default => :test 10 | 11 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | An implementation of Douglas Crockfords Base32-Encoding in Ruby 2 | 3 | see 4 | 5 | Installation 6 | ============ 7 | 8 | $ gem install base32-crockford 9 | 10 | Changes 11 | ======= 12 | 13 | 0.1.0 - rename gem to base32-crockford 14 | 0.0.2 - ruby 1.9 compatibility 15 | 0.0.1 - initial release 16 | 17 | Usage 18 | ===== 19 | 20 | #!/usr/bin/env ruby 21 | 22 | require 'base32/crockford' 23 | 24 | Base32::Crockford.encode(1234) # => "16J" 25 | Base32::Crockford.encode(100**10, :split=>5, :length=>15) # => "02PQH-TY5NH-H0000" 26 | Base32::Crockford.decode("2pqh-ty5nh-hoooo") # => 10**100 27 | -------------------------------------------------------------------------------- /base32-crockford.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | require 'base32/crockford' 3 | 4 | spec = Gem::Specification.new do |s| 5 | s.name = 'base32-crockford' 6 | s.version = Base32::Crockford::VERSION 7 | s.summary = "32-symbol notation for expressing numbers in a form that can be conveniently and accurately transmitted between humans" 8 | 9 | s.files = `git ls-files`.split("\n") 10 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 11 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 12 | 13 | s.require_path = 'lib' 14 | s.author = "Levin Alexander" 15 | s.homepage = "https://github.com/levinalex/base32" 16 | s.email = "mail@levinalex.net" 17 | 18 | s.add_development_dependency 'rake' 19 | end 20 | -------------------------------------------------------------------------------- /test/test_base32_crockford.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'test/unit' 4 | require 'base32/crockford' 5 | 6 | class TestBase32Crockford < Test::Unit::TestCase 7 | 8 | def test_encoding_and_decoding_single_chars 9 | from = (0..31).to_a 10 | to = %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H J K M N P Q R S T V W X Y Z) 11 | 12 | from.zip(to) do |symbol_value, encode_symbol| 13 | assert_equal encode_symbol, Base32::Crockford.encode(symbol_value) 14 | assert_equal symbol_value, Base32::Crockford.decode(encode_symbol) 15 | end 16 | end 17 | 18 | def test_encoding_larger_numbers 19 | assert_equal("10", Base32::Crockford.encode(32)) 20 | assert_equal("16J", Base32::Crockford.encode(1234)) 21 | end 22 | 23 | def test_decoding_strings 24 | assert_equal(1234, Base32::Crockford.decode("16J")) 25 | end 26 | 27 | def test_decoding_normalizes_symbols 28 | assert_equal Base32::Crockford.decode('11100110'), 29 | Base32::Crockford.decode('IL1O0ilo') 30 | end 31 | 32 | def test_decoding_lowercase 33 | assert_equal Base32::Crockford.decode("abcdefghijklmnopqrstvwxyz"), 34 | Base32::Crockford.decode("ABCDEFGHIJKLMNOPQRSTVWXYZ") 35 | end 36 | 37 | def test_decoding_invalid_strings 38 | assert_equal nil, Base32::Crockford.decode("Ü'+?") 39 | assert_raises(ArgumentError) { Base32::Crockford.decode!("'+?") } 40 | end 41 | 42 | def test_decode_should_ignore_hyphens 43 | assert_equal 1234, Base32::Crockford.decode("1-6-j") 44 | end 45 | 46 | def test_normalize 47 | assert_equal "HE110W0R1D", Base32::Crockford.normalize("hello-world") 48 | assert_equal "B?123", Base32::Crockford.normalize("BU-123") 49 | end 50 | 51 | def test_valid 52 | assert_equal true, Base32::Crockford.valid?("hello-world") 53 | assert_equal false, Base32::Crockford.valid?("BU-123") 54 | end 55 | 56 | def test_length_and_hyphenization 57 | assert_equal "0016J", Base32::Crockford.encode(1234, :length => 5) 58 | assert_equal "0-01-6J", 59 | Base32::Crockford.encode(1234, :length => 5, :split => 2) 60 | assert_equal "00-010", 61 | Base32::Crockford.encode(32, :length => 5, :split => 3) 62 | end 63 | end 64 | 65 | -------------------------------------------------------------------------------- /lib/base32/crockford.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # 3 | # (c) 2008, Levin Alexander 4 | # 5 | # This file is released under the same license as ruby. 6 | 7 | require 'enumerator' 8 | 9 | module Base32 10 | end 11 | 12 | # encode a value with the encoding defined by _Douglas_ _Crockford_ in 13 | # 14 | # 15 | # this is *not* the same as the Base32 encoding defined in RFC 4648 16 | # 17 | # 18 | # The Base32 symbol set is a superset of the Base16 symbol set. 19 | # 20 | # We chose a symbol set of 10 digits and 22 letters. We exclude 4 of the 26 21 | # letters: I L O U. 22 | # 23 | # Excluded Letters 24 | # 25 | # I:: Can be confused with 1 26 | # L:: Can be confused with 1 27 | # O:: Can be confused with 0 28 | # U:: Accidental obscenity 29 | # 30 | # When decoding, upper and lower case letters are accepted, and i and l will 31 | # be treated as 1 and o will be treated as 0. When encoding, only upper case 32 | # letters are used. 33 | # 34 | # If the bit-length of the number to be encoded is not a multiple of 5 bits, 35 | # then zero-extend the number to make its bit-length a multiple of 5. 36 | # 37 | # Hyphens (-) can be inserted into symbol strings. This can partition a 38 | # string into manageable pieces, improving readability by helping to prevent 39 | # confusion. Hyphens are ignored during decoding. An application may look for 40 | # hyphens to assure symbol string correctness. 41 | # 42 | # 43 | class Base32::Crockford 44 | VERSION = "0.1.0" 45 | 46 | ENCODE_CHARS = 47 | %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H J K M N P Q R S T V W X Y Z ?) 48 | 49 | DECODE_MAP = ENCODE_CHARS.to_enum(:each_with_index).inject({}) do |h,(c,i)| 50 | h[c] = i; h 51 | end.merge({'I' => 1, 'L' => 1, 'O' => 0}) 52 | 53 | # encodes an integer into a string 54 | # 55 | # when +split+ is given a hyphen is inserted every characters to improve 56 | # readability 57 | # 58 | # when +length+ is given, the resulting string is zero-padded to be exactly 59 | # this number of characters long (hyphens are ignored) 60 | # 61 | # Base32::Crockford.encode(1234) # => "16J" 62 | # Base32::Crockford.encode(123456789012345, :split=>5) # => "3G923-0VQVS" 63 | # 64 | def self.encode(number, opts = {}) 65 | # verify options 66 | raise ArgumentError unless (opts.keys - [:length, :split] == []) 67 | 68 | str = number.to_s(2).reverse.scan(/.{1,5}/).map do |bits| 69 | ENCODE_CHARS[bits.reverse.to_i(2)] 70 | end.reverse.join 71 | 72 | str = str.rjust(opts[:length], '0') if opts[:length] 73 | 74 | if opts[:split] 75 | str = str.reverse 76 | str = str.scan(/.{1,#{opts[:split]}}/).map { |x| x.reverse } 77 | str = str.reverse.join("-") 78 | end 79 | 80 | str 81 | end 82 | 83 | # decode a string to an integer using Douglas Crockfords Base32 Encoding 84 | # 85 | # the string is converted to uppercase and hyphens are stripped before 86 | # decoding 87 | # 88 | # I,i,l,L decodes to 1 89 | # O,o decodes to 0 90 | # 91 | # Base32::Crockford.decode("16J") # => 1234 92 | # Base32::Crockford.decode("OI") # => 1 93 | # Base32::Crockford.decode("3G923-0VQVS") # => 123456789012345 94 | # 95 | # returns +nil+ if the string contains invalid characters and can't be 96 | # decoded 97 | # 98 | def self.decode(string) 99 | clean(string).split(//).map { |char| 100 | DECODE_MAP[char] or return nil 101 | }.inject(0) { |result,val| (result << 5) + val } 102 | end 103 | 104 | # same as decode, but raises ArgumentError when the string can't be decoded 105 | # 106 | def self.decode!(string) 107 | decode(string) or raise ArgumentError 108 | end 109 | 110 | # return the canonical encoding of a string. converts it to uppercase 111 | # and removes hyphens 112 | # 113 | # replaces invalid characters with a question mark ('?') 114 | # 115 | def self.normalize(string) 116 | clean(string).split(//).map { |char| 117 | ENCODE_CHARS[DECODE_MAP[char] || 32] 118 | }.join 119 | end 120 | 121 | # returns false iff the string contains invalid characters and can't be 122 | # decoded 123 | # 124 | def self.valid?(string) 125 | !(normalize(string) =~ /\?/) 126 | end 127 | 128 | class << self 129 | def clean(string) 130 | string.gsub(/-/,'').upcase 131 | end 132 | private :clean 133 | end 134 | end 135 | 136 | --------------------------------------------------------------------------------