spass 0.0.1 → 0.0.2

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