spass 0.0.1 → 0.0.2

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/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format doc
2
+ --color
data/History.md ADDED
@@ -0,0 +1,19 @@
1
+ History
2
+ =======
3
+
4
+ 0.0.2
5
+ -----
6
+
7
+ - Added command-line options
8
+ - Multiple-phrase generation
9
+ - Optional inclusion of digits
10
+ - Restricted word length
11
+
12
+
13
+ 0.0.1
14
+ -----
15
+
16
+ - Initial release
17
+ - Single phrase with random lowercase words
18
+
19
+
data/README.md CHANGED
@@ -47,11 +47,10 @@ if you want to use words from a different dictionary, pass a second argument:
47
47
  Future plans
48
48
  ------------
49
49
 
50
- - Optional inclusion of uppercase, numbers, symbols
50
+ - Optional inclusion of uppercase and symbols
51
51
  - Avoid word repetition
52
52
 
53
53
 
54
-
55
54
  MIT License
56
55
  -----------
57
56
 
data/bin/spass CHANGED
@@ -1,13 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'spass' # Is this needed?
3
+ require 'spass'
4
+ require 'optparse'
4
5
 
5
- USAGE = <<EOF
6
- spass: Generate a passphrase with lowercase words
7
-
8
- Usage:
9
-
10
- $ spass <length> [path_to_dict]
6
+ HELP = <<EOF
11
7
 
12
8
  This script generates a plain-English passphrase of at least the given length
13
9
  (in characters), using random words from the given dictionary file
@@ -17,15 +13,12 @@ nonsensical).
17
13
 
18
14
  Examples:
19
15
 
20
- $ spass 12
16
+ $ spass -l 12
21
17
  hive frighten
22
18
 
23
- $ spass 24
19
+ $ spass -l 24
24
20
  moppet castigator harvesters
25
21
 
26
- $ spass 32
27
- munificent icebound raymond clorets
28
-
29
22
  A 32-character passphrase has about 120 bits of entropy, which is overkill for
30
23
  most purposes. A 24-character passphrase clocks in at about 90 bits of entropy,
31
24
  suitable for most high-security applications like root passwords or financial
@@ -35,18 +28,65 @@ your passphrase after generating one that you like:
35
28
  http://rumkin.com/tools/password/passchk.php
36
29
 
37
30
  Inspired by http://xkcd.com/936/
31
+
38
32
  EOF
39
33
 
40
- if ARGV.count == 0
41
- puts USAGE
42
- else
43
- length = ARGV[0].to_i
44
- if ARGV[1]
45
- sp = SPass.new(ARGV[1])
46
- else
47
- sp = SPass.new
34
+ options = {
35
+ :length => 24,
36
+ :number => 10,
37
+ :dict => '/usr/share/dict/words',
38
+ :digits => false,
39
+ :chars => 10,
40
+ }
41
+
42
+ optparse = OptionParser.new do |opts|
43
+ opts.on('-l', '--length [NUM]', Integer,
44
+ "Ensure passphrases are at least NUM characters.",
45
+ "Default: #{options[:length]}") do |len|
46
+ options[:length] = len
47
+ end
48
+
49
+ opts.on('-n', '--number [NUM]', Integer,
50
+ "Generate NUM passphrases.",
51
+ "Default: #{options[:number]}") do |num|
52
+ options[:number] = num
53
+ end
54
+
55
+ opts.on('-f', '--file [FILE]', String,
56
+ "Read words from a FILE having one word per line.",
57
+ "Default: #{options[:dict]}") do |dict|
58
+ options[:dict] = dict
48
59
  end
