tokogen 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d37166686245cf99243365f86bcef7c4a8ec2b1a
4
- data.tar.gz: 978498c998a874852766f7c0e0f1ad6809cf633f
3
+ metadata.gz: 4935595f4088dbbf55cf945d762542593fd849e2
4
+ data.tar.gz: 6cf358d6f950178c266f9f1102822586c2e9e14d
5
5
  SHA512:
6
- metadata.gz: 7ac95fe534f71d357d48b4c08fb1f461553314d6f5fa1c3912e4f20c0c08529a2e4b7bc00130b95954d9d8195656d84a708f36d8ef4c78a9dacf79a4636c2a3f
7
- data.tar.gz: d73f36a631cf3385dde27a3607456987848366044f06f3e56c59c01bad28f8cfe1b49c3b466d3c3123e3516e493a13ad83e379c748055eec8256987c32b5abeb
6
+ metadata.gz: 693e975d0e678c26e8eb5ecb8721fbc795573d834c8f4528b5f1f1af31c462646f148a2c06c9873d3af130e07b6fb27d029001fe51fcaa6a7405b97907ee03df
7
+ data.tar.gz: e1961e28a51c691c6c0201bc25009d5c9c664ed4a54860269988a4b92c407af63736eef1a0c3de3ed61e3e3c027051ceb75cd48f57cd8c8eee8a413bac96c2c7
@@ -5,6 +5,7 @@ AllCops:
5
5
  - '**/*.gemspec'
6
6
  - '**/Gemfile'
7
7
  - '**/Rakefile'
8
+ - '**/Guardfile'
8
9
  - '**/*.rake'
9
10
  Rails:
10
11
  Enabled: false
@@ -25,14 +26,17 @@ Style/IndentHash:
25
26
  Style/FirstParameterIndentation:
26
27
  EnforcedStyle: consistent
27
28
  Style/CaseIndentation:
28
- IndentWhenRelativeTo: end
29
+ EnforcedStyle: end
29
30
  Style/Alias:
30
31
  EnforcedStyle: prefer_alias_method
31
32
  Lint/EndAlignment:
32
- AlignWith: variable
33
+ EnforcedStyleAlignWith: variable
33
34
  Style/MultilineMethodCallIndentation:
34
35
  EnforcedStyle: indented
35
36
  Style/SpaceInLambdaLiteral:
36
37
  Enabled: false
37
38
  Bundler/OrderedGems:
38
39
  Enabled: false
40
+ Metrics/BlockLength:
41
+ Exclude:
42
+ - 'spec/**/*.rb'
@@ -2,6 +2,8 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - 2.3.1
5
+ - 2.3.3
6
+ - 2.4.0
5
7
  before_install: gem install bundler -v 1.13.6
6
8
  install:
7
9
  - bundle install --retry=3
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ guard :rspec, cmd: 'bundle exec rspec' do
3
+ require 'guard/rspec/dsl'
4
+ dsl = Guard::RSpec::Dsl.new(self)
5
+
6
+ # RSpec files
7
+ rspec = dsl.rspec
8
+ watch(rspec.spec_helper) { rspec.spec_dir }
9
+ watch(rspec.spec_support) { rspec.spec_dir }
10
+ watch(rspec.spec_files)
11
+
12
+ # Ruby files
13
+ ruby = dsl.ruby
14
+ dsl.watch_spec_files_for(ruby.lib_files)
15
+ end
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
1
  # Tokogen
