passphrase 1.1.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f0c06ef87ec317d113d67632f44873e4c0b6c7f
4
- data.tar.gz: c144b9ec21879b8717a5c3c3a0f5dc13be273542
3
+ metadata.gz: 4033cb9da1097b10de0c83644706f4b5ec71d04a
4
+ data.tar.gz: aba0d115c820f615f3f6124688f548b96f0fe9ba
5
5
  SHA512:
6
- metadata.gz: f2711c0224023e76b00f2f1fea8358be40ee078edaa1891c3623f9803398cf4d2868261db067e0b9d0e072679cdb385e9b4809934325c06686de5d747c3110f1
7
- data.tar.gz: a425a87a7134ffc3efa2262f208f278e0f941a33ea97f1eb35558fa8a68cba03661e139856e5d72a402ff49c6d50cd4ecf9eef950326b54270f8f7383160c07b
6
+ metadata.gz: 4cb595f7d1b1de7ee367a32817a6c5f9f1e568a12433303867f188192d7c8e4354c7ea3b4d3e40c5218c69592d3ff8b9a47b0fb81169c7263cea198f5bcc1b77
7
+ data.tar.gz: c2d8bb80517d152cfdf8f23e4c1c8bcb2f9ca52dfd01dee89b682b931940a849714219176919dade72afbdea97daaa7c9ba16bbbf6b4dea183081630d0352c5d
Binary file
data.tar.gz.sig CHANGED
Binary file
@@ -2,6 +2,12 @@
2
2
  All notable changes to this project will be documented in this file. This