49
- puts sp.generate(length)
60
+
61
+ opts.on('-d', '--digits',
62
+ "Include random numbers from 1-99.",
63
+ "Default: False") do |digits|
64
+ options[:digits] = digits
65
+ end
66
+
67
+ opts.on('-c', '--chars NUM',
68
+ "Limit words to NUM characters in length.",
69
+ "Default: 10") do |chars|
70
+ options[:chars] = chars
71
+ end
72
+
73
+ opts.on_tail('-h', '--help', 'Display the help page') do
74
+ puts opts
75
+ puts HELP
76
+ exit 0
77
+ end
78
+ end
79
+
80
+ begin
81
+ optparse.parse!
82
+ rescue OptionParser::InvalidOption => e
83
+ puts optparse
84
+ puts "*** #{e}"
85
+ exit 1
50
86
  end
51
87
 
88
+ sp = Generator.new(options[:dict], options)
89
+ options[:number].times do
90
+ puts sp.generate(options[:length], :digits=>options[:digits])
91
+ end
52
92
 
data/lib/spass.rb CHANGED
@@ -1,41 +1,47 @@
1
1
 
2
- class SPass
3
- attr_reader :dict_path, :dict_lines
4
-
5
- def initialize(dict_path='/usr/share/dict/words')
6
- @dict_path = File.expand_path(dict_path)
7
- if !File.file?(@dict_path)
8
- raise RuntimeError, "Cannot find dict file: #{@dict_path}"
9
- end
10
- @dict_lines = `wc -l #{@dict_path}`.split.first.to_i
2
+ class Generator
3
+ # Create a Generator using the given word dictionary, returning
4
+ # only words that match the given regular expression
5
+ def initialize(dict_path='/usr/share/dict/words', options={})
6
+ @dict_path = dict_path
7
+ @dict = read_dict(dict_path, options)
11
8
  end
12
9
 
13
- # Return a random line number from 1..N where N is the last line
14
- # in the dict file
15
- def random_line
16
- rand(@dict_lines) + 1
10
+ # Read a word dictionary from the given file, and return an array
11
+ # of elements that match the allowed regex. Raise an exception if
12
+ # the given dictionary file cannot be found.
13
+ def read_dict(path, options={})
14
+ allowed = options[:allowed] || /^[a-z]+$/
15
+ chars = (options[:chars] || 10).to_i
16
+ if !File.file?(path)
17
+ raise RuntimeError, "Cannot find dict file: #{path}"
18
+ end
19
+ dict = []
20
+ IO.readlines(path).each do |line|
21
+ line.strip!
22
+ if line.length <= chars && line =~ allowed
23
+ dict << line
24
+ end
25
+ end
26
+ return dict
17
27
  end
18
28
 
19
- # Return a random word from the dictionary, lowercased
29
+ # Return a random word from the dictionary
20
30
  def random_word
21
- cmd = "sed -n '#{random_line} {p;q;}' '#{@dict_path}'"
22
- `#{cmd}`.chomp.downcase
31
+ @dict[rand(@dict.count)]
23
32
  end
24
33
 
25
- # Return a random word that consists of only lowercase letters
26
- def random_ascii_word
27
- word = random_word
28
- while word =~ /[^a-z]/
29
- word = random_word
30
- end
31
- return word
34
+ # Return a random number from 1 to 99
35
+ def random_number
36
+ 1 + rand(99)
32
37
  end
33
38
 
34
39
  # Generate a passphrase of at least the given length in characters
35
- def generate(length)
40
+ def generate(length, options={})
36
41
  phrase = ''
37
42
  while phrase.length < length + 1 # to account for trailing space
38
- phrase += random_ascii_word + ' '
43
+ phrase += "#{random_word} "
44
+ phrase += "#{random_number} " if options[:digits]
39
45
  end
40
46
  return phrase.chomp
41
47
  end
data/spass.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "spass"
3
- s.version = "0.0.1"
3
+ s.version = "0.0.2"
4
4
  s.summary = "Generate passphrases"
5
5
  s.description = <<-EOS
6
6
  spass generates passphrases using random words
data/spec/spass_spec.rb CHANGED
@@ -2,39 +2,33 @@ require 'spass'
2
2
 
3
3
  TEST_DICT = File.expand_path('../data/ten_words', __FILE__)