2
+ [![Build Status](https://travis-ci.org/MOZGIII/tokogen.svg?branch=master)](https://travis-ci.org/MOZGIII/tokogen)
3
+ [![Gem Version](https://badge.fury.io/rb/tokogen.svg)](https://badge.fury.io/rb/tokogen)
2
4
 
3
5
  A ruby gem that allows you to generate customizable random tokens.
4
6
 
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require 'benchmark'
4
+
5
+ TIMES = 500_000
6
+
7
+ puts '# Sequential'
8
+ puts
9
+
10
+ Benchmark.bmbm(12) do |b|
11
+ b.report('(1 << x) - 1') { TIMES.times { |x| (1 << x) - 1 } }
12
+ b.report('~(-1 << x)') { TIMES.times { |x| ~(-1 << x) } }
13
+ end
14
+
15
+ puts
16
+
17
+ puts '# Random'
18
+ puts
19
+
20
+ top = 1 << 32
21
+ numbers = Array.new(TIMES) { Random::DEFAULT.rand(top) }
22
+
23
+ Benchmark.bmbm(12) do |b|
24
+ b.report('(1 << x) - 1') { numbers.each { |x| (1 << x) - 1 } }
25
+ b.report('~(-1 << x)') { numbers.each { |x| ~(-1 << x) } }
26
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require 'benchmark'
4
+
5
+ TIMES = 100_000_000
6
+
7
+ puts '# Sequential'
8
+ puts
9
+
10
+ Benchmark.bmbm(12) do |b|
11
+ b.report('x <= 255') { TIMES.times { |x| x <= 255 } }
12
+ b.report('x < 256') { TIMES.times { |x| x < 256 } }
13
+ b.report('x.bit_length <= 8') { TIMES.times { |x| x.bit_length <= 8 } }
14
+ end
15
+
16
+ puts
17
+
18
+ puts '# Random'
19
+ puts
20
+
21
+ top = 1 << 32
22
+ numbers = Array.new(TIMES) { Random::DEFAULT.rand(top) }
23
+
24
+ Benchmark.bmbm(12) do |b|
25
+ b.report('x <= 255') { numbers.each { |x| x <= 255 } }
26
+ b.report('x < 256') { numbers.each { |x| x < 256 } }
27
+ b.report('x.bit_length <= 8') { numbers.each { |x| x.bit_length <= 8 } }
28
+ end
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+ require 'tokogen'
4
+ args = []
5
+ args << ARGV[0].to_i if ARGV[0]
6
+ puts Tokogen.token(*args)
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
  require 'securerandom'
3
3
  require 'tokogen/version'
4
+ require 'tokogen/bit_splitter'
5
+ require 'tokogen/bit_combiner'
4
6
  require 'tokogen/generator'
7
+ require 'tokogen/alphabet'
5
8
 
6
9
  module Tokogen
7
10
  def self.token(length = 32)
@@ -12,7 +15,7 @@ module Tokogen
12
15
  generator # calling explicitly without arguments
13
16
  end
14
17
 
15
- def self.generator(randomness_source: SecureRandom, **options)
16
- Generator.new(randomness_source: randomness_source, **options)
18
+ def self.generator(randomness_source: SecureRandom, alphabet: Alphabet::BASE62)
19
+ Generator.new(randomness_source: randomness_source, alphabet: alphabet)
17
20
  end
18
21
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module Tokogen
3
+ module Alphabet
4
+ BASE62 = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a).join.freeze
5
+ BASE58 = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a - %w(0 O I l)).join.freeze
6
+ BASE64 = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['+', '/']).join.freeze
7
+
8
+ # Note: This is NOT ASCII85 coding alphabet, it's from RFC1924.
9
+ # Do not use in unless you're sure you need it!
10
+ BASE85 = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a + ['!', '#', '$', '%', '&', '(', ')', '*', '+', '-', ';', '<', '=', '>', '?', '@', '^', '_', '`', '{', '|', '}', '~']).join.freeze
11
+
12
+ # Some simple alphabets.
13
+ # Mainly used for testing but you can use them too.
14
+ BASE2 = ('0'..'1').to_a.join.freeze
15
+ BASE3 = ('0'..'2').to_a.join.freeze
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+ module Tokogen
3
+ class BitCombiner
4
+ attr_reader :size
5
+
6
+ def initialize(bits_enum, width_in_bits)
7
+ @bits_enum = bits_enum
8
+ @width_in_bits = width_in_bits
9
+
10
+ bits_enum_size = @bits_enum.size
11
+ @size = bits_enum_size / @width_in_bits if bits_enum_size
12
+ end
13
+
14
+ def each(&block) # rubocop:disable Metrics/MethodLength
15
+ enum = Enumerator.new(@size) do |y|
16
+ @bits_enum.each_slice(@width_in_bits) do |slice|
17
+ num = 0
18
+ slice.size.times do |i|
19
+ num |= (slice[i] << i)
20
+ end
21
+ y.yield(num)
22
+ end
23
+ end
24
+ return enum if block.nil?
25
+ enum.each(&block)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Tokogen
3
+ class BitSplitter
4
+ BYTE_SIZE = 8
5
+
6
+ attr_reader :size
7
+
8
+ def initialize(bytes_enum)
9
+ @bytes_enum = bytes_enum
10
+
11
+ bytes_enum_size = @bytes_enum.size
12
+ @size = bytes_enum_size * BYTE_SIZE if bytes_enum_size
13
+ end
14
+
15
+ def each(&block)
16
+ enum = Enumerator.new(@bytes_enum_bit_size) do |y|
17
+ @bytes_enum.each do |b|
18
+ BYTE_SIZE.times do |i|
19
+ y.yield(b[i])
20
+ end
21
+ end
22
+ end
23
+ return enum if block.nil?
24
+ enum.each(&block)
25
+ end
26
+ end
27
+ end
@@ -1,67 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
  module Tokogen
3
3
  class Generator
