flexkey 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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