flexkey 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,18 @@
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
+ .ruby-version
@@ -0,0 +1 @@
1
+ --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in flexkey.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Devin McCabe
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,169 @@
1
+ ## Flexkey
2
+
3
+ Flexkey is a Ruby gem for generating unique, random strings for use as product keys, redemption codes, invoice numbers, passwords, etc.
4
+
5
+ ### Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'flexkey'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install flexkey
18
+
19
+ ##### Dependencies
20
+
21
+ None. Flexkey has been tested with Ruby 1.9.3 and 2.0.
22
+
23
+ ### Usage
24
+
25
+ ##### Basic usage
26
+
27
+ Instantiate a new generator with `Flexkey::Generator.new` and provide a `format` and `char_pool`. Then, generate a single key or multiple unique keys with the `generate` method:
28
+
29
+ ```ruby
30
+ keygen = Flexkey::Generator.new(format: 'aaaa-aa', char_pool: { 'a' => :alpha_upper })
31
+ # => #<Flexkey::Generator @format="aaaa-aa", @char_pool={"a"=>:alpha_upper}, @n_possible_keys=308915776>
32
+ keygen.generate
33
+ # => "KBND-NR"
34
+ keygen.generate(3)
35
+ # => ["IVXT-LB", "BFZB-LM", "WIVG-ZJ"]
36
+ ```
37
+
38
+ In this example, Flexkey generates a key by replacing each instance of `'a'` in the string `'aaaa-aa'` with a character
39
+ randomly drawn from a pool of uppercase letters while leaving unspecified characters (i.e. `'-'`) alone.
40
+
41
+ Since you're most likely persisting generated keys in a database, even though `generate(n)` will return `n` _unique_ keys, you'll still want to validate them for uniqueness against keys you've previously saved.
42
+
43
+ ##### Built-in character types
44
+
45
+ | **Character type** | **Description** |
46
+ |-------------------|--------------------------------------------------------|
47
+ | alpha_upper | uppercase letters |
48
+ | alpha_lower | lowercase letters |
49
+ | numeric | numerals |
50
+ | alpha_upper_clear | alpha_upper without ambiguous letters ("S", "O", etc.) |
51
+ | alpha_lower_clear | alpha_lower without ambiguous letters ("l", "O", etc.) |
52
+ | numeric_clear | numeric without ambiguous numerals ("0", "1", and "5") |
53
+ | symbol | symbols on a standard U.S. keyboard |
54
+ | basic_symbol | basic symbols on a standard U.S. keyboard |
55
+
56
+ The names and characters present in each type can be retrieved as such:
57
+
58
+ ```ruby
59
+ Flexkey::CharPool.available_char_types
60
+ # => [:alpha_upper, :alpha_lower, :numeric, :alpha_upper_clear, :alpha_lower_clear, :numeric_clear, :symbol, :basic_symbol]
61
+ Flexkey::CharPool.available_char_pools
62
+ # => {:alpha_upper=>"ABCDEFGHIJKLMNOPQRSTUVWXYZ", :alpha_lower=>"abcdefghijklmnopqrstuvwxyz", :numeric=>"0123456789", :alpha_upper_clear=>"ABCDEFGHJKLMNPQRTUVWXYZ", :alpha_lower_clear=>"abcdefghjkmnpqrtuvwxyz", :numeric_clear=>"2346789", :symbol=>"!@\#$%^&*;:()_+-=[]{}\\|'\",.<>/?", :basic_symbol=>"!@\#$%^&*;:"}
63
+ ```
64
+
65
+ ##### Examples
66
+
67
+ ```ruby
68
+ Flexkey::Generator.new(format: 'a-nnnn', char_pool: {
69
+ 'a' => :alpha_upper, 'n' => :numeric
70
+ }).generate
71
+ # => "Y-1145"
72
+ ```
73
+
74
+ ```ruby
75
+ Flexkey::Generator.new(format: 'SN-nnnnnnnnn', char_pool: {
76
+ 'n' => :numeric
77
+ }).generate
78
+ # => "SN-181073470"
79
+ ```
80
+
81
+ ```ruby
82
+ Flexkey::Generator.new(format: 'AA.aa.nn.ss', char_pool: {
83
+ 'A' => :alpha_upper_clear, 'a' => :alpha_lower_clear, 'n' => :numeric_clear, 's' => :basic_symbol
84
+ }).generate
85
+ # => "MX.mh.33.*:"
86
+ ```
87
+
88
+ You can also specify a custom string instead of using a built-in type.
89
+
90
+ ```ruby
91
+ Flexkey::Generator.new(format: 'annn/c', char_pool: {
92
+ 'a' => :alpha_upper, 'n' => :numeric, 'c' => 'LMN'
93
+ }).generate(5)
94
+ # => ["X905/N", "F865/L", "M423/N", "V564/L", "V874/M"]
95
+ ```
96
+
97
+ ```ruby
98
+ Flexkey::Generator.new(format: 'mnnnnnnn', char_pool: {
99
+ 'm' => '123456789', 'n' => :numeric
100
+ }).generate(5)
101
+ # => ["45862188", "23054329", "36248220", "56044911", "49873464"]
102
+ ```
103
+
104
+ ##### Selecting from multiple character types
105
+
106
+ To replace a character in the `format` from a pool of multiple character types, specify the both the types and proportions in the `char_pool`. For example, to generate keys of length 10 where each character is randomly selected from a pool of uppercase letters and numerals with equal proportions, do the following:
107
+
108
+ ```ruby
109
+ Flexkey::Generator.new(format: '..........', char_pool: {
110
+ '.' => { alpha_upper: 0.5, numeric: 0.5 }
111
+ }).generate(5)
112
+ # => ["OXAEXC2O87", "3D95JXQ60P", "8F28OX31Y8", "65ZY7IM6TL", "B971Q602SO"]
113
+ ```
114
+
115
+ You can specify proportions with any positive numbers:
116
+
117
+ ```ruby
118
+ Flexkey::Generator.new(format: 'U-ccccc-ccccc', char_pool: {
119
+ 'U' => :alpha_upper_clear, 'c' => { numeric_clear: 1, alpha_upper_clear: 7 }
120
+ }).generate(5)
121
+ # => ["H-KYLYK-DQLK3", "U-7QUXV-6DXNW", "X-REYAX-LL489", "L-8ABNJ-ZW3A7", "M-TPVTW-VEMTE"]
122
+ ```
123
+
124
+ ```ruby
125
+ Flexkey::Generator.new(format: 'UUUUU.LLLLL', char_pool: {
126
+ 'U' => { 'WXYZ' => 3, 'FGH' => 1 }, 'L' => :alpha_lower_clear
127
+ }).generate(5)
128
+ # => ["XZYYF.kwkth", "HHFYW.nkpdr", "WXXGW.fvyeu", "HWFXW.xzgeq", "WXWXW.twrdk"]
129
+ ```
130
+
131
+ ##### Number of possible keys
132
+
133
+ Flexkey will raise an exception if you try to request more keys than are possible with the `format` you provided:
134
+
135
+ ```ruby
136
+ keygen = Flexkey::Generator.new(format: 'annn', char_pool: {
137
+ 'a' => :alpha_upper, 'n' => :numeric
138
+ })
139
+ keygen.generate(3)
140
+ # => ["F202", "U811", "W802"]
141
+ keygen.generate(26001)
142
+ # Flexkey::GeneratorError: There are only 26000 possible keys
143
+ keygen.n_possible_keys
144
+ # => 26000
145
+ ```
146
+
147
+ The instance method `n_possible_keys` is available for reference.
148
+
149
+ ##### CharPool
150
+
151
+ If you're only interested in using the proportional sampling feature of Flexkey and will generate keys yourself, use `Flexkey::CharPool.generate` with a single hash argument:
152
+
153
+ ```ruby
154
+ char_pool = Flexkey::CharPool.generate({ alpha_upper: 0.75, numeric: 0.25 })
155
+ 10.times.map { char_pool.sample }.join
156
+ => "XC3RPKKWKA"
157
+ ```
158
+
159
+ ### Additional documentation
160
+
161
+ Somewhere.
162
+
163
+ ### Contributing
164
+
165
+ 1. Fork it
166
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
167
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
168
+ 4. Push to the branch (`git push origin my-new-feature`)
169
+ 5. Create new Pull Request
@@ -0,0 +1,4 @@
1
+ require 'rspec/core/rake_task'
2
+ require 'bundler/gem_tasks'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'flexkey/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'flexkey'
8
+ spec.version = Flexkey::VERSION
9
+ spec.authors = ['Devin McCabe']
10
+ spec.email = ['devin.mccabe@gmail.com']
11
+ spec.description = %q{Flexible product key generation}
12
+ spec.summary = %q{Use Flexkey to generate random product keys for use in software licenses,
13
+ invoices, etc.}
14
+ spec.homepage = 'https://github.com/dpmccabe/flexkey'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ['lib']
21
+
22
+ spec.add_development_dependency 'bundler', '~> 1.3'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ spec.add_development_dependency 'rspec-its'
26
+ end
@@ -0,0 +1,3 @@
1
+ require 'flexkey/version'
2
+ require 'flexkey/generator'
3
+ require 'flexkey/char_pool'
@@ -0,0 +1,108 @@
1
+ module Flexkey
2
+ class CharPoolError < StandardError; end
3
+
4
+ module CharPool
5
+ extend self
6
+
7
+ # Generates an array of characters of the specified types and proportions
8
+ #
9
+ # @param arg [Hash{ Symbol, String => Fixnum }, Symbol, String] a single character type or a
10
+ # hash of several character types and their proportions
11
+ #
12
+ # @return [Array<String>] the requested character pool
13
+ #
14
+ # @example
15
+ # Flexkey::CharPool.generate(:numeric)
16
+ # Flexkey::CharPool.generate('XYZ01234')
17
+ # Flexkey::CharPool.generate({ numeric: 0.25, alpha_upper: 0.75 })
18
+ # Flexkey::CharPool.generate({ alpha_lower_clear: 1, alpha_upper_clear: 3 })
19
+ # Flexkey::CharPool.generate({ 'AEIOU' => 1, 'BCDFGHJKLMNPQRSTVWXYZ' => 1, symbol: 0.5 })
20
+ def generate(arg)
21
+ if arg.is_a?(Hash)
22
+ # Extract character types and proportions.
23
+ char_arrays = arg.keys.map { |t| single_pool(t) }
24
+ char_props = arg.values.each do |p|
25
+ raise CharPoolError.new("Invalid char_pool proportion #{p.inspect}") unless
26
+ p.is_a?(Numeric) && p >= 0
27
+ end
28
+
29
+ # Standardize the proportions if they don't sum to 1.
30
+ total_prop = char_props.inject(:+).to_f
31
+ char_props = char_props.map { |prop| prop / total_prop }
32
+
33
+ multiple_pool(char_arrays, char_props)
34
+ else
35
+ single_pool(arg)
36
+ end
37
+ end
38
+
39
+ # Provides a list of the available built-in character pool types.
40
+ #
41
+ # @return [Array<Symbol>] the list of character pool types
42
+ #
43
+ # @example
44
+ # Flexkey::CharPool.available_char_types
45
+ def available_char_types
46
+ character_types.keys
47
+ end
48
+
49
+ # Provides a list of the available built-in character pool types with the characters of each
50
+ # type.
51
+ #
52
+ # @return [Hash{ Symbol => String }] a hash of character pool types and their characters
53
+ #
54
+ # @example
55
+ # Flexkey::CharPool.available_char_pools
56
+ def available_char_pools
57
+ character_types.inject({}) { |acc, (k, v)| acc[k] = v.join; acc }
58
+ end
59
+
60
+ private
61
+
62
+ def character_types
63
+ {
64
+ alpha_upper: 'A'.upto('Z').to_a,
65
+ alpha_lower: 'a'.upto('z').to_a,
66
+ numeric: '0'.upto('9').to_a,
67
+ alpha_upper_clear: 'A'.upto('Z').to_a - 'IOS'.chars.to_a,
68
+ alpha_lower_clear: 'a'.upto('z').to_a - 'ilos'.chars.to_a,
69
+ numeric_clear: '2346789'.chars.to_a,
70
+ symbol: "!@#\$%^&*;:()_+-=[]{}\\|'\",.<>/?".chars.to_a,
71
+ basic_symbol: '!@#$%^&*;:'.chars.to_a
72
+ }
73
+ end
74
+
75
+ # Return an array of characters from either a custom pool or a built-in type
76
+ def single_pool(char_type)
77
+ if char_type.respond_to?(:chars)
78
+ if char_type.empty?
79
+ raise CharPoolError.new('A custom char_pool was blank')
80
+ else
81
+ char_type.chars.to_a
82
+ end
83
+ elsif character_types.keys.include?(char_type)
84
+ character_types[char_type]
85
+ else
86
+ raise CharPoolError.new("Invalid char_pool #{char_type.inspect}")
87
+ end
88
+ end
89
+
90
+ # Return a flat array of characters from the provided character pools and proportions
91
+ def multiple_pool(char_arrays, char_props)
92
+ # Compute LCM of sizes of char type arrays
93
+ char_array_sizes = char_arrays.map(&:size)
94
+ char_array_sizes_lcm = char_array_sizes.inject(:lcm)
95
+
96
+ # Compute approximate number of multiples needed for each char type array
97
+ char_array_multiples = char_array_sizes.zip(char_props).map { |arr|
98
+ (char_array_sizes_lcm / arr[0] * arr[1] * 100).round }
99
+
100
+ # Construct combined array of all char types with correct proportions
101
+ char_array_multiples_gcd = char_array_multiples.inject(:gcd)
102
+ char_arrays_mult = char_arrays.zip(char_array_multiples).map { |arr|
103
+ arr[0] * (arr[1] / char_array_multiples_gcd) }
104
+
105
+ char_arrays_mult.flatten
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,120 @@
1
+ module Flexkey
2
+ class GeneratorError < StandardError; end
3
+
4
+ class Generator
5
+ # @return [String] the format of the keys to be generated
6
+ attr_accessor :format
7
+
8
+ # @return [Hash{ String => Symbol, String }] a pool of available character types specified in
9
+ # the format
10
+ attr_accessor :char_pool
11
+
12
+ # @return [Fixnum] the number of possible keys for a Flexkey instance with given format and
13
+ # character pool
14
+ attr_reader :n_possible_keys
15
+
16
+ # Initializes and validates a new Flexkey key generator.
17
+ #
18
+ # @param args [Hash{ Symbol => String, Hash{ String => Symbol, String }}] the format of the keys
19
+ # to be generated and a pool of available character types specified in the format
20
+ #
21
+ # @return [Flexkey] the Flexkey instance
22
+ #
23
+ # @example
24
+ # Flexkey::Generator.new(format: 'nnn-aaa',
25
+ # char_pool: { 'n' => :numeric, 'a' => :alpha_upper_clear })
26
+ # Flexkey::Generator.new(format: 'ccccc-ccccc',
27
+ # char_pool: { 'c' => { alpha_upper: 0.75, alpha_lower: 0.25 } })
28
+ # Flexkey::Generator.new(format: 'key_#######', char_pool: { '#' => :numeric_clear })
29
+ # Flexkey::Generator.new(format: 'a-nnnn', char_pool: { 'a' => :alpha_upper, 'n' => '12345' })
30
+ def initialize(args = {})
31
+ @format, @char_pool = args[:format], args[:char_pool]
32
+ validate!
33
+ set_char_pool
34
+ calculate_n_possible_keys
35
+ end
36
+
37
+ def format=(new_format)
38
+ @format = new_format
39
+ validate!
40
+ calculate_n_possible_keys
41
+ end
42
+
43
+ def char_pool=(new_char_pool)
44
+ @char_pool = new_char_pool
45
+ validate!
46
+ set_char_pool
47
+ calculate_n_possible_keys
48
+ end
49
+
50
+ # Generates a single key or an array of `n` keys.
51
+ #
52
+ # @param n [Integer] the number of keys to generate
53
+ #
54
+ # @return [String] a single key
55
+ # @return [Array<String>] `n` keys
56
+ #
57
+ # @example
58
+ # fk = Flexkey::Generator.new(format: 'nnn-aaa',
59
+ # char_pool: { 'n' => :numeric, 'a' => :alpha_upper_clear })
60
+ # fk.generate
61
+ # fk.generate(10)
62
+ def generate(n = 1)
63
+ validate_n!(n)
64
+
65
+ if n == 1
66
+ generate_one
67
+ elsif n > 1
68
+ keys = []
69
+ new_key = nil
70
+
71
+ n.times do
72
+ loop do
73
+ new_key = generate_one
74
+ break unless keys.include?(new_key)
75
+ end
76
+
77
+ keys << new_key
78
+ end
79
+
80
+ keys
81
+ end
82
+ end
83
+
84
+ def inspect
85
+ %Q{#<#{self.class} @format="#{format}", @char_pool=#{@char_pool}, } +
86
+ %Q{@n_possible_keys=#{@n_possible_keys}>}
87
+ end
88
+ alias to_s inspect
89
+
90
+ private
91
+
92
+ def validate!
93
+ raise GeneratorError.new('format is required') if @format.nil? || @format.empty?
94
+ raise GeneratorError.new('char_pool is required') if @char_pool.nil? || @char_pool.empty?
95
+ raise GeneratorError.new('char_pool letters must each be strings of length 1') unless
96
+ @char_pool.keys.all? { |letter| letter.is_a?(String) && letter.length == 1 }
97
+ raise GeneratorError.new('No char_pool letters present in format') if
98
+ (@format.chars.to_a & @char_pool.keys).empty?
99
+ end
100
+
101
+ def set_char_pool
102
+ @_char_pool = @char_pool.inject({}) { |h, (k, v)| h[k] = CharPool.generate(v); h }
103
+ end
104
+
105
+ def calculate_n_possible_keys
106
+ @n_possible_keys = @format.chars.to_a.map do |c|
107
+ @_char_pool[c].nil? ? 1 : @_char_pool[c].size
108
+ end.inject(:*)
109
+ end
110
+
111
+ def generate_one
112
+ @format.chars.to_a.map { |c| @_char_pool[c].nil? ? c : @_char_pool[c].sample }.join
113
+ end
114
+
115
+ def validate_n!(n)
116
+ raise GeneratorError.new("There are only #{@n_possible_keys} possible keys") if
117
+ n > @n_possible_keys
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,3 @@
1
+ module Flexkey
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ module CharPoolSpecHelpers
4
+ def cpj(arg)
5
+ Flexkey::CharPool.generate(arg).join
6
+ end
7
+ end
8
+
9
+ describe Flexkey::CharPool do
10
+ include CharPoolSpecHelpers
11
+
12
+ describe 'utility methods' do
13
+ it 'should provide a list of available character types' do
14
+ expect(Flexkey::CharPool.available_char_types).to eq([
15
+ :alpha_upper, :alpha_lower, :numeric, :alpha_upper_clear, :alpha_lower_clear,
16
+ :numeric_clear, :symbol, :basic_symbol])
17
+ end
18
+
19
+ it 'should provide a list of character pools for available character types' do
20
+ expect(Flexkey::CharPool.available_char_pools).to eq({
21
+ alpha_upper: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
22
+ alpha_lower: 'abcdefghijklmnopqrstuvwxyz',
23
+ numeric: '0123456789',
24
+ alpha_upper_clear: 'ABCDEFGHJKLMNPQRTUVWXYZ',
25
+ alpha_lower_clear: 'abcdefghjkmnpqrtuvwxyz',
26
+ numeric_clear: '2346789',
27
+ symbol: "!@\#$%^&*;:()_+-=[]{}\\|'\",.<>/?",
28
+ basic_symbol: "!@\#$%^&*;:"
29
+ })
30
+ end
31
+ end
32
+
33
+ context 'when a single type is requested' do
34
+ describe 'validations' do
35
+ it 'raises an exception for an unknown character pool symbol' do
36
+ expect {
37
+ Flexkey::CharPool.generate(:bad)
38
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool :bad')
39
+ end
40
+
41
+ it 'raises an exception for an nil character pool symbol' do
42
+ expect {
43
+ Flexkey::CharPool.generate(nil)
44
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool nil')
45
+ end
46
+
47
+ it 'raises an exception for a non-symbol type' do
48
+ expect {
49
+ Flexkey::CharPool.generate(123)
50
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool 123')
51
+ end
52
+
53
+ it 'raises an exception for a blank custom type' do
54
+ expect {
55
+ Flexkey::CharPool.generate('')
56
+ }.to raise_error(Flexkey::CharPoolError, 'A custom char_pool was blank')
57
+ end
58
+ end
59
+
60
+ it 'generates an uppercase alphabetical character pool' do
61
+ expect(cpj(:alpha_upper)).to eq('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
62
+ end
63
+
64
+ it 'generates a lowercase alphabetical character pool' do
65
+ expect(cpj(:alpha_lower)).to eq('abcdefghijklmnopqrstuvwxyz')
66
+ end
67
+
68
+ it 'generates a numeric character pool' do
69
+ expect(cpj(:numeric)).to eq('0123456789')
70
+ end
71
+
72
+ it 'generates an uppercase and clear alphabetical character pool' do
73
+ expect(cpj(:alpha_upper_clear)).to eq('ABCDEFGHJKLMNPQRTUVWXYZ')
74
+ end
75
+
76
+ it 'generates a lowercase and clear alphabetical character pool' do
77
+ expect(cpj(:alpha_lower_clear)).to eq('abcdefghjkmnpqrtuvwxyz')
78
+ end
79
+
80
+ it 'generates a numeric and clear character pool' do
81
+ expect(cpj(:numeric_clear)).to eq('2346789')
82
+ end
83
+
84
+ it 'generates a full symbol character pool' do
85
+ expect(cpj(:symbol)).to eq("!@#\$%^&*;:()_+-=[]{}\\|'\",.<>/?")
86
+ end
87
+
88
+ it 'generates a basic symbol character pool' do
89
+ expect(cpj(:basic_symbol)).to eq('!@#$%^&*;:')
90
+ end
91
+
92
+ it 'generates a custom character pool' do
93
+ expect(cpj('ABC123')).to eq('ABC123')
94
+ end
95
+ end
96
+
97
+ context 'when multiple types are requested' do
98
+ describe 'validations' do
99
+ it 'raises an exception for an unknown character pool symbol' do
100
+ expect {
101
+ Flexkey::CharPool.generate({ alpha_upper: 0.5, bad: 0.5 })
102
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool :bad')
103
+ end
104
+
105
+ it 'raises an exception for a non-symbol type' do
106
+ expect {
107
+ Flexkey::CharPool.generate({ alpha_upper: 0.5, 123 => 0.5 })
108
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool 123')
109
+ end
110
+
111
+ it 'raises an exception for a negative proportion' do
112
+ expect {
113
+ Flexkey::CharPool.generate({ alpha_upper: 0.5, numeric: -0.5 })
114
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool proportion -0.5')
115
+ end
116
+
117
+ it 'raises an exception for a non-numeric proportion' do
118
+ expect {
119
+ Flexkey::CharPool.generate({ alpha_upper: 0.5, numeric: 'test' })
120
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool proportion "test"')
121
+ end
122
+
123
+ it 'raises an exception for missing proportion' do
124
+ expect {
125
+ Flexkey::CharPool.generate({ alpha_upper: 0.5, numeric: nil })
126
+ }.to raise_error(Flexkey::CharPoolError, 'Invalid char_pool proportion nil')
127
+ end
128
+ end
129
+
130
+ it 'generates a pool of upper and lowercase alphabetical types in equal proportions' do
131
+ expect(cpj({ alpha_upper: 0.5, alpha_lower: 0.5 })).to eq(
132
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
133
+ end
134
+
135
+ it 'generates a pool of upper and lowercase alphabetical types in a 1:1 proportion' do
136
+ expect(cpj({ alpha_upper: 1, alpha_lower: 1 })).to eq(
137
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
138
+ end
139
+
140
+ it 'generates a pool of upper and lowercase alphabetical types in a 3:1 proportion' do
141
+ expect(cpj({ alpha_upper: 0.75, alpha_lower: 0.25 })).to eq(
142
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' +
143
+ 'abcdefghijklmnopqrstuvwxyz')
144
+ end
145
+
146
+ it 'generates a pool of upper and lowercase alphabetical types in a 3:1 proportion' do
147
+ expect(cpj({ alpha_upper: 3, alpha_lower: 1 })).to eq(
148
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' +
149
+ 'abcdefghijklmnopqrstuvwxyz')
150
+ end
151
+
152
+ it 'generates a pool of upper and lowercase alphabetical types in a 1:3 proportion' do
153
+ expect(cpj({ alpha_upper: 0.25, alpha_lower: 0.75 })).to eq(
154
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz' +
155
+ 'abcdefghijklmnopqrstuvwxyz')
156
+ end
157
+
158
+ it 'generates a pool of upper and lowercase alphabetical types in a 0:1 proportion' do
159
+ expect(cpj({ alpha_upper: 0, alpha_lower: 99 })).to eq('abcdefghijklmnopqrstuvwxyz')
160
+ end
161
+
162
+ it 'generates a pool of upper and lowercase alphabetical types in a 0.2:0.2 proportion' do
163
+ expect(cpj({ alpha_upper: 0.2, alpha_lower: 0.2 })).to eq(
164
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
165
+ end
166
+
167
+ it 'generates a pool of uppercase and numeric character types in equal proportions' do
168
+ expect(cpj({ alpha_upper: 0.5, numeric: 0.5 })).to eq(
169
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' +
170
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ0' +
171
+ '1234567890123456789012345678901234567890123456789012345678901234567890123456789' +
172
+ '01234567890123456789012345678901234567890123456789')
173
+ end
174
+
175
+ it 'generates a pool of numeric and a custom type in equal proportions' do
176
+ expect(cpj({ numeric: 0.5, 'ABCDE' => 0.5 })).to eq('0123456789ABCDEABCDE')
177
+ end
178
+
179
+ it 'generates a pool of uppercase and another custom type in equal proportions' do
180
+ expect(cpj({ alpha_upper: 1, '0123456789!@#' => 1 })).to eq(
181
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#0123456789!@#')
182
+ end
183
+
184
+ it 'generates a pool of two custom types in a 1:4 proportion' do
185
+ expect(cpj({ 'ABCD' => 1, 'WXYZ' => 4 })).to eq('ABCDWXYZWXYZWXYZWXYZ')
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ describe Flexkey::Generator do
4
+ describe '#new' do
5
+ describe 'validations' do
6
+ it 'raises an exception when format is missing' do
7
+ expect {
8
+ Flexkey::Generator.new(char_pool: { 'n' => :numeric })
9
+ }.to raise_error(Flexkey::GeneratorError, 'format is required')
10
+ end
11
+
12
+ it 'raises an exception when format is blank' do
13
+ expect {
14
+ Flexkey::Generator.new(format: '', char_pool: { 'n' => :numeric })
15
+ }.to raise_error(Flexkey::GeneratorError, 'format is required')
16
+ end
17
+
18
+ it 'raises an exception when char_pool is missing' do
19
+ expect {
20
+ Flexkey::Generator.new(format: 'nnn')
21
+ }.to raise_error(Flexkey::GeneratorError, 'char_pool is required')
22
+ end
23
+
24
+ it 'raises an exception when char_pool has non-string letters' do
25
+ expect {
26
+ Flexkey::Generator.new(format: 'nnn', char_pool: { 123 => :numeric })
27
+ }.to raise_error(Flexkey::GeneratorError,
28
+ 'char_pool letters must each be strings of length 1')
29
+ end
30
+
31
+ it 'raises an exception when char_pool has letters longer than 1 character' do
32
+ expect {
33
+ Flexkey::Generator.new(format: 'nnn', char_pool: { 'nn' => :numeric })
34
+ }.to raise_error(Flexkey::GeneratorError,
35
+ 'char_pool letters must each be strings of length 1')
36
+ end
37
+
38
+ it 'raises an exception when char_pool has blank letters' do
39
+ expect {
40
+ Flexkey::Generator.new(format: 'nnn', char_pool: { '' => :numeric })
41
+ }.to raise_error(Flexkey::GeneratorError,
42
+ 'char_pool letters must each be strings of length 1')
43
+ end
44
+
45
+ it 'raises an exception when no char_pool letters found in format' do
46
+ expect {
47
+ Flexkey::Generator.new(format: 'nnn', char_pool: { 'a' => :numeric })
48
+ }.to raise_error(Flexkey::GeneratorError, 'No char_pool letters present in format')
49
+ end
50
+ end
51
+
52
+ context 'when a valid format and char_pool is provided' do
53
+ subject { Flexkey::Generator.new(format: 'nnn', char_pool: { 'n' => :numeric }) }
54
+
55
+ its(:format) { is_expected.to eq('nnn') }
56
+ its(:char_pool) { is_expected.to eq({ 'n' => :numeric }) }
57
+ its(:n_possible_keys) { is_expected.to eq(10**3) }
58
+ end
59
+ end
60
+
61
+ describe 'accessors' do
62
+ let(:key_gen) {
63
+ Flexkey::Generator.new(format: 'nnn-aaa', char_pool: {
64
+ 'n' => :numeric, 'a' => :alpha_upper }) }
65
+
66
+ it 'updates the format' do
67
+ key_gen.format = 'aaa-nnn'
68
+ expect(key_gen.format).to eq('aaa-nnn')
69
+ end
70
+
71
+ it 'updates the char_pool' do
72
+ key_gen.char_pool = { 'n' => :numeric, 'a' => :alpha_lower }
73
+ expect(key_gen.char_pool).to eq({ 'n' => :numeric, 'a' => :alpha_lower })
74
+ end
75
+
76
+ it 'updates the number of possible keys' do
77
+ key_gen.format = 'aaaa-nnnn-0000'
78
+ key_gen.char_pool = { 'n' => :numeric, 'a' => :alpha_lower_clear }
79
+ expect(key_gen.n_possible_keys).to eq(10**4 * 22**4)
80
+ end
81
+ end
82
+
83
+ describe '#generate' do
84
+ context 'when a single key is requested' do
85
+ context 'with alphabetical and numeric character types' do
86
+ subject { Flexkey::Generator.new(format: 'aaaaa-nnnn', char_pool: {
87
+ 'a' => :alpha_lower, 'n' => :numeric }).generate }
88
+
89
+ it { is_expected.not_to be_nil }
90
+ it { is_expected.to match(/^[a-z]{5}-\d{4}$/) }
91
+ end
92
+
93
+ context 'with all character types' do
94
+ subject { Flexkey::Generator.new(format: 'u-l-n-v-m-o-s-b', char_pool: {
95
+ 'u' => :alpha_upper, 'l' => :alpha_lower, 'n' => :numeric, 'v' => :alpha_upper_clear,
96
+ 'm' => :alpha_lower_clear, 'o' => :numeric_clear, 's' => :symbol, 'b' => :basic_symbol
97
+ }).generate }
98
+
99
+ it { is_expected.to match(%r{
100
+ ^[A-Z]-[a-z]-[0-9]-[ABCDEFGHJKLMNPQRTUVWXYZ]-[abcdefghjkmnpqrtuvwxyz]-
101
+ [2346789]-[!@#\$%\^&*;:\(\)_\+-=\[\]{}\\\|'",.<>\/?]-[!@#\$%\^&\*;\:]$
102
+ }x) }
103
+ end
104
+ end
105
+
106
+ context 'when the format and char_pool change' do
107
+ let(:key_gen) { Flexkey::Generator.new(format: 'aaa-nnn', char_pool: {
108
+ 'n' => :numeric, 'a' => :alpha_upper }) }
109
+
110
+ it 'generates a key with the new format and char_pool' do
111
+ key_gen.format = 'nnn-aaa'
112
+ key_gen.char_pool = { 'n' => :numeric, 'a' => :alpha_lower }
113
+ expect(key_gen.generate).to match(/^\d{3}-[a-z]{3}$/)
114
+ end
115
+ end
116
+
117
+ context 'when multiple keys are requested' do
118
+ let(:new_keys) { Flexkey::Generator.new(format: 'aaaaa-nnnn/cc', char_pool: {
119
+ 'a' => :alpha_lower, 'n' => :numeric, 'c' => 'LMN' }).generate(5) }
120
+
121
+ it 'generates the specified number of keys' do
122
+ expect(new_keys.size).to eq(5)
123
+ end
124
+
125
+ it 'generates unique keys' do
126
+ expect(new_keys.uniq.size).to eq(5)
127
+ end
128
+
129
+ it 'generates keys with the specified format' do
130
+ expect(new_keys.count { |key| key =~ /^[a-z]{5}-\d{4}\/[LMN]{2}$/ }).to eq(5)
131
+ end
132
+ end
133
+
134
+ it 'raises an exception when too many keys are requested' do
135
+ expect {
136
+ Flexkey::Generator.new(format: 'nnn', char_pool: { 'n' => :numeric }).generate(1001)
137
+ }.to raise_error(Flexkey::GeneratorError, "There are only 1000 possible keys")
138
+ end
139
+ end
140
+
141
+ it 'allows inspection of an instance' do
142
+ fk = Flexkey::Generator.new(format: 'nnn', char_pool: { 'n' => :numeric })
143
+ expect(fk.inspect).to eq(%q{#<Flexkey::Generator @format="nnn", @char_pool={"n"=>:numeric}, } +
144
+ %q{@n_possible_keys=1000>})
145
+ end
146
+
147
+ it 'displays an instance as a string' do
148
+ fk = Flexkey::Generator.new(format: 'nnn', char_pool: { 'n' => :numeric })
149
+ expect(fk.to_s).to eq(%q{#<Flexkey::Generator @format="nnn", @char_pool={"n"=>:numeric}, } +
150
+ %q{@n_possible_keys=1000>})
151
+ end
152
+ end
@@ -0,0 +1,3 @@
1
+ require 'rspec'
2
+ require 'rspec/its'
3
+ require 'flexkey'
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flexkey
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 1.0.0
6
+ platform: ruby
7
+ authors:
8
+ - Devin McCabe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-06-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ type: :development
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '1.3'
22
+ none: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.3'
28
+ none: false
29
+ prerelease: false
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ type: :development
33
+ requirement: !ruby/object:Gem::Requirement
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ none: false
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ none: false
45
+ prerelease: false
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ type: :development
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ none: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ none: false
61
+ prerelease: false
62
+ - !ruby/object:Gem::Dependency
63
+ name: rspec-its
64
+ type: :development
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ none: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ! '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ none: false
77
+ prerelease: false
78
+ description: Flexible product key generation
79
+ email:
80
+ - devin.mccabe@gmail.com
81
+ executables: []
82
+ extensions: []
83
+ extra_rdoc_files: []
84
+ files:
85
+ - .gitignore
86
+ - .yardopts
87
+ - Gemfile
88
+ - LICENSE.txt
89
+ - README.md
90
+ - Rakefile
91
+ - flexkey.gemspec
92
+ - lib/flexkey.rb
93
+ - lib/flexkey/char_pool.rb
94
+ - lib/flexkey/generator.rb
95
+ - lib/flexkey/version.rb
96
+ - spec/flexkey/char_pool_spec.rb
97
+ - spec/flexkey/generator_spec.rb
98
+ - spec/spec_helper.rb
99
+ homepage: https://github.com/dpmccabe/flexkey
100
+ licenses:
101
+ - MIT
102
+ post_install_message:
103
+ rdoc_options: []
104
+ require_paths:
105
+ - lib
106
+ required_ruby_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ segments:
111
+ - 0
112
+ hash: 3377894280690752207
113
+ version: '0'
114
+ none: false
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ segments:
120
+ - 0
121
+ hash: 3377894280690752207
122
+ version: '0'
123
+ none: false
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 1.8.23
127
+ signing_key:
128
+ specification_version: 3
129
+ summary: Use Flexkey to generate random product keys for use in software licenses,
130
+ invoices, etc.
131
+ test_files:
132
+ - spec/flexkey/char_pool_spec.rb
133
+ - spec/flexkey/generator_spec.rb
134
+ - spec/spec_helper.rb