4
- DEFAULT_ALPHABET = (('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a).join.freeze
4
+ class AssertionFail < StandardError; end
5
5
 
6
- attr_reader :randomness_source
6
+ attr_reader :randomness_source, :alphabet
7
7
 
8
- def initialize(randomness_source:, alphabet: DEFAULT_ALPHABET)
8
+ def initialize(randomness_source:, alphabet:)
9
9
  @randomness_source = randomness_source
10
10
  @alphabet = alphabet
11
+
12
+ @alphabet_size = @alphabet.size
13
+ @max_char_index = @alphabet_size - 1
14
+ @bits_per_char = @max_char_index.bit_length
11
15
  end
12
16
 
13
- def generate(length)
14
- token_bits_amount = length * bits_per_char
17
+ def generate(length) # rubocop:disable Metrics/AbcSize
18
+ token_bits_amount = length * @bits_per_char
15
19
  bytes_to_read = full_bytes_in_bits(token_bits_amount)
16
20
  bytes = random_bytes(bytes_to_read)
17
- bits = bytes.unpack('b*')[0]
21
+ splitter = BitSplitter.new(bytes.each_byte)
22
+ combiner = BitCombiner.new(splitter.each, @bits_per_char)
18
23
  # It's possible we've read a couple exta bits of randomness,
19
24
  # since randomness is rounded to bytes.
20
25
  # Here we only take first `length` of bit that we need.
21
- bit_string_split(bits, bits_per_char)
22
- .take(length)
23
- .map { |index| alphabet_char(index) }
24
- .join
26
+ indexes = combiner.each.take(length)
27
+ raise AssertionFail, 'Invalid length' if indexes.size != length
28
+ indexes.map do |index|
29
+ # We split out random data into chunks of bits with fixed length.
30
+ # Therefore it's possible to have an index value that is larger than
31
+ # an alphabet size.
32
+ # In this case we'd resolve to nil, so we're just using modulo of the
33
+ # alphabet size. This will probably ruin the distribution that
34
+ # the randromness source provides, but it will at least work.
35
+ # If you don't want this behavior, just ensure you're using an alphabet
36
+ # with an even size - then there will always be a bijection between
37
+ # the generated indicies and the alphabet and the described issue
38
+ # will never occur.
39
+ alphabet_char(index % @alphabet_size)
40
+ end.join
25
41
  end
26
42
 
27
43
  def random_bytes(size)
28
44
  @randomness_source.random_bytes(size)
29
45
  end
30
46
 
31
- def max_char_index
32
- @alphabet.size - 1
47
+ def alphabet_char(index)
48
+ @alphabet[index]
33
49
  end
34
50
 
35
- def bits_per_char
36
- max_char_index.bit_length
37
- end
51
+ private
38
52
 
39
53
  def full_bytes_in_bits(bits)
40
54
  (bits + 7) >> 3
41
55
  end
42
-
43
- private
44
-
45
- def bit_string_split(bits, bits_per_char, &block) # rubocop:disable Metrics/MethodLength
46
- top = max_char_index
47
- curry = 0
48
- last_curry = 0
49
- bits.each_char.each_slice(bits_per_char).map do |binary_ord|
50
- val = binary_ord.join.to_i(2) + curry
51
- last_curry = curry
52
- if val <= top
53
- current = val
54
- curry = 0
55
- else
56
- current = top
57
- curry = val % top
58
- end
59
- current
60
- end.each(&block)
61
- end
62
-
63
- def alphabet_char(index)
64
- @alphabet[index]
65
- end
66
56
  end
67
57
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Tokogen
3
- VERSION = '0.1.2'
3
+ VERSION = '0.2.1'
4
4
  end
@@ -24,4 +24,6 @@ Gem::Specification.new do |spec|
24
24
  spec.add_development_dependency 'bundler', '~> 1.13'
25
25
  spec.add_development_dependency 'rake', '~> 10.0'
26
26
  spec.add_development_dependency 'rspec', '~> 3.0'
27
+ spec.add_development_dependency 'guard', '~> 2.14.0'
28
+ spec.add_development_dependency 'guard-rspec', '~> 4.7.3'
27
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tokogen
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MOZGIII
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-11 00:00:00.000000000 Z
11
+ date: 2017-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,10 +52,39 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: guard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 2.14.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 2.14.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: guard-rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.7.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.7.3
55
83
  description: A ruby gem that allows you to generate customizable random tokens.
56
84
  email:
57
85
  - mike-n@narod.ru
58
- executables: []
86
+ executables:
87
+ - tokogen
59
88
  extensions: []
60
89
  extra_rdoc_files: []
61
90
  files:
@@ -66,11 +95,18 @@ files:
66
95
  - ".rubocop_todo.yml"
67
96
  - ".travis.yml"
68
97
  - Gemfile
98
+ - Guardfile
69
99
  - README.md
70
100
  - Rakefile
101
+ - bin/bench_ruby_binary
102
+ - bin/bench_ruby_comparisons
71
103
  - bin/console
72
104
  - bin/setup
105
+ - exe/tokogen
73
106
  - lib/tokogen.rb
107
+ - lib/tokogen/alphabet.rb
108
+ - lib/tokogen/bit_combiner.rb
109
+ - lib/tokogen/bit_splitter.rb
74
110
  - lib/tokogen/generator.rb
75
111
  - lib/tokogen/version.rb
76
112
  - tokogen.gemspec