3
3
  project adheres to [Semantic Versioning](http://semver.org/).
4
4
 
5
+ ## UNRELEASED
6
+ ### Added
7
+ - List available languages.
8
+ - Added option `--languages|-l` to select a subset of languages.
9
+ - Added a WordlistDatabase class.
10
+
5
11
  ## Version 1.1.0 - 2015-04-14
6
12
  ### Added
7
13
  - Added SHA256 checksum calculation to rake_helper.rb.
data/README.md CHANGED
@@ -2,9 +2,10 @@
2
2
  Use Passphrase to generate a passphrase for SSH or GPG keys. For example, on
3
3
  the command-line, run
4
4
 
5
- $ passphrase --num-words=4
6
- dokusi uolgo allunga totalisa
7
-
5
+ ```bash
6
+ $ passphrase --num-words=4
7
+ dokusi uolgo allunga totalisa
8
+ ```
8
9
  or programmatically,
9
10
 
10
11
  ```ruby
@@ -12,12 +13,15 @@ require "passphrase"
12
13
  p = Passphrase::Passphrase.new(number_of_words: 4)
13
14
  passphrase = p.passphrase
14
15
  ```
16
+
15
17
  Passphrase also has the capability to generate passwords (indirectly).
16
18
 
17
19
  ## Installation
18
20
  The Passphrase command-line tool and library can be installed with
19
21
 
20
- $ gem install passphrase
22
+ ```bash
23
+ $ gem install passphrase
24
+ ```
21
25
 
22
26
  However, because the gem is cryptographically signed to prevent tampering, the
23
27
  preferred installation command should include the `--trust-policy` security
@@ -25,38 +29,63 @@ option, which causes the gem to be verified before being installed. To invoke
25
29
  this option, you must first add my public key `esumbar.pem` to your list of
26
30
  trusted certificates, as follows.
27
31
 
28
- $ gem cert --add <(curl -Ls https://raw.githubusercontent.com/esumbar/passphrase/master/certs/esumbar.pem)
32
+ ```bash
33
+ $ gem cert --add <(curl -Ls https://raw.githubusercontent.com/esumbar/passphrase/master/certs/esumbar.pem)
34
+ ```
29
35
 
30
36
  Finally, specify the appropriate security level when installing.
31
37
 
32
- $ gem install passphrase --trust-policy MediumSecurity
38
+ ```bash
39
+ $ gem install passphrase --trust-policy MediumSecurity
40
+ ```
33
41
 
34
- Using `MediumSecurity` rather than `HighSecurity` omits dependent gems, in
35
- this case `sqlite3`, which is not signed, from the verification process.
42
+ Using `MediumSecurity` rather than `HighSecurity` omits dependent gems that
43
+ are not signed from the verification process. Passphrase depends on the
44
+ unsigned gem `sqlite3`.
36
45
 
37
46
  ## Basic usage
38
47
  ### Command-line tool
39
48
 
40
- $ passphrase --help
41
- Usage: passphrase [options]
42
- -n, --num-words=NUM Number of words in passphrase 3..10
43
- (default: 5)
44
- -p, --[no-]passwordize Add one cap, one num, and one special char
45
- (default: --no-passwordize)
46
- -r, --[no-]random-org Use RANDOM.ORG to generate random numbers
47
- (default: --no-random-org)
48
- -h, --help Show this message
49
- -v, --version Show version
50
-
51
- $ passphrase
52
- sinmak termyne ismus affidavo recur
53
-
54
- $ passphrase -n 4
55
- apaisado vermouth seemag ebelik
56
-
57
- $ passphrase -n 3 -p
58
- pothvati alewives escatima
59
- pothvati_alewiNes_e&cat5ma
49
+ ```bash
50
+ $ passphrase --help
51
+ Usage: passphrase [options]
52
+ -l, --languages=LANG1,... Specify languages to use, none for a listing
53
+ (default: --languages=all)
54
+ -n, --num-words=NUM Number of words in passphrase 3..10
55
+ (default: --num-words=5)
56
+ -p, --[no-]passwordize Add one cap, one num, and one special char
57
+ (default: --no-passwordize)
58
+ -r, --[no-]random-org Use RANDOM.ORG to generate random numbers
59
+ (default: --no-random-org)
60
+ -h, --help Show this message
61
+ -v, --version Show version
62
+
63
+ $ # generate a passphrase using default settings
64
+ $ passphrase
65
+ sinmak termyne ismus affidavo recur
66
+
67
+ $ # generate a four-word passphrase
68
+ $ passphrase -n4
69
+ apaisado vermouth seemag ebelik
70
+
71
+ $ # generate a passphrase using only English and Spanish words
72
+ $ passphrase --languages=english,spanish
73
+ fumada hearsay murcio phosphor azufroso
74
+
75
+ $ # generate a three-word Polish passphrase and password
76
+ $ passphrase -lp -n3 -p
77
+ podejrza zmalala wypadkow
78
+ 8odej=za_Xmalala_wypadkow
79
+
80
+ $ # list the available languages
81
+ $ passphrase -l
82
+ afrikaans
83
+ croatian
84
+ czech
85
+ diceware
86
+ english
87
+ ...
88
+ ```
60
89
 
61
90
  ### Ruby library
62
91
 
@@ -78,10 +107,15 @@ passphrase3 = p.generate.passphrase
78
107
  # generate an array of six-word passphrases
79
108
  passphrase_array = Array.new(100)
80
109
  Passphrase::Passphrase.new(number_of_words: 6) do |p|
81
- passphrase_array.map! { |e| p.generate.passphrase }
110
+ passphrase_array.map! { |array_element| p.generate.passphrase }
82
111
  end
83
112
 
84
- # generate a password
113
+ # generate a passphrase using only French and Italian words
114
+ options = { number_of_words: 4, languages: %w( fr it ) }
115
+ p = Passphrase::Passphrase.new(options)
116
+ passphrase = p.passphrase
117
+
118
+ # generate a password from a passphrase
85
119
  p = Passphrase::Passphrase.new(number_of_words: 3)
86
120
  passphrase = p.passphrase
87
121
  password = passphrase.to_password
@@ -150,6 +184,13 @@ have the option of requesting random numbers from
150
184
  [RANDOM.ORG](http://www.random.org). However, because RANDOM.ORG depends on
151
185
  network access, it is susceptible to network problems, and is also slower.
152
186
 
187
+ ### Subset of languages
188
+ By default, Passphrase randomly selects words from the collection of available
189
+ languages. If desired, the selection can be limited to a subset of languages.
190
+ This can be done on the command-line and in code by supplying a list of
191
+ language names or abbreviations. A minimal abbreviation uses the first one or
192
+ two letters of a language name, just enough to avoid ambiguity.
193
+
153
194
  ### Passwords
154
195
  A typical passphrase will not satisfy password policies that require the use
155
196
  of upper case letters, numbers, and special characters (~, !, and the like).
@@ -173,13 +214,17 @@ compliment of 7776 entries.
173
214
  To run the command-line tool within the repository directory, try `ruby -Ilib
174
215
  bin/passphrase`. You can also experiment with the library in irb. For example,
175
216
 
176
- $ irb -Ilib -rpassphrase
177
- >> p = Passphrase::Passphrase.new(number_of_words: 3)
178
- => {:passphrase=>"jazzy vannier viscount", :number_of_words=>3, ... }
179
- >> p.passphrase
180
- => "jazzy vannier viscount"
181
- >> p.passphrase.to_password
182
- => "jazz{_2annier_vBscount"
217
+ ```bash
218
+ $ irb -Ilib -rpassphrase
219
+ >> p = Passphrase::Passphrase.new(number_of_words: 3)
220
+ => {:passphrase=>"jazzy vannier viscount", :number_of_words=>3, ... }
221
+ >> p.passphrase
222
+ => "jazzy vannier viscount"
223
+ >> p.passphrase.to_password
224
+ => "jazz{_2annier_vBscount"
225
+ >> p = Passphrase::Passphrase.new(languages: ["e", "fr"])
226
+ => {:passphrase=>"obstrua lamparos orgy forerez deduce", ... }
227
+ ```
183
228
 
184
229
  Run the tests with `rake spec`.
185
230
 
@@ -2,6 +2,7 @@ require "passphrase/default"
2
2
  require "passphrase/passphrase"
3
3
  require "passphrase/version"
4
4
 
5
+ # The enclosing namespace for the Passphrase library.
5
6
  module Passphrase
6
7
  autoload(:CLI, "passphrase/CLI")
7
8
  end
@@ -1,4 +1,5 @@
1
1
  require "optparse"
2
+ require "passphrase/wordlist_database"
2
3
 
3
4
  module Passphrase
4
5
  # The CLI class encapsulates a simple command line interface to the
@@ -38,13 +39,18 @@ module Passphrase
38
39
  default_random_org = Default.options[:use_random_org] ? "--random-org" : "--no-random-org"
39
40
  default_number_range = Default.number_range
40
41
 
41
- parser = OptionParser.new do |opts|
42
+ parser = OptionParser.new(nil, 28, " " * 4) do |opts|
42
43
  opts.banner = "Usage: passphrase [options]"
44
+ opts.on(:OPTIONAL, "-l", "--languages=LANG1,...", Array,
45
+ "Specify languages to use, none for a listing",
46
+ "(default: --languages=all)") do |l|
47
+ options[:languages] = (l || [])
48
+ end
43
49
  opts.on(:REQUIRED, "-n NUM", "--num-words=NUM", Integer,
44
50
  "Number of words in passphrase #{default_number_range}",
45
- "(default: #{default_number_of_words})") do |n|
51
+ "(default: --num-words=#{default_number_of_words})") do |n|
46
52
  options[:number_of_words] = n
47
- end
53
+ end
48
54
  opts.on(:NONE, "-p", "--[no-]passwordize",
49
55
  "Add one cap, one num, and one special char",
50
56
  "(default: #{default_passwordize})") do |p|
@@ -67,15 +73,15 @@ module Passphrase
67
73
 
68
74
  begin
69
75
  parser.parse!(args)
76
+ display_languages(options)
70
77
  validate_number_of_words(options)
71
78
  passphrase = Passphrase.new(options).passphrase
72
- puts passphrase
73
- puts passphrase.to_password if options[:passwordize]
79
+ print_out(passphrase, options)
74
80
  rescue OptionParser::InvalidOption => e
75
81
  handle_error(e)
76
82
  rescue OptionParser::MissingArgument => e
77
83
  handle_error(e)
78
- # gracefully handle exit(0) from --help and --version options
84
+ # Gracefully handle exit(0) from --help, --version, and --language options
79
85
  rescue SystemExit => e
80
86
  exit(e.status)
81
87
  rescue Exception => e
@@ -83,20 +89,35 @@ module Passphrase
83
89
  end
84
90
  end
85
91
 
92
+ def self.display_languages(options)
93
+ if options[:languages].empty?
94
+ puts WordlistDatabase.connect.from(:languages).all
95
+ exit(0)
96
+ end
97
+ end
98
+
86
99
  def self.validate_number_of_words(options)
87
100
  number_of_words = options[:number_of_words]
88
101
  unless Default.number_range.include?(number_of_words)
89
- raise "number of words out of range #{Default.number_range}"
102
+ raise "Number of words out of range #{Default.number_range}"
90
103
  end
91
104
  end
92
105
 
106
+ def self.print_out(passphrase, options)
107
+ puts passphrase
108
+ puts passphrase.to_password if options[:passwordize]
109
+ end
110
+
93
111
  def self.handle_error(error)
94
112
  STDERR.puts "ERROR: #{error.message}"
95
113
  exit(1)
96
114
  end
97
115
 
98
116
  class << self
99
- private :validate_number_of_words, :handle_error
117
+ private :display_languages
118
+ private :validate_number_of_words
119
+ private :print_out
120
+ private :handle_error
100
121
  end
101
122
  end
102
123
  end
@@ -1,4 +1,7 @@
1
1
  module Passphrase
2
+ # Class instance variables in this class define default values for
3
+ # {Passphrase} options and the range for the number of words that can be
4
+ # specified for a passphrase on the command-line.
2
5
  class Default
3
6
  class << self
4
7
  # @return [Hash] the default options that the command line interface
@@ -9,7 +12,12 @@ module Passphrase
9
12
  attr_reader :number_range
10
13
  end
11
14
 
12
- @options = { number_of_words: 5, passwordize: nil, use_random_org: nil }
15
+ @options = {
16
+ languages: ["all"],
17
+ number_of_words: 5,
18
+ passwordize: nil,
19
+ use_random_org: nil
20
+ }
13
21
  @number_range = (3..10)
14
22
  end
15
23
  end
@@ -1,7 +1,5 @@
1
- require "sqlite3"
2
1
  require "passphrase/diceware_random"
3
- require "passphrase/language_query"
4
- require "passphrase/word_query"
2
+ require "passphrase/wordlist_database"
5
3
 
6
4
  module Passphrase
7
5
  # This class implements the Diceware Method for generating a passphrase. It
@@ -20,8 +18,9 @@ module Passphrase
20
18
  def initialize(options)
21
19
  @number_of_words = options[:number_of_words]
22
20
  @random = DicewareRandom.new(options[:use_random_org])
23
- setup_database
24
- setup_queries
21
+ db = WordlistDatabase.connect
22
+ @languages = db.from(:languages).only(options[:languages])
23
+ @words = db.from(:words)
25
24
  end
26
25
 
27
26
  # Runs the Diceware method and returns its result to the calling
@@ -37,18 +36,6 @@ module Passphrase
37
36
 
38
37
  private
39
38
 
40
- def setup_database
41
- wordlist_file = "wordlist/words.sqlite3"
42
- wordlist_path = File.join(File.dirname(__FILE__), wordlist_file)
43
- raise "Wordlist database not found" unless File.exist?(wordlist_path)
44
- @db = SQLite3::Database.new(wordlist_path, readonly: true)
45
- end
46
-
47
- def setup_queries
48
- @languages = LanguageQuery.new(@db)
49
- @words = WordQuery.new(@db)
50
- end
51
-
52
39
  def get_random_languages
53
40
  @random_languages = @random.indices(@number_of_words, @languages.count)
54
41
  @random_languages.map! { |index| @languages[index] }
@@ -65,6 +65,7 @@ module Passphrase
65
65
  def setup_remote_generator
66
66
  self.class.class_eval do
67
67
  def generate_random_numbers(count, maximum, minimum=0)
68
+ return Array.new(count, maximum) if maximum == minimum
68
69
  # Check quota before proceeding and thereafter every 1000 uses to
69
70
  # comply with random.org guidelines.
70
71
  check_random_org_quota if self.class.random_org_requests % 1000 == 0
@@ -92,7 +93,7 @@ module Passphrase
92
93
  open("#{@random_org_uri}#{query}") do |data|
93
94
  quota = data.gets.chomp.to_i
94
95
  over_quota_message = "RANDOM.ORG over quota, try again in 10 minutes"
95
- raise "ERROR: #{over_quota_message}" if quota < 0
96
+ raise "#{over_quota_message}" if quota < 0
96
97
  end
97
98
  end
98
99
 
@@ -104,6 +105,7 @@ module Passphrase
104
105
  def setup_local_generator
105
106
  self.class.class_eval do
106
107
  def generate_random_numbers(count, maximum, minimum=0)
108
+ return Array.new(count, maximum) if maximum == minimum
107
109
  max = maximum - minimum + 1
108
110
  offset = minimum
109
111
  # array_of_random_numbers
@@ -1,5 +1,5 @@
1
1
  module Passphrase
2
- # This subclass of String implements the {#passwordize} instance method for
2
+ # This subclass of String implements the {#to_password} instance method for
3
3
  # substituting a capital letter, number, and special character into a
4
4
  # passphrase.
5
5
  class PassphraseString < String
@@ -1,5 +1,5 @@
1
1
  module Passphrase
2
2
  # Version numbers are bumped according to {http://semver.org Semantic
3
3
  # Versioning}.
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
@@ -0,0 +1,103 @@
1
+ require "sqlite3"
2
+
3
+ module Passphrase
4
+ # This class encapsulates the {#count} and {#[]} queries against the
5
+ # "languages" table in the "words" SQLite 3 database. It also provides the
6
+ # {#only} method which allows a subset of languages to be specified.
7
+ class Language
8
+ def initialize(db)
9
+ @languages = []
10
+ sql = "SELECT language FROM languages"
11
+ db.execute(sql).each { |lang| @languages << lang.first }
12
+ end
13
+
14
+ # @return [Integer] the number of rows in the languages table
15
+ def count
16
+ @languages.size
17
+ end
18
+
19
+ # @param index [Integer] selects a specific row in the languages table
20
+ # @return [String] the language corresponding to the given index
21
+ def [](index)
22
+ @languages[index]
23
+ end
24
+
25
+ # @return [Array] all rows in the languages table
26
+ def all
27
+ @languages
28
+ end
29
+
30
+ # @param language_list [Array] restrict languages to those in the list
31
+ # @return [self] to allow chaining methods
32
+ def only(language_list)
33
+ return self if /^all$/ =~ language_list.first
34
+ validate(language_list)
35
+ @languages.keep_if do |language|
36
+ language_list.any? { |l| language.match("^#{l}") }
37
+ end
38
+ self
39
+ end
40
+
41
+ private
42
+
43
+ # Make sure that each language specification matches at least one
44
+ # language.
45
+ def validate(language_list)
46
+ language_list.each do |l|
47
+ matches_language = @languages.any? { |language| language.match("^#{l}") }
48
+ raise "No language match for #{l}" unless matches_language
49
+ end
50
+ end
51
+ end
52
+ # This class encapsulates the {#where} query against the "words" table in
53
+ # the "words" SQLite 3 database. The filter parameter must be a hash that
54
+ # specifies a language and sequence of die rolls.
55
+ # @example
56
+ # {language: "afrikaans", die_rolls: "11111"}
57
+ class Word
58
+ def initialize(db)
59
+ @db = db
60
+ end
61
+
62
+ # @param filter [Hash] specifies the language/die_roll combination
63
+ # @return [String] a string of space-separated words from the wordlist
64
+ def where(filter)
65
+ sql = "SELECT words " +
66
+ "FROM words " +
67
+ "WHERE language = :language AND die_rolls = :die_rolls"
68
+ @db.get_first_value(sql, filter)
69
+ end
70
+ end
71
+ # This class encapsulates the Diceware wordlist database file and dispatches
72
+ # one of two table query objects via the {#from} method.
73
+ class WordlistDatabase
74
+ # A "connect" method seems more natural than "new" when connecting to a
75
+ # database.
76
+ def self.connect
77
+ new
78
+ end
79
+
80
+ def initialize
81
+ wordlist_file = "wordlist/words.sqlite3"
82
+ wordlist_path = File.join(File.dirname(__FILE__), wordlist_file)
83
+ raise "Wordlist database file not found" unless File.exist?(wordlist_path)
84
+ @db = SQLite3::Database.new(wordlist_path, readonly: true)
85
+ end
86
+
87
+ # @param table [Symbol] the table name
88
+ # @return [Language] if the "languages" table is specified
89
+ # @return [Word] if the "words" table is specified
90
+ def from(table)
91
+ case table
92
+ when :languages
93
+ Language.new(@db)
94
+ when :words
95
+ Word.new(@db)
96
+ else
97
+ raise "Unknown table #{table}"
98
+ end
99
+ end
100
+
101
+ alias [] from
102
+ end
103
+ end
@@ -16,14 +16,26 @@ module Passphrase
16
16
  expect(CLI).to respond_to(:parse).with(1).argument
17
17
  end
18
18
 
19
+ it "does not respond to class method display_languages() (private)" do
20
+ expect(CLI).not_to respond_to(:display_languages)
21
+ end
22
+
19
23
  it "does not respond to class method validate_number_of_words() (private)" do
20
24
  expect(CLI).not_to respond_to(:validate_number_of_words)
21
25
  end
22
26
 
27
+ it "does not respond to class method print_out() (private)" do
28
+ expect(CLI).not_to respond_to(:print_out)
29
+ end
30
+
23
31
  it "does not respond to class method handle_error() (private)" do
24
32
  expect(CLI).not_to respond_to(:handle_error)
25
33
  end
26
34
 
35
+ it "exits when option -l is supplied" do
36
+ expect { CLI.parse(["-l"]) }.to raise_error(SystemExit)
37
+ end
38
+
27
39
  it "does not emit an error when option -n 3 is supplied" do
28
40
  expect(CLI).not_to receive(:handle_error)
29
41
  CLI.parse(["-n", "3"])
@@ -6,25 +6,15 @@ module Passphrase
6
6
  expect(DicewareMethod).to respond_to(:run).with(1).argument
7
7
  end
8
8
 
9
- context "initialized with given options" do
9
+ context "initialized with default options" do
10
10
  before do
11
- @number_of_words = 5
12
- options = { number_of_words: @number_of_words, use_random_org: nil }
13
- @diceware = DicewareMethod.new(options)
11
+ @diceware = DicewareMethod.new(Default.options)
14
12
  end
15
13
 
16
14
  it "responds to the run() method with no arguments" do
17
15
  expect(@diceware).to respond_to(:run)
18
16
  end
19
17
 
20
- it "does not respond to the setup_database() method (private)" do
21
- expect(@diceware).not_to respond_to(:setup_database)
22
- end
23
-
24
- it "does not respond to the setup_queries() method (private)" do
25
- expect(@diceware).not_to respond_to(:setup_queries)
26
- end
27
-
28
18
  it "does not respond to the get_random_languages() method (private)" do
29
19
  expect(@diceware).not_to respond_to(:get_random_languages)
30
20
  end
@@ -50,9 +40,9 @@ module Passphrase
50
40
  )
51
41
  end
52
42
 
53
- it "each array contains :number_of_words elements" do
43
+ it "each array contains the default number of words elements" do
54
44
  @result.each do |result|
55
- expect(result.size).to eq(@number_of_words)
45
+ expect(result.size).to eq(Default.options[:number_of_words])
56
46
  end
57
47
  end
58
48
 
@@ -52,6 +52,24 @@ module Passphrase
52
52
  end
53
53
  end
54
54
 
55
+ describe "#indices(4, 1)" do
56
+ before do
57
+ @result = @random.indices(4, 1)
58
+ end
59
+
60
+ it "returns an array" do
61
+ expect(@result).to be_an_instance_of(Array)
62
+ end
63
+
64
+ it "of size 4" do
65
+ expect(@result.size).to eq(4)
66
+ end
67
+
68
+ it "where each element is zero" do
69
+ expect(@result).to all eq(0)
70
+ end
71
+ end
72
+
55
73
  describe "#die_rolls(6)" do
56
74
  before do
57
75
  @result = @random.die_rolls(6)
@@ -97,7 +115,7 @@ module Passphrase
97
115
  @random = DicewareRandom.new(true)
98
116
  end
99
117
 
100
- it "increments the random.org request count on indices()" do
118
+ it "increments the RANDOM.ORG request count on indices()" do
101
119
  expect {
102
120
  @random.indices(4, 15)
103
121
  }.to change(DicewareRandom, :random_org_requests).by(1)
@@ -0,0 +1,147 @@
1
+ require "passphrase"
2
+
3
+ module Passphrase
4
+ RSpec.describe WordlistDatabase, "Diceware wordlist database class" do
5
+ before do
6
+ @db = WordlistDatabase.connect
7
+ end
8
+
9
+ describe "#from(:languages)" do
10
+ it "returns a Language object" do
11
+ expect(@db.from(:languages)).to be_an_instance_of(Language)
12
+ end
13
+ end
14
+
15
+ describe "#[:languages]" do
16
+ it "returns a Language object" do
17
+ expect(@db[:languages]).to be_an_instance_of(Language)
18
+ end
19
+ end
20
+
21
+ describe "#from(:words)" do
22
+ it "returns a Word object" do
23
+ expect(@db.from(:words)).to be_an_instance_of(Word)
24
+ end
25
+ end
26
+
27
+ describe "#[:words]" do
28
+ it "returns a Word object" do
29
+ expect(@db[:words]).to be_an_instance_of(Word)
30
+ end
31
+ end
32
+
33
+ describe "#from(:nonexistant)" do
34
+ it "raises an error" do
35
+ expect { @db.from(:nonexistant) }.to raise_error("Unknown table nonexistant")
36
+ end
37
+ end
38
+ end
39
+
40
+ RSpec.describe Word, "queries the words table in a valid wordlist database" do
41
+ before do
42
+ @words = WordlistDatabase.connect.from(:words)
43
+ end
44
+
45
+ it "responds to the where() method with one argument" do
46
+ expect(@words).to respond_to(:where).with(1).argument
47
+ end
48
+
49
+ describe "#where([Hash])" do
50
+ before do
51
+ @selection = @words.where(language: "afrikaans", die_rolls: "11111")
52
+ end
53
+
54
+ it "returns a string" do
55
+ expect(@selection).to be_an_instance_of(String)
56
+ end
57
+
58
+ it "that is not empty" do
59
+ expect(@selection).not_to be_empty
60
+ end
61
+ end
62
+ end
63
+
64
+ RSpec.describe Language, "queries the languages table in a valid wordlist database" do
65
+ before do
66
+ @languages = WordlistDatabase.connect.from(:languages)
67
+ end
68
+
69
+ it "responds to the count() method with no arguments" do
70
+ expect(@languages).to respond_to(:count).with(0).arguments
71
+ end
72
+
73
+ it "responds to the []() method with one argument" do
74
+ expect(@languages).to respond_to(:[]).with(1).argument
75
+ end
76
+
77
+ it "responds to the all() method with no arguments" do
78
+ expect(@languages).to respond_to(:all).with(0).arguments
79
+ end
80
+
81
+ it "responds to the only() method with one argument" do
82
+ expect(@languages).to respond_to(:only).with(1).argument
83
+ end
84
+
85
+ it "does not respond to the validate() method (private)" do
86
+ expect(@languages).not_to respond_to(:validate)
87
+ end
88
+
89
+ describe "#count" do
90
+ it "returns 15" do
91
+ expect(@languages.count).to eq(15)
92
+ end
93
+ end
94
+
95
+ describe "#[](0)" do
96
+ before do
97
+ @language = @languages[0]
98
+ end
99
+
100
+ it "returns a string" do
101
+ expect(@language).to be_an_instance_of(String)
102
+ end
103
+
104
+ it "that is not empty" do
105
+ expect(@language).not_to be_empty
106
+ end
107
+ end
108
+
109
+ describe "#all" do
110
+ before do
111
+ @result = @languages.all
112
+ end
113
+
114
+ it "returns an array" do
115
+ expect(@result).to be_an_instance_of(Array)
116
+ end
117
+
118
+ it "with 15 elements" do
119
+ expect(@result.size).to eq(15)
120
+ end
121
+
122
+ it "of all strings" do
123
+ expect(@result).to all be_an_instance_of(String)
124
+ end
125
+ end
126
+
127
+ describe "#only([\"e\", \"fr\"])" do
128
+ before do
129
+ @languages.only(["e", "fr"])
130
+ end
131
+
132
+ it "produces a count of 2" do
133
+ expect(@languages.count).to eq(2)
134
+ end
135
+
136
+ it "returns only english and french" do
137
+ expect(@languages.all).to eq(%w( english french ))
138
+ end
139
+ end
140
+
141
+ describe "#only([\"zz\"])" do
142
+ it "raises an error" do
143
+ expect { @languages.only(["zz"]) }.to raise_error("No language match for zz")
144
+ end
145
+ end
146
+ end
147
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: passphrase
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Edmund Sumbar
@@ -30,7 +30,7 @@ cert_chain:
30
30
  aoKZhUfRmAZDUgzW4PMtMxO34fQDbDYevBzMcxmkHNDs87ROMOuPLWJGLcKpgGwJ
31
31
  TxzUtBfb7E+Qa/wyo0Ws7A==
32
32
  -----END CERTIFICATE-----
33
- date: 2015-04-15 00:00:00.000000000 Z
33
+ date: 2015-04-18 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: sqlite3
@@ -137,19 +137,17 @@ files:
137
137
  - lib/passphrase/default.rb
138
138
  - lib/passphrase/diceware_method.rb
139
139
  - lib/passphrase/diceware_random.rb
140
- - lib/passphrase/language_query.rb
141
140
  - lib/passphrase/passphrase.rb
142
141
  - lib/passphrase/passphrase_string.rb
143
142
  - lib/passphrase/version.rb
144
- - lib/passphrase/word_query.rb
145
143
  - lib/passphrase/wordlist/words.sqlite3
144
+ - lib/passphrase/wordlist_database.rb
146
145
  - spec/passphrase/cli_spec.rb
147
146
  - spec/passphrase/diceware_method_spec.rb
148
147
  - spec/passphrase/diceware_random_spec.rb
149
- - spec/passphrase/language_query_spec.rb
150
148
  - spec/passphrase/passphrase_spec.rb
151
149
  - spec/passphrase/passphrase_string_spec.rb
152
- - spec/passphrase/word_query_spec.rb
150
+ - spec/passphrase/wordlist_database_spec.rb
153
151
  - spec/spec_helper.rb
154
152
  homepage: https://github.com/esumbar/passphrase
155
153
  licenses:
metadata.gz.sig CHANGED
@@ -1 +1 @@
1
- S������@��0J����۔t��D_���_��h|2@N���I��扷��Xܟ����t2}P���aD�p����-Yid��F��,��4����$-k��Sp��r�N�7dd}0�]F��f�������m�.�R����d[fu���s���Fo�p�im�>����*�2�B�G��x�6���\���#-}��J���"y#@��>.]��ц!NJ��Dq���<ۢ'��N+���1���մ��
1
+ -��1�,x8X:����h6��j;��&RSLY<�Li`p�����q���'l��d9/�S�`�O!N]&_ɩ�����{>C��?�$��ʎ���� lu_R[][�~4r% Rr��g=@��x�]f/&%����8S0F�O|��X�Z$�cUC��6��Mh���:gz��ê��������i.��0ë2Xx���0IĹ�~I4-ٮ�+���b�%4 ���>�oc8G
@@ -1,26 +0,0 @@
1
- module Passphrase
2
- # This class encapsulates the {#count} and {#[]} queries against the
3
- # "languages" table in the "words" SQLite 3 database.
4
- class LanguageQuery
5
- def initialize(db)
6
- @db = db
7
- end
8
-
9
- # @return [Integer] the number of rows in the languages table
10
- def count
11
- sql = "SELECT COUNT(*) AS count FROM languages"
12
- @db.get_first_value(sql)
13
- end
14
-
15
- # @param index [Integer] selects a specific row in the languages table
16
- # @return [String] the language corresponding to the given index
17
- def [](index)
18
- sql = "SELECT language FROM languages"
19
- unless @languages
20
- @languages = []
21
- @db.execute(sql).each { |lang| @languages << lang.first }
22
- end
23
- @languages[index]
24
- end
25
- end
26
- end
@@ -1,21 +0,0 @@
1
- module Passphrase
2
- # This class encapsulates the {#where} query against the "words" table in
3
- # the "words" SQLite 3 database. The filter parameter must be a hash that
4
- # specifies a language and sequence of die rolls.
5
- # @example
6
- # {language: "afrikaans", die_rolls: "11111"}
7
- class WordQuery
8
- def initialize(db)
9
- @db = db
10
- end
11
-
12
- # @param filter [Hash] specifies the language/die_roll combination
13
- # @return [String] the selected words from the wordlist
14
- def where(filter)
15
- sql = "SELECT words " +
16
- "FROM words " +
17
- "WHERE language = :language AND die_rolls = :die_rolls"
18
- @db.get_first_value(sql, filter)
19
- end
20
- end
21
- end
@@ -1,48 +0,0 @@
1
- require "passphrase"
2
-
3
- module Passphrase
4
- RSpec.describe LanguageQuery, "queries the languages table in a valid wordlist database" do
5
- before do
6
- wordlist_file = "../../lib/passphrase/wordlist/words.sqlite3"
7
- wordlist_path = File.join(File.dirname(__FILE__), wordlist_file)
8
- db = SQLite3::Database.new(wordlist_path, readonly: true)
9
- @languages = LanguageQuery.new(db)
10
- end
11
-
12
- it "responds to the count() method with no arguments" do
13
- expect(@languages).to respond_to(:count).with(0).arguments
14
- end
15
-
16
- it "responds to the []() method with one argument" do
17
- expect(@languages).to respond_to(:[]).with(1).argument
18
- end
19
-
20
- context "#count" do
21
- before do
22
- @count = @languages.count
23
- end
24
-
25
- it "returns an integer" do
26
- expect(@count).to be_an_instance_of(Fixnum)
27
- end
28
-
29
- it "greater than zero" do
30
- expect(@count).to be > 0
31
- end
32
- end
33
-
34
- context "#[](0)" do
35
- before do
36
- @language = @languages[0]
37
- end
38
-
39
- it "returns a string" do
40
- expect(@language).to be_an_instance_of(String)
41
- end
42
-
43
- it "that is not empty" do
44
- expect(@language).not_to be_empty
45
- end
46
- end
47
- end
48
- end
@@ -1,30 +0,0 @@
1
- require "passphrase"
2
-
3
- module Passphrase
4
- RSpec.describe WordQuery, "queries the words table in a valid wordlist database" do
5
- before do
6
- wordlist_file = "../../lib/passphrase/wordlist/words.sqlite3"
7
- wordlist_path = File.join(File.dirname(__FILE__), wordlist_file)
8
- db = SQLite3::Database.new(wordlist_path, readonly: true)
9
- @words = WordQuery.new(db)
10
- end
11
-
12
- it "responds to the where() method with one argument" do
13
- expect(@words).to respond_to(:where).with(1).argument
14
- end
15
-
16
- context "#where([Hash])" do
17
- before do
18
- @selection = @words.where(language: "afrikaans", die_rolls: "11111")
19
- end
20
-
21
- it "returns a string" do
22
- expect(@selection).to be_an_instance_of(String)
23
- end
24
-
25
- it "that is not empty" do
26
- expect(@selection).not_to be_empty
27
- end
28
- end
29
- end
30
- end