4
4
 
5
- describe SPass do
6
- before(:each) do
7
- @sp = SPass.new(TEST_DICT)
5
+ describe Generator do
6
+ before(:all) do
7
+ @sp = Generator.new(TEST_DICT)
8
8
  end
9
9
 
10
10
 
11
- describe "#initialize" do
12
- it "correctly sets dict_path" do
13
- path = TEST_DICT
14
- @sp = SPass.new(path)
15
- @sp.dict_path.should == path
11
+ describe "#read_dict" do
12
+ it "returns an array of lines from the given file" do
13
+ @sp.read_dict(TEST_DICT).should == [
14
+ 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten']
16
15
  end
17
16
 
18
- it "raises an exception for invalid path" do
19
- path = File.expand_path('non/existent/path')
20
- lambda do
21
- @sp = SPass.new(path)
22
- end.should raise_error(RuntimeError, /Cannot find dict file/)
17
+ it "only returns words matching a regex" do
18
+ @sp.read_dict(TEST_DICT, :allowed=>/^t/).should == ['two', 'three', 'ten']
19
+ @sp.read_dict(TEST_DICT, :allowed=>/^f/).should == ['four', 'five']
20
+ @sp.read_dict(TEST_DICT, :allowed=>/e$/).should == ['one', 'three', 'five', 'nine']
23
21
  end
24
- end
25
-
26
22
 
27
- describe "#random_line" do
28
- it "is >= 1" do
29
- 100.times do
30
- @sp.random_line.should be >= 1
31
- end
23
+ it "only returns words with N chars or fewer" do
24
+ @sp.read_dict(TEST_DICT, :chars=>3).should == ['one', 'two', 'six', 'ten']
32
25
  end
33
26
 
34
- it "is <= number of lines" do
35
- 100.times do
36
- @sp.random_line.should be <= @sp.dict_lines
37
- end
27
+ it "raises an exception when the file does not exist" do
28
+ path = File.expand_path('non/existent/path')
29
+ lambda do
30
+ @sp.read_dict(path)
31
+ end.should raise_error(RuntimeError, /Cannot find dict file/)
38
32
  end
39
33
  end
40
34
 
@@ -48,14 +42,17 @@ describe SPass do
48
42
  end
49
43
 
50
44
 
51
- describe "#random_ascii_word" do
52
- it "only includes lowercase letters" do
53
- 10.times do
54
- @sp.random_ascii_word.should =~ /^[a-z]+$/
45
+ describe "#random_number" do
46
+ it "returns a number between 1 and 999" do
47
+ 1000.times do
48
+ num = @sp.random_number
49
+ num.should be >= 1
50
+ num.should be <= 99
55
51
  end
56
52
  end
57
53
  end
58
54
 
55
+
59
56
  describe "#generate" do
60
57
  it "is at least the given length" do
61
58
  [10, 15, 20, 25, 30, 35, 40].each do |len|
@@ -64,6 +61,12 @@ describe SPass do
64
61
  end
65
62
  end
66
63
  end
64
+
65
+ it "includes digits when :digits is true" do
66
+ 10.times do
67
+ @sp.generate(24, :digits=>true).should =~ /^([a-z]+ [0-9]+ ?)+$/
68
+ end
69
+ end
67
70
  end
68
71
  end
69
72
 
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spass
3
3
  version: !ruby/object:Gem::Version
4
- hash: 29
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 1
10
- version: 0.0.1
9
+ - 2
10
+ version: 0.0.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Eric Pierce
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-10-30 00:00:00 -06:00
18
+ date: 2011-11-01 00:00:00 -06:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -58,8 +58,10 @@ extra_rdoc_files: []
58
58
 
59
59
  files:
60
60
  - .gitignore
61
+ - .rspec
61
62
  - Gemfile
62
63
  - Gemfile.lock
64
+ - History.md
63
65
  - README.md
64
66
  - bin/spass
65
67
  - lib/spass.rb