memorable_strings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,25 @@
1
+ == master
2
+
3
+ == 0.1.0 / 2009-04-18
4
+
5
+ * Add :print_friendly option to skip ambiguous characters [Nate Kontny]
6
+ * Add better tests
7
+ * Add more documentation
8
+ * Redesign phoneme / consonant / vowel / digit logic to be in separate classes
9
+ * Move Password.generate to String.memorable
10
+ * Rename to memorable_strings
11
+
12
+ == 0.0.3 / 2009-01-11
13
+
14
+ * Add compatibility with Ruby 1.9+
15
+
16
+ == 0.0.2 / 2008-05-05
17
+
18
+ * Update documentation
19
+
20
+ == 0.0.1 / 2007-09-26
21
+
22
+ * Add documentation
23
+ * Fix licensing
24
+ * Add unit tests
25
+ * Rename to password_generator
data/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ Copyright (C) 2002-2006 Ian Macdonald, 2006-2009 Aaron Pfeifer
2
+
3
+ This program is free software; you can redistribute it and/or modify
4
+ it under the terms of the GNU General Public License as published by
5
+ the Free Software Foundation; either version 2, or (at your option)
6
+ any later version.
7
+
8
+ This program is distributed in the hope that it will be useful,
9
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ GNU General Public License for more details.
12
+
13
+ You should have received a copy of the GNU General Public License
14
+ along with this program; if not, write to the Free Software Foundation,
15
+ Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
data/README.rdoc ADDED
@@ -0,0 +1,49 @@
1
+ = memorable_strings
2
+
3
+ +memorable_strings+ generates strings that can be easily remembered.
4
+
5
+ == Resources
6
+
7
+ API
8
+
9
+ * http://api.pluginaweek.org/memorable_strings
10
+
11
+ Bugs
12
+
13
+ * http://pluginaweek.lighthouseapp.com/projects/13282-memorable_strings
14
+
15
+ Development
16
+
17
+ * http://github.com/pluginaweek/memorable_strings
18
+
19
+ Source
20
+
21
+ * git://github.com/pluginaweek/memorable_strings.git
22
+
23
+ == Description
24
+
25
+ Certain strings like passwords are often difficult to remember, especially if
26
+ they are simply a random combination of letters and numbers. With
27
+ +memorable_strings+, strings are generated based on a set of rules that
28
+ combine phonemes in a manner that makes the sequence of characters more
29
+ memorable. When used in combination with features like generating passwords,
30
+ this can help improve the user experience.
31
+
32
+ == Usage
33
+
34
+ String.memorable # => "maikipeo"
35
+ String.memorable(:length => 5) # => "quoge"
36
+ String.memorable(:capital => true) # => "Bukievai"
37
+ String.memorable(:digit => true) # => "ood4yosa"
38
+ String.memorable(:capital => true, :digit => true) # => "Goodah5e"
39
+ String.memorable(:print_friendly => true) # => "ahkeehav"
40
+
41
+ See MemorableStrings::Extensions::String for more information.
42
+
43
+ == Dependencies
44
+
45
+ None.
46
+
47
+ == References
48
+
49
+ * Ian Macdonald - {ruby-password}[http://raa.ruby-lang.org/project/ruby-password]
data/Rakefile ADDED
@@ -0,0 +1,88 @@
1
+ require 'rake/testtask'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/sshpublisher'
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = 'memorable_strings'
8
+ s.version = '0.1.0'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Generates strings that can be easily remembered'
11
+
12
+ s.files = FileList['{lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc)
13
+ s.require_path = 'lib'
14
+ s.has_rdoc = true
15
+ s.test_files = Dir['test/**/*_test.rb']
16
+
17
+ s.author = 'Aaron Pfeifer'
18
+ s.email = 'aaron@pluginaweek.org'
19
+ s.homepage = 'http://www.pluginaweek.org'
20
+ s.rubyforge_project = 'pluginaweek'
21
+ end
22
+
23
+ desc 'Default: run all tests.'
24
+ task :default => :test
25
+
26
+ desc "Test the #{spec.name} plugin."
27
+ Rake::TestTask.new(:test) do |t|
28
+ t.libs << 'lib'
29
+ t.test_files = spec.test_files
30
+ t.verbose = true
31
+ end
32
+
33
+ begin
34
+ require 'rcov/rcovtask'
35
+ namespace :test do
36
+ desc "Test the #{spec.name} plugin with Rcov."
37
+ Rcov::RcovTask.new(:rcov) do |t|
38
+ t.libs << 'lib'
39
+ t.test_files = spec.test_files
40
+ t.rcov_opts << '--exclude="^(?!lib/)"'
41
+ t.verbose = true
42
+ end
43
+ end
44
+ rescue LoadError
45
+ end
46
+
47
+ desc "Generate documentation for the #{spec.name} plugin."
48
+ Rake::RDocTask.new(:rdoc) do |rdoc|
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = spec.name
51
+ rdoc.template = '../rdoc_template.rb'
52
+ rdoc.options << '--line-numbers' << '--inline-source'
53
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb')
54
+ end
55
+
56
+ Rake::GemPackageTask.new(spec) do |p|
57
+ p.gem_spec = spec
58
+ p.need_tar = true
59
+ p.need_zip = true
60
+ end
61
+
62
+ desc 'Publish the beta gem.'
63
+ task :pgem => [:package] do
64
+ Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
65
+ end
66
+
67
+ desc 'Publish the API documentation.'
68
+ task :pdoc => [:rdoc] do
69
+ Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
70
+ end
71
+
72
+ desc 'Publish the API docs and gem'
73
+ task :publish => [:pgem, :pdoc, :release]
74
+
75
+ desc 'Publish the release files to RubyForge.'
76
+ task :release => [:gem, :package] do
77
+ require 'rubyforge'
78
+
79
+ ruby_forge = RubyForge.new.configure
80
+ ruby_forge.login
81
+
82
+ %w(gem tgz zip).each do |ext|
83
+ file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
84
+ puts "Releasing #{File.basename(file)}..."
85
+
86
+ ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
87
+ end
88
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'memorable_strings'
@@ -0,0 +1,21 @@
1
+ require 'memorable_strings/phoneme'
2
+
3
+ module MemorableStrings
4
+ # Represents a phoneme that begins with a consonant letter
5
+ class Consonant < Phoneme
6
+ # Generates a phoneme of at most the given length that should follow this
7
+ # consononant.
8
+ #
9
+ # This will always generate a vowel.
10
+ def next(stack, maxlength, &block)
11
+ Vowel.random(maxlength, &block)
12
+ end
13
+
14
+ # Bootstrap
15
+ add %w(c f h j k m n p r t v w x y ch ph th)
16
+ add %w(b d g q s z qu sh), :print_friendly => :downcase
17
+ add :l, :print_friendly => false
18
+ add :ng, :first => false
19
+ add :gh, :first => false, :print_friendly => :downcase
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ require 'memorable_strings/phoneme'
2
+
3
+ module MemorableStrings
4
+ # Represents a single-digit character
5
+ class Digit < Phoneme
6
+ # Generates a phoneme of at most the given length that should follow this
7
+ # digit.
8
+ #
9
+ # This will always randomly generate either a vowel or a consonant.
10
+ def next(stack, maxlength, &block)
11
+ Phoneme.first(maxlength, &block)
12
+ end
13
+
14
+ # Bootstrap
15
+ add %w(3 4 7 9)
16
+ add %w(0 1 2 5 6 8), :print_friendly => false
17
+ end
18
+ end
@@ -0,0 +1,91 @@
1
+ require 'memorable_strings/phoneme'
2
+ require 'memorable_strings/digit'
3
+ require 'memorable_strings/vowel'
4
+ require 'memorable_strings/consonant'
5
+
6
+ module MemorableStrings
7
+ module Extensions #:nodoc:
8
+ # Adds support for easily generating memorable strings
9
+ module String
10
+ # Generates a string based on a set of rules defining which characters
11
+ # should be grouped together to form a memorable sequence.
12
+ #
13
+ # Configuration options:
14
+ # * <tt>:length</tt> - The length of the string to generate. Default is 8.
15
+ # * <tt>:capital</tt> - Whether to include a capital letter. Default is false.
16
+ # * <tt>:digit</tt> - Whether to include a digit. Default is false.
17
+ # * <tt>:print_friendly</tt> - Whether to only include characters that
18
+ # are unambiguous when printed out. This includes: B8G6I1l0OQDS5Z2
19
+ #
20
+ # == Examples
21
+ #
22
+ # String.memorable # => "maikipeo"
23
+ # String.memorable(:length => 5) # => "quoge"
24
+ # String.memorable(:capital => true) # => "Bukievai"
25
+ # String.memorable(:digit => true) # => "ood4yosa"
26
+ # String.memorable(:capital => true, :digit => true) # => "Goodah5e"
27
+ #
28
+ # == Algorithm
29
+ #
30
+ # See MemorableStrings::Consonant, MemorableStrings::Vowel, and
31
+ # MemorableStrings::Digit for more information about how it's determined
32
+ # which characters are allowed to follow which other characters.
33
+ def memorable(options = {})
34
+ invalid_keys = options.keys - [:length, :capital, :digit, :print_friendly]
35
+ raise ArgumentError, "Invalid key(s): #{invalid_keys.join(', ')}" unless invalid_keys.empty?
36
+
37
+ length = options[:length] || 8
38
+ raise ArgumentError, 'Length must be at least 1' if length < 1
39
+ raise ArgumentError, 'Length must be at least 3 if using digits' if length < 3 && options[:digit]
40
+
41
+ value = nil
42
+ conditions = lambda {|phoneme| options[:print_friendly] ? phoneme.print_friendly?(:downcase) : true}
43
+
44
+ begin
45
+ value = ''
46
+ stack = []
47
+ left = length
48
+ flags = options.dup
49
+ phoneme = nil
50
+
51
+ begin
52
+ if !phoneme
53
+ # Special-case first
54
+ phoneme = Phoneme.first(left, &conditions)
55
+ elsif flags[:digit] && stack.length >= 2 && rand(10) < 3
56
+ # Add digit in >= 3rd spot (30% chance)
57
+ flags.delete(:digit)
58
+ phoneme = Digit.random(1, &conditions)
59
+ else
60
+ # Choose next based on current phoneme
61
+ phoneme = phoneme.next(stack, left, &conditions)
62
+ end
63
+
64
+ # Track the phoneme
65
+ stack << phoneme
66
+ value << phoneme.value
67
+ left -= phoneme.length
68
+
69
+ # Capitalize the first letter, phoneme after or a digit, or a consonant (30% chance)
70
+ if flags[:capital]
71
+ previous = stack[-2]
72
+ upcase_allowed = !options[:print_friendly] || phoneme.print_friendly?(:upcase)
73
+ context_allowed = !previous || previous.is_a?(Digit) || phoneme.is_a?(Consonant)
74
+
75
+ if upcase_allowed && context_allowed && rand(10) < 3
76
+ flags.delete(:capital)
77
+ value[-phoneme.length, 1] = value[-phoneme.length, 1].upcase!
78
+ end
79
+ end
80
+ end while left > 0
81
+ end while [:capital, :digit].any? {|key| flags.include?(key)}
82
+
83
+ value
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ String.class_eval do
90
+ extend MemorableStrings::Extensions::String
91
+ end
@@ -0,0 +1,116 @@
1
+ module MemorableStrings
2
+ # A unit of sound that can be used to build a larger string. Phonemes have
3
+ # no semantic meaning themselves, but have sound types and characteristics
4
+ # associated with them that are helpful in building a discernable word.
5
+ class Phoneme
6
+ class << self
7
+ # The collection of phonemes
8
+ attr_reader :all
9
+
10
+ # Adds a new phoneme of the current class's type.
11
+ #
12
+ # See Phoneme#new for more information.
13
+ def add(*values)
14
+ options = values.last.is_a?(Hash) ? values.pop : {}
15
+
16
+ values.flatten!
17
+ values.map! do |value|
18
+ (@all ||= []) << value = new(value, options)
19
+ value
20
+ end
21
+ values.length == 1 ? values.first : values
22
+ end
23
+
24
+ # Generates a random phoneme for the current class of at most the given
25
+ # length. In addition, an optional block can be used to determine
26
+ # whether the chosen phoneme is acceptable.
27
+ #
28
+ # == Examples
29
+ #
30
+ # # Choose any vowel
31
+ # MemorableStrings::Vowel.random
32
+ # # => #<MemorableStrings::Vowel:0xb7c3efe4 @first=true, @value="e", @length=1>
33
+ #
34
+ # # Choose a vowel with at most 1 character
35
+ # MemorableStrings::Vowel.random(2)
36
+ # # => <MemorableStrings::Vowel:0xb7c3eb34 @first=true, @value="u", @length=1>
37
+ #
38
+ # # Choose a vowel that can be the first letter
39
+ # MemorableStrings::Vowel.random {|vowel| vowel.first?}
40
+ # # => #<MemorableStrings::Vowel:0xb7c3e080 @first=true, @value="a", @length=1>
41
+ def random(maxlength = nil, &block)
42
+ phonemes = all
43
+ phonemes = phonemes.select {|phoneme| phoneme.length <= maxlength} if maxlength
44
+
45
+ begin
46
+ phoneme = phonemes[rand(phonemes.size)]
47
+ end while !phoneme.matches?(&block)
48
+
49
+ phoneme
50
+ end
51
+
52
+ # Generates a random phoneme of at most the given length. This will
53
+ # only randomly choose from the following sounds that are allowed to be
54
+ # the first character:
55
+ # * Vowel
56
+ # * Consonant
57
+ #
58
+ # == Examples
59
+ #
60
+ # MemorableStrings::Phoneme.first(1)
61
+ # #<MemorableStrings::Consonant:0xb7c38248 @first=true, @value="x", @length=1>
62
+ #
63
+ # MemorableStrings::Phoneme.first(2)
64
+ # #<MemorableStrings::Vowel:0xb7c3e2b0 @first=true, @value="ae", @length=2>
65
+ def first(maxlength, &block)
66
+ (rand(2) == 1 ? Vowel : Consonant).random(maxlength) do |phoneme|
67
+ phoneme.first? && phoneme.matches?(&block)
68
+ end
69
+ end
70
+ end
71
+
72
+ # The character(s) representing this phoneme
73
+ attr_reader :value
74
+
75
+ # The number of characters
76
+ attr_reader :length
77
+
78
+ # Creates a new phoneme with the given value and configuration options.
79
+ #
80
+ # Configuration options:
81
+ # * <tt>:first</strong> - Whether it can be used as the first value in a
82
+ # string. Default is true.
83
+ # * <tt>:print_friendly</strong> - Whether the characters are unambiguous
84
+ # when printed. This can be set to one of the following values:
85
+ # * <tt>true</tt> - Always print-friendly (default)
86
+ # * <tt>:downcase</tt> - Only print-friendly when in lower case
87
+ # * <tt>:upcase</tt> - Only print-friendly when in upper case
88
+ # * <tt>false</tt> - Never print-friendly
89
+ def initialize(value, options = {})
90
+ invalid_keys = options.keys - [:first, :print_friendly]
91
+ raise ArgumentError, "Invalid key(s): #{invalid_keys.join(', ')}" unless invalid_keys.empty?
92
+
93
+ options = {:first => true, :print_friendly => true}.merge(options)
94
+
95
+ @value = value.to_s
96
+ @length = @value.length
97
+ @first = options[:first]
98
+ @print_friendly = options[:print_friendly]
99
+ end
100
+
101
+ # Is this allowed to be the first in a sequence of phonemes?
102
+ def first?
103
+ @first
104
+ end
105
+
106
+ # Is this character unambiguous with other characters?
107
+ def print_friendly?(context)
108
+ @print_friendly == true || @print_friendly == context
109
+ end
110
+
111
+ # Does this phoneme match the conditions specified by the block?
112
+ def matches?
113
+ !block_given? || yield(self)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,29 @@
1
+ require 'memorable_strings/phoneme'
2
+
3
+ module MemorableStrings
4
+ # Represents a phoneme that begins with a vowel letter
5
+ class Vowel < Phoneme
6
+ # Generates a phoneme of at most the given length that should follow this
7
+ # vowel.
8
+ #
9
+ # If the last two characters in the stack (including this one) were
10
+ # vowels, then this will always generate a consonant. Otherwise, 40% of
11
+ # the time it will generate a single-character vowel and 60% of the time
12
+ # it will generate a consonant.
13
+ #
14
+ # It is never possible for two consecutive vowel phonemes to result in
15
+ # more than 2 vowel characters in the stack.
16
+ def next(stack, maxlength, &block)
17
+ previous = stack[-2]
18
+ if previous && previous.is_a?(Vowel) || length > 1 || rand(10) > 3
19
+ Consonant.random(1, &block)
20
+ else
21
+ Vowel.random(1, &block)
22
+ end
23
+ end
24
+
25
+ # Bootstrap
26
+ add %w(a e u ae ah ee)
27
+ add %w(i o ai ei ie oh oo), :print_friendly => false
28
+ end
29
+ end
@@ -0,0 +1,5 @@
1
+ require 'memorable_strings/extensions/string'
2
+
3
+ # Provides the ability to generate strings that can be easily remembered
4
+ module MemorableStrings
5
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ConsonantNextTest < Test::Unit::TestCase
4
+ def setup
5
+ setup_phonemes
6
+
7
+ MemorableStrings::Vowel.all << @a = MemorableStrings::Vowel.new(:a)
8
+ @b = MemorableStrings::Consonant.new(:b)
9
+ @stack = [@b]
10
+ end
11
+
12
+ def test_should_generate_a_vowel
13
+ assert_equal @a, @b.next(@stack, 2)
14
+ end
15
+
16
+ def test_should_allow_block_conditional
17
+ MemorableStrings::Vowel.all << @c = MemorableStrings::Vowel.new(:c)
18
+ MemorableStrings::Vowel.expects(:rand).with(2).times(2).returns(1, 0)
19
+
20
+ assert_equal @a, @b.next(@stack, 2) {|phoneme| phoneme.value == 'a'}
21
+ end
22
+
23
+ def teardown
24
+ teardown_phonemes
25
+ end
26
+ end
@@ -0,0 +1,41 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class DigitNextTest < Test::Unit::TestCase
4
+ def setup
5
+ setup_phonemes
6
+
7
+ @digit = MemorableStrings::Digit.new(1)
8
+ @stack = [@digit]
9
+ end
10
+
11
+ def test_should_generate_vowel_half_the_time
12
+ MemorableStrings::Vowel.all << @a = MemorableStrings::Vowel.new(:a)
13
+
14
+ MemorableStrings::Vowel.expects(:rand).at_least_once.returns(0)
15
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(1)
16
+
17
+ assert_equal @a, @digit.next(@stack, 2)
18
+ end
19
+
20
+ def test_should_generate_consonant_half_the_time
21
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
22
+
23
+ MemorableStrings::Consonant.expects(:rand).at_least_once.returns(0)
24
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
25
+
26
+ assert_equal @b, @digit.next(@stack, 2)
27
+ end
28
+
29
+ def test_should_allow_block_conditional
30
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
31
+ MemorableStrings::Consonant.all << @c = MemorableStrings::Vowel.new(:c)
32
+ MemorableStrings::Consonant.expects(:rand).with(2).times(2).returns(1, 0)
33
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
34
+
35
+ assert_equal @b, @digit.next(@stack, 2) {|phoneme| phoneme.value == 'b'}
36
+ end
37
+
38
+ def teardown
39
+ teardown_phonemes
40
+ end
41
+ end
@@ -0,0 +1,220 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class PhonemeByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @phoneme = MemorableStrings::Phoneme.new(:a)
6
+ end
7
+
8
+ def test_should_have_a_value
9
+ assert_equal 'a', @phoneme.value
10
+ end
11
+
12
+ def test_should_have_a_length
13
+ assert_equal 1, @phoneme.length
14
+ end
15
+
16
+ def test_should_be_allowed_to_be_first
17
+ assert @phoneme.first?
18
+ end
19
+
20
+ def test_should_be_print_friendly_with_downcase_context
21
+ assert @phoneme.print_friendly?(:downcase)
22
+ end
23
+
24
+ def test_should_be_print_friendly_with_upcase_context
25
+ assert @phoneme.print_friendly?(:upcase)
26
+ end
27
+ end
28
+
29
+ class PhonemeTest < Test::Unit::TestCase
30
+ def test_should_raise_exception_if_invalid_option_specified
31
+ assert_raise(ArgumentError) { MemorableStrings::Phoneme.new(:a, :invalid => true) }
32
+ end
33
+ end
34
+
35
+ class PhonemeWithMultipleCharactersTest < Test::Unit::TestCase
36
+ def setup
37
+ @phoneme = MemorableStrings::Phoneme.new(:ab)
38
+ end
39
+
40
+ def test_should_have_a_value
41
+ assert_equal 'ab', @phoneme.value
42
+ end
43
+
44
+ def test_should_have_a_length
45
+ assert_equal 2, @phoneme.length
46
+ end
47
+
48
+ def test_should_be_allowed_to_be_first
49
+ assert @phoneme.first?
50
+ end
51
+ end
52
+
53
+ class PhonemeNotFirstTest < Test::Unit::TestCase
54
+ def setup
55
+ @phoneme = MemorableStrings::Phoneme.new(:a, :first => false)
56
+ end
57
+
58
+ def test_should_not_be_allowed_to_be_first
59
+ assert !@phoneme.first?
60
+ end
61
+ end
62
+
63
+ class PhonemeNotPrintFriendlyTest < Test::Unit::TestCase
64
+ def setup
65
+ @phoneme = MemorableStrings::Phoneme.new(:i, :print_friendly => false)
66
+ end
67
+
68
+ def test_should_not_be_print_friendly_with_downcase_context
69
+ assert !@phoneme.print_friendly?(:downcase)
70
+ end
71
+
72
+ def test_should_not_be_print_friendly_with_upcase_context
73
+ assert !@phoneme.print_friendly?(:upcase)
74
+ end
75
+ end
76
+
77
+ class PhonemeContextPrintFriendlyTest < Test::Unit::TestCase
78
+ def setup
79
+ @phoneme = MemorableStrings::Phoneme.new(:b, :print_friendly => :downcase)
80
+ end
81
+
82
+ def test_should_be_print_friendly_with_downcase_context
83
+ assert @phoneme.print_friendly?(:downcase)
84
+ end
85
+
86
+ def test_should_not_be_print_friendly_with_upcase_context
87
+ assert !@phoneme.print_friendly?(:upcase)
88
+ end
89
+ end
90
+
91
+ class PhonemeMatchingTest < Test::Unit::TestCase
92
+ def setup
93
+ @phoneme = MemorableStrings::Phoneme.new(:a)
94
+ end
95
+
96
+ def test_should_match_if_no_block_given
97
+ assert @phoneme.matches?
98
+ end
99
+
100
+ def test_should_pass_self_into_block
101
+ context = nil
102
+ @phoneme.matches? {|*args| context = args}
103
+
104
+ assert_equal [@phoneme], context
105
+ end
106
+
107
+ def test_should_match_if_block_is_not_false
108
+ assert @phoneme.matches? {true}
109
+ end
110
+
111
+ def test_should_not_match_if_block_is_false
112
+ assert !@phoneme.matches? {false}
113
+ end
114
+ end
115
+
116
+ class PhonemeCreationTest < Test::Unit::TestCase
117
+ def test_should_add_phoneme_to_collection
118
+ phoneme = MemorableStrings::Phoneme.add(:a)
119
+
120
+ assert_equal [phoneme], MemorableStrings::Phoneme.all
121
+ assert_equal 'a', phoneme.value
122
+ end
123
+
124
+ def test_should_allow_multiple_phonemes
125
+ phonemes = MemorableStrings::Phoneme.add(:a, :b, :c, :first => false)
126
+
127
+ assert_equal 3, phonemes.length
128
+ assert_equal phonemes, MemorableStrings::Phoneme.all
129
+ assert phonemes.all? {|phoneme| !phoneme.first?}
130
+ end
131
+
132
+ def test_should_allow_phoneme_arraye
133
+ phonemes = MemorableStrings::Phoneme.add([:a, :b, :c], :first => false)
134
+
135
+ assert_equal 3, phonemes.length
136
+ assert_equal phonemes, MemorableStrings::Phoneme.all
137
+ assert phonemes.all? {|phoneme| !phoneme.first?}
138
+ end
139
+
140
+ def teardown
141
+ MemorableStrings::Phoneme.all.clear
142
+ end
143
+ end
144
+
145
+ class PhonemeRandomTest < Test::Unit::TestCase
146
+ def setup
147
+ @a, @b, @ab = MemorableStrings::Phoneme.add(:a, :b, :ab)
148
+ end
149
+
150
+ def test_should_select_any_phoneme
151
+ MemorableStrings::Phoneme.expects(:rand).with(3).returns(0)
152
+ assert_equal @a, MemorableStrings::Phoneme.random
153
+
154
+ MemorableStrings::Phoneme.expects(:rand).with(3).returns(1)
155
+ assert_equal @b, MemorableStrings::Phoneme.random
156
+ end
157
+
158
+ def test_should_restrict_selection_based_on_maxlength
159
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
160
+ assert_equal @a, MemorableStrings::Phoneme.random(1)
161
+ end
162
+
163
+ def teardown
164
+ MemorableStrings::Phoneme.all.clear
165
+ end
166
+ end
167
+
168
+ class PhonemeRandomWithBlockTest < Test::Unit::TestCase
169
+ def setup
170
+ @a, @b = MemorableStrings::Phoneme.add(:a, :b)
171
+ end
172
+
173
+ def test_should_select_phoneme_that_returns_true
174
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
175
+ assert_equal @a, MemorableStrings::Phoneme.random {|phoneme| phoneme.value == 'a'}
176
+
177
+ MemorableStrings::Phoneme.expects(:rand).with(2).times(2).returns(1, 0)
178
+ assert_equal @a, MemorableStrings::Phoneme.random {|phoneme| phoneme.value == 'a'}
179
+ end
180
+
181
+ def teardown
182
+ MemorableStrings::Phoneme.all.clear
183
+ end
184
+ end
185
+
186
+ class PhonemeFirstTest < Test::Unit::TestCase
187
+ def setup
188
+ setup_phonemes
189
+
190
+ MemorableStrings::Vowel.all << @a = MemorableStrings::Vowel.new(:a)
191
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Consonant.new(:b)
192
+ end
193
+
194
+ def test_should_select_vowel_half_the_time
195
+ MemorableStrings::Vowel.expects(:rand).with(1).returns(0)
196
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(1)
197
+
198
+ assert_equal @a, MemorableStrings::Phoneme.first(1)
199
+ end
200
+
201
+ def test_should_select_consonant_half_the_time
202
+ MemorableStrings::Consonant.expects(:rand).with(1).returns(0)
203
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
204
+
205
+ assert_equal @b, MemorableStrings::Phoneme.first(1)
206
+ end
207
+
208
+ def test_should_not_select_phonemes_marked_as_not_first
209
+ c = MemorableStrings::Consonant.add(:c, :first => false)
210
+
211
+ MemorableStrings::Consonant.expects(:rand).with(2).times(2).returns(1, 0)
212
+ MemorableStrings::Phoneme.expects(:rand).with(2).returns(0)
213
+
214
+ assert_equal @b, MemorableStrings::Phoneme.first(1)
215
+ end
216
+
217
+ def teardown
218
+ teardown_phonemes
219
+ end
220
+ end
@@ -0,0 +1,234 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class StringByDefaultTest < Test::Unit::TestCase
4
+ def setup
5
+ @value = String.memorable
6
+ end
7
+
8
+ def test_should_have_8_characters
9
+ assert_equal 8, @value.length
10
+ end
11
+
12
+ def test_should_not_include_capital_letter
13
+ assert_no_match /[A-Z]/, @value
14
+ end
15
+
16
+ def test_should_not_include_digit
17
+ assert_no_match /[0-9]/, @value
18
+ end
19
+
20
+ def test_should_be_consistent
21
+ consistent = (1..1000).each do
22
+ value = String.memorable
23
+ value =~ /^[a-z]+$/
24
+ end
25
+
26
+ assert consistent
27
+ end
28
+ end
29
+
30
+ class StringTest < Test::Unit::TestCase
31
+ def test_should_raise_exception_if_invalid_option_specified
32
+ assert_raise(ArgumentError) { String.memorable(:invalid => true) }
33
+ end
34
+
35
+ def test_should_raise_exception_if_invalid_length
36
+ assert_raise(ArgumentError) { String.memorable(:length => -1) }
37
+ assert_raise(ArgumentError) { String.memorable(:length => 0) }
38
+ assert_nothing_raised { String.memorable(:length => 1) }
39
+ end
40
+
41
+ def test_should_raise_exception_if_length_too_short_with_digits
42
+ assert_raise(ArgumentError) { String.memorable(:length => 1, :digit => true) }
43
+ assert_raise(ArgumentError) { String.memorable(:length => 2, :digit => true) }
44
+ assert_nothing_raised { String.memorable(:length => 3, :digit => true) }
45
+ end
46
+ end
47
+
48
+ class StringWithCustomLengthTest < Test::Unit::TestCase
49
+ def setup
50
+ @value = String.memorable(:length => 2)
51
+ end
52
+
53
+ def test_should_use_custom_length
54
+ assert_equal 2, @value.length
55
+ end
56
+ end
57
+
58
+ class StringWithCapitalTest < Test::Unit::TestCase
59
+ def setup
60
+ @value = String.memorable(:capital => true)
61
+ end
62
+
63
+ def test_should_have_8_characters
64
+ assert_equal 8, @value.length
65
+ end
66
+
67
+ def test_include_capital
68
+ assert_match /[A-Z]/, @value
69
+ end
70
+
71
+ def test_should_not_include_digit
72
+ assert_no_match /[0-9]/, @value
73
+ end
74
+
75
+ def test_should_capitalize_with_length_of_1
76
+ @value = String.memorable(:capital => true, :length => 1)
77
+ assert_equal 1, @value.length
78
+ assert_equal @value.upcase, @value
79
+ end
80
+
81
+ def test_should_be_consistent
82
+ consistent = (1..1000).all? do
83
+ value = String.memorable(:capital => true)
84
+ value =~ /^[a-z]*[A-Z][a-z]*$/
85
+ end
86
+
87
+ assert consistent
88
+ end
89
+ end
90
+
91
+ class StringWithDigitTest < Test::Unit::TestCase
92
+ def setup
93
+ @value = String.memorable(:digit => true)
94
+ end
95
+
96
+ def test_should_have_8_characters
97
+ assert_equal 8, @value.length
98
+ end
99
+
100
+ def test_not_include_capital
101
+ assert_no_match /[A-Z]/, @value
102
+ end
103
+
104
+ def test_should_include_digit
105
+ assert_match /[0-9]/, @value
106
+ end
107
+
108
+ def test_should_always_use_last_character_with_length_of_3
109
+ @value = String.memorable(:digit => true, :length => 3)
110
+ assert_equal 3, @value.length
111
+ assert_match /[0-9]/, @value[-1, 1]
112
+ end
113
+
114
+ def test_should_be_consistent
115
+ consistent = (1..1000).all? do
116
+ value = String.memorable(:digit => true)
117
+ value =~ /^[a-z]+[0-9]{1}[a-z]*$/
118
+ end
119
+
120
+ assert consistent
121
+ end
122
+ end
123
+
124
+ class StringWithCapitalAndDigitTest < Test::Unit::TestCase
125
+ def setup
126
+ @value = String.memorable(:capital => true, :digit => true)
127
+ end
128
+
129
+ def test_should_have_8_characters
130
+ assert_equal 8, @value.length
131
+ end
132
+
133
+ def test_include_capital
134
+ assert_match /[A-Z]/, @value
135
+ end
136
+
137
+ def test_should_include_digit
138
+ assert_match /[0-9]/, @value
139
+ end
140
+
141
+ def test_should_be_consistent
142
+ consistent = (1..1000).all? do
143
+ value = String.memorable(:capital => true, :digit => true)
144
+ value =~ /^[a-z]*([A-Z][a-z]*[0-9]|[0-9][a-z]*[A-Z])[a-z]*$/
145
+ end
146
+
147
+ assert consistent
148
+ end
149
+ end
150
+
151
+ class StringWithPrintFriendlyTest < Test::Unit::TestCase
152
+ def setup
153
+ @value = String.memorable(:print_friendly => true)
154
+ end
155
+
156
+ def test_should_have_8_characters
157
+ assert_equal 8, @value.length
158
+ end
159
+
160
+ def test_not_include_capital
161
+ assert_no_match /[A-Z]/, @value
162
+ end
163
+
164
+ def test_should_not_include_digit
165
+ assert_no_match /[0-9]/, @value
166
+ end
167
+
168
+ def test_should_not_include_ambiguous_letters
169
+ assert_no_match /lio/, @value
170
+ end
171
+
172
+ def test_should_be_consistent
173
+ consistent = (1..1000).all? do
174
+ value = String.memorable(:print_friendly => true)
175
+ value =~ /^[^lio]*$/
176
+ end
177
+
178
+ assert consistent
179
+ end
180
+ end
181
+
182
+ class StringWithPrintFriendlyAndCapitalTest < Test::Unit::TestCase
183
+ def setup
184
+ @value = String.memorable(:print_friendly => true, :capital => true)
185
+ end
186
+
187
+ def test_should_include_capital
188
+ assert_match /[A-Z]/, @value
189
+ end
190
+
191
+ def test_should_not_include_digit
192
+ assert_no_match /[0-9]/, @value
193
+ end
194
+
195
+ def test_should_not_include_ambiguous_letters
196
+ assert_no_match /lioBDGQSZIO/, @value
197
+ end
198
+
199
+ def test_should_be_consistent
200
+ consistent = (1..1000).all? do
201
+ value = String.memorable(:print_friendly => true, :capital => true)
202
+ value =~ /^[^lioBDGQSZIO]*$/
203
+ end
204
+
205
+ assert consistent
206
+ end
207
+ end
208
+
209
+ class StringWithPrintFriendlyAndDigitTest < Test::Unit::TestCase
210
+ def setup
211
+ @value = String.memorable(:print_friendly => true, :digit => true)
212
+ end
213
+
214
+ def test_should_not_include_capital
215
+ assert_no_match /[A-Z]/, @value
216
+ end
217
+
218
+ def test_should_include_digit
219
+ assert_match /[0-9]/, @value
220
+ end
221
+
222
+ def test_should_not_include_ambiguous_letters
223
+ assert_no_match /lio012568/, @value
224
+ end
225
+
226
+ def test_should_be_consistent
227
+ consistent = (1..1000).all? do
228
+ value = String.memorable(:print_friendly => true, :digit => true)
229
+ value =~ /^[^lio012568]*$/
230
+ end
231
+
232
+ assert consistent
233
+ end
234
+ end
@@ -0,0 +1,31 @@
1
+ require 'test/unit'
2
+
3
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
4
+ require File.dirname(__FILE__) + '/../init'
5
+
6
+ # Force mocha to be installed
7
+ begin
8
+ require 'rubygems'
9
+ require 'mocha'
10
+ rescue LoadError
11
+ $stderr.puts "Mocha is not installed. `gem install mocha` and try again."
12
+ exit
13
+ end
14
+
15
+ Test::Unit::TestCase.class_eval do
16
+ def setup_phonemes
17
+ @vowels = MemorableStrings::Vowel.all.dup
18
+ @consonants = MemorableStrings::Consonant.all.dup
19
+ @digits = MemorableStrings::Digit.all.dup
20
+
21
+ MemorableStrings::Vowel.all.clear
22
+ MemorableStrings::Consonant.all.clear
23
+ MemorableStrings::Digit.all.clear
24
+ end
25
+
26
+ def teardown_phonemes
27
+ MemorableStrings::Vowel.all.replace(@vowels)
28
+ MemorableStrings::Consonant.all.replace(@consonants)
29
+ MemorableStrings::Digit.all.replace(@digits)
30
+ end
31
+ end
@@ -0,0 +1,92 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class VowelWithPreviousVowelTest < Test::Unit::TestCase
4
+ def setup
5
+ setup_phonemes
6
+
7
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
8
+ MemorableStrings::Consonant.all << @br = MemorableStrings::Vowel.new(:br)
9
+
10
+ @previous = MemorableStrings::Vowel.new(:a)
11
+ @vowel = MemorableStrings::Vowel.new(:e)
12
+ @stack = [@previous, @vowel]
13
+ end
14
+
15
+ def test_should_generate_consonant_with_one_character
16
+ MemorableStrings::Consonant.expects(:rand).with(1).returns(0)
17
+ assert_equal @b, @vowel.next(@stack, 2)
18
+ end
19
+
20
+ def teardown
21
+ teardown_phonemes
22
+ end
23
+ end
24
+
25
+ class VowelWithMultipleCharactersTest < Test::Unit::TestCase
26
+ def setup
27
+ setup_phonemes
28
+
29
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
30
+ MemorableStrings::Consonant.all << @br = MemorableStrings::Vowel.new(:br)
31
+
32
+ @previous = MemorableStrings::Consonant.new(:c)
33
+ @vowel = MemorableStrings::Vowel.new(:ai)
34
+ @stack = [@previous, @vowel]
35
+ end
36
+
37
+ def test_should_generate_consonant_with_one_character
38
+ MemorableStrings::Consonant.expects(:rand).with(1).returns(0)
39
+ assert_equal @b, @vowel.next(@stack, 2)
40
+ end
41
+
42
+ def teardown
43
+ teardown_phonemes
44
+ end
45
+ end
46
+
47
+ class VowelNextTest < Test::Unit::TestCase
48
+ def setup
49
+ setup_phonemes
50
+
51
+ @previous = MemorableStrings::Consonant.new(:c)
52
+ @vowel = MemorableStrings::Vowel.new(:a)
53
+ @stack = [@previous, @vowel]
54
+ end
55
+
56
+ def test_should_generate_vowel_forty_percent_the_time
57
+ MemorableStrings::Vowel.all << @e = MemorableStrings::Vowel.new(:e)
58
+ MemorableStrings::Vowel.all << @ee = MemorableStrings::Vowel.new(:ee)
59
+
60
+ (0..3).each do |value|
61
+ @vowel.expects(:rand).with(10).returns(value)
62
+ MemorableStrings::Vowel.expects(:rand).with(1).returns(0)
63
+
64
+ assert_equal @e, @vowel.next(@stack, 2)
65
+ end
66
+ end
67
+
68
+ def test_should_generate_consonant_sixty_percent_the_time
69
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
70
+ MemorableStrings::Consonant.all << @br = MemorableStrings::Vowel.new(:br)
71
+
72
+ (4..9).each do |value|
73
+ @vowel.expects(:rand).with(10).returns(value)
74
+ MemorableStrings::Consonant.expects(:rand).with(1).returns(0)
75
+
76
+ assert_equal @b, @vowel.next(@stack, 2)
77
+ end
78
+ end
79
+
80
+ def test_should_allow_block_conditional
81
+ MemorableStrings::Consonant.all << @b = MemorableStrings::Vowel.new(:b)
82
+ MemorableStrings::Consonant.all << @c = MemorableStrings::Vowel.new(:c)
83
+ MemorableStrings::Consonant.expects(:rand).with(2).times(2).returns(1, 0)
84
+ @vowel.expects(:rand).returns(4)
85
+
86
+ assert_equal @b, @vowel.next(@stack, 2) {|phoneme| phoneme.value == 'b'}
87
+ end
88
+
89
+ def teardown
90
+ teardown_phonemes
91
+ end
92
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memorable_strings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Aaron Pfeifer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-18 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: aaron@pluginaweek.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/memorable_strings.rb
26
+ - lib/memorable_strings
27
+ - lib/memorable_strings/vowel.rb
28
+ - lib/memorable_strings/consonant.rb
29
+ - lib/memorable_strings/extensions
30
+ - lib/memorable_strings/extensions/string.rb
31
+ - lib/memorable_strings/digit.rb
32
+ - lib/memorable_strings/phoneme.rb
33
+ - test/test_helper.rb
34
+ - test/consonant_test.rb
35
+ - test/string_test.rb
36
+ - test/phoneme_test.rb
37
+ - test/digit_test.rb
38
+ - test/vowel_test.rb
39
+ - CHANGELOG.rdoc
40
+ - init.rb
41
+ - LICENSE
42
+ - Rakefile
43
+ - README.rdoc
44
+ has_rdoc: true
45
+ homepage: http://www.pluginaweek.org
46
+ post_install_message:
47
+ rdoc_options: []
48
+
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project: pluginaweek
66
+ rubygems_version: 1.3.1
67
+ signing_key:
68
+ specification_version: 2
69
+ summary: Generates strings that can be easily remembered
70
+ test_files:
71
+ - test/consonant_test.rb
72
+ - test/string_test.rb
73
+ - test/phoneme_test.rb
74
+ - test/digit_test.rb
75
+ - test/vowel_test.rb