memorable_strings 0.1.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.
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