keyrack 0.2.3 → 0.3.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +2 -0
- data/README.rdoc +9 -6
- data/Rakefile +3 -1
- data/VERSION +1 -1
- data/features/console.feature +33 -9
- data/features/step_definitions/keyrack_steps.rb +7 -1
- data/keyrack.gemspec +29 -16
- data/lib/keyrack/database.rb +70 -8
- data/lib/keyrack/runner.rb +2 -2
- data/lib/keyrack/ui/console.rb +53 -25
- data/test/keyrack/test_database.rb +35 -4
- data/test/keyrack/test_runner.rb +2 -2
- data/test/keyrack/test_utils.rb +1 -1
- data/test/keyrack/ui/test_console.rb +85 -27
- metadata +34 -23
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
data/README.rdoc
CHANGED
@@ -26,16 +26,19 @@ Running keyrack will display a simple menu in your terminal, like this:
|
|
26
26
|
=== Keyrack Main Menu ===
|
27
27
|
1. Twitter [foobar]
|
28
28
|
2. Facebook [foobar@example.com]
|
29
|
-
|
30
|
-
d
|
31
|
-
g. New group
|
32
|
-
s. Save
|
33
|
-
q. Quit
|
29
|
+
Mode: copy
|
30
|
+
Commands: [n]ew [d]elete [g]roup [s]ave [m]ode [q]uit
|
34
31
|
|
35
32
|
Selecting <b>1</b> in this case will copy the Twitter password for the foobar user
|
36
33
|
to your clipboard.
|
37
34
|
|
38
|
-
You can create groups to organize your sites.
|
35
|
+
You can create groups (using the 'group' command) to organize your sites.
|
36
|
+
|
37
|
+
There are two modes, <b>copy</b> (default) and <b>print</b>. In print mode,
|
38
|
+
keyrack will print out your password instead of copying it to your clipboard.
|
39
|
+
After printing, it will try to erase it after you hit a key (if you're on
|
40
|
+
win32 or have either {termios}[https://github.com/arika/ruby-termios]
|
41
|
+
or {ffi-ncurses}[http://rubygems.org/gems/ffi-ncurses] installed).
|
39
42
|
|
40
43
|
== Contributing to keyrack
|
41
44
|
|
data/Rakefile
CHANGED
@@ -50,7 +50,7 @@ end
|
|
50
50
|
|
51
51
|
task :default => :test
|
52
52
|
|
53
|
-
require '
|
53
|
+
require 'rdoc/task'
|
54
54
|
Rake::RDocTask.new do |rdoc|
|
55
55
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
56
56
|
|
@@ -59,3 +59,5 @@ Rake::RDocTask.new do |rdoc|
|
|
59
59
|
rdoc.rdoc_files.include('README*')
|
60
60
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
61
61
|
end
|
62
|
+
|
63
|
+
task :build => :gemspec
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0.pre
|
data/features/console.feature
CHANGED
@@ -12,11 +12,11 @@ Feature: Console runner
|
|
12
12
|
* the output should contain "Choose storage type:"
|
13
13
|
* I type "filesystem"
|
14
14
|
|
15
|
-
* the output should contain "g
|
15
|
+
* the output should contain "[g]roup"
|
16
16
|
* I type "g" to add a new group
|
17
17
|
* the output should contain "Group:"
|
18
18
|
* I type "Social"
|
19
|
-
* the output should contain "n
|
19
|
+
* the output should contain "[n]ew"
|
20
20
|
* I type "n" to add a new entry
|
21
21
|
* the output should contain "Label:"
|
22
22
|
* I type "Twitter"
|
@@ -29,13 +29,13 @@ Feature: Console runner
|
|
29
29
|
* the output should contain "Password (again):"
|
30
30
|
* I type "kittens"
|
31
31
|
* the output should contain "1. Twitter [dudeguy]"
|
32
|
-
* the output should also contain "s
|
32
|
+
* the output should also contain "[s]ave"
|
33
33
|
* I type "s" to save the database
|
34
|
-
* the output should contain "t
|
34
|
+
* the output should contain "[t]op"
|
35
35
|
* I type "t"
|
36
36
|
* the output should match /1\. .+Social.+/
|
37
37
|
|
38
|
-
* the output should also contain "n
|
38
|
+
* the output should also contain "[n]ew"
|
39
39
|
* I type "n" to add a new entry
|
40
40
|
* the output should contain "Label:"
|
41
41
|
* I type "Company X"
|
@@ -46,10 +46,20 @@ Feature: Console runner
|
|
46
46
|
* the output should contain "Sound good? [yn]"
|
47
47
|
* I type "y" for yes
|
48
48
|
* the output should contain "2. Company X [buddypal]"
|
49
|
-
* the output should also contain "s
|
49
|
+
* the output should also contain "[s]ave"
|
50
|
+
* I type "n" to add a new entry
|
51
|
+
* the output should contain "Label:"
|
52
|
+
* I type "Company X"
|
53
|
+
* the output should contain "Username:"
|
54
|
+
* I type "friendguy"
|
55
|
+
* the output should contain "Generate password?"
|
56
|
+
* I type "y" for yes
|
57
|
+
* the output should contain "Sound good? [yn]"
|
58
|
+
* I type "y" for yes
|
59
|
+
* the output should contain "3. Company X [friendguy]"
|
50
60
|
* I type "s" to save the database
|
51
61
|
|
52
|
-
* the output should contain "q
|
62
|
+
* the output should contain "[q]uit"
|
53
63
|
* I type "q" to quit
|
54
64
|
* I wait a few seconds
|
55
65
|
* I run keyrack interactively again
|
@@ -59,21 +69,35 @@ Feature: Console runner
|
|
59
69
|
* I wait a few seconds
|
60
70
|
* the output should match /1\. .+Social.+/
|
61
71
|
* the output should also contain "2. Company X [buddypal]"
|
72
|
+
* the output should also contain "3. Company X [friendguy]"
|
62
73
|
* I type "1" for Social
|
63
74
|
* the output should contain "1. Twitter [dudeguy]"
|
64
75
|
* I type "1" for Twitter
|
65
76
|
* my clipboard should contain "kittens"
|
66
77
|
|
67
|
-
* the output should contain "d
|
78
|
+
* the output should contain "[d]elete"
|
68
79
|
* I type "d"
|
69
80
|
* the output should contain "1. Twitter [dudeguy]"
|
70
81
|
* I type "1"
|
71
82
|
* the output should contain "Are you sure?"
|
72
83
|
* I type "y"
|
73
|
-
* the output should contain "t
|
84
|
+
* the output should contain "[t]op"
|
74
85
|
* I type "t"
|
75
86
|
|
87
|
+
* the output should contain "2. Company X [buddypal]"
|
88
|
+
* I type "2" for Company X (buddypal)
|
89
|
+
* my clipboard should match "^.{8}$"
|
90
|
+
|
91
|
+
* the output should contain "Main Menu"
|
92
|
+
* I type "d"
|
93
|
+
* the output should contain "1. Company X [buddypal]"
|
94
|
+
* I type "1"
|
95
|
+
* the output should contain "Company X [buddypal]"
|
96
|
+
* the output should also contain "Are you sure?"
|
97
|
+
* I type "y"
|
98
|
+
|
76
99
|
* the output should contain "Main Menu"
|
100
|
+
* the output should also contain "2. Company X [friendguy]"
|
77
101
|
* I type "q" to quit
|
78
102
|
* the output should contain "Really quit?" (since the database is dirty)
|
79
103
|
* I type "y"
|
@@ -21,7 +21,7 @@ Then /the output should contain "([^"]+)"/ do |expected|
|
|
21
21
|
sleep 1
|
22
22
|
end
|
23
23
|
@output = @out.read_nonblock(255)
|
24
|
-
assert @output.include?(expected)
|
24
|
+
assert @output.include?(expected), "Output: #{@output.inspect}"
|
25
25
|
end
|
26
26
|
|
27
27
|
Then %r{the output should match /([^/]+)/} do |expected|
|
@@ -53,3 +53,9 @@ Then /my clipboard should contain "([^"]+)"/ do |expected|
|
|
53
53
|
result = %x{xclip -selection clipboard -o}.chomp
|
54
54
|
assert_equal expected, result
|
55
55
|
end
|
56
|
+
|
57
|
+
Then /my clipboard should match "([^"]+)"/ do |expected|
|
58
|
+
sleep 1
|
59
|
+
result = %x{xclip -selection clipboard -o}.chomp
|
60
|
+
assert_match Regexp.new(expected), result
|
61
|
+
end
|
data/keyrack.gemspec
CHANGED
@@ -4,19 +4,19 @@
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name =
|
8
|
-
s.version = "0.
|
7
|
+
s.name = "keyrack"
|
8
|
+
s.version = "0.3.0.pre"
|
9
9
|
|
10
|
-
s.required_rubygems_version = Gem::Requirement.new("
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jeremy Stephens"]
|
12
|
-
s.date =
|
13
|
-
s.
|
14
|
-
s.
|
15
|
-
s.email = %q{viking@pillageandplunder.net}
|
12
|
+
s.date = "2012-01-11"
|
13
|
+
s.description = "Simple password manager with local/remote database storage and RSA encryption."
|
14
|
+
s.email = "viking@pillageandplunder.net"
|
16
15
|
s.executables = ["keyrack"]
|
17
16
|
s.extra_rdoc_files = [
|
18
17
|
"LICENSE.txt",
|
19
|
-
"README.rdoc"
|
18
|
+
"README.rdoc",
|
19
|
+
"TODO"
|
20
20
|
]
|
21
21
|
s.files = [
|
22
22
|
".document",
|
@@ -26,8 +26,13 @@ Gem::Specification.new do |s|
|
|
26
26
|
"LICENSE.txt",
|
27
27
|
"README.rdoc",
|
28
28
|
"Rakefile",
|
29
|
+
"TODO",
|
29
30
|
"VERSION",
|
30
31
|
"bin/keyrack",
|
32
|
+
"features/console.feature",
|
33
|
+
"features/step_definitions/keyrack_steps.rb",
|
34
|
+
"features/support/env.rb",
|
35
|
+
"keyrack.gemspec",
|
31
36
|
"lib/keyrack.rb",
|
32
37
|
"lib/keyrack/database.rb",
|
33
38
|
"lib/keyrack/runner.rb",
|
@@ -36,23 +41,25 @@ Gem::Specification.new do |s|
|
|
36
41
|
"lib/keyrack/store/ssh.rb",
|
37
42
|
"lib/keyrack/ui.rb",
|
38
43
|
"lib/keyrack/ui/console.rb",
|
44
|
+
"lib/keyrack/utils.rb",
|
45
|
+
"test/fixtures/aes",
|
39
46
|
"test/fixtures/config.yml",
|
40
47
|
"test/fixtures/foo.txt",
|
41
48
|
"test/fixtures/id_rsa",
|
42
|
-
"test/fixtures/id_rsa.pub",
|
43
49
|
"test/helper.rb",
|
44
50
|
"test/keyrack/store/test_filesystem.rb",
|
45
51
|
"test/keyrack/store/test_ssh.rb",
|
46
52
|
"test/keyrack/test_database.rb",
|
47
53
|
"test/keyrack/test_runner.rb",
|
48
54
|
"test/keyrack/test_store.rb",
|
55
|
+
"test/keyrack/test_utils.rb",
|
49
56
|
"test/keyrack/ui/test_console.rb"
|
50
57
|
]
|
51
|
-
s.homepage =
|
58
|
+
s.homepage = "http://github.com/viking/keyrack"
|
52
59
|
s.licenses = ["MIT"]
|
53
60
|
s.require_paths = ["lib"]
|
54
|
-
s.rubygems_version =
|
55
|
-
s.summary =
|
61
|
+
s.rubygems_version = "1.8.11"
|
62
|
+
s.summary = "Simple password manager"
|
56
63
|
s.test_files = [
|
57
64
|
"test/helper.rb",
|
58
65
|
"test/keyrack/store/test_filesystem.rb",
|
@@ -60,38 +67,44 @@ Gem::Specification.new do |s|
|
|
60
67
|
"test/keyrack/test_database.rb",
|
61
68
|
"test/keyrack/test_runner.rb",
|
62
69
|
"test/keyrack/test_store.rb",
|
70
|
+
"test/keyrack/test_utils.rb",
|
63
71
|
"test/keyrack/ui/test_console.rb"
|
64
72
|
]
|
65
73
|
|
66
74
|
if s.respond_to? :specification_version then
|
67
|
-
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
68
75
|
s.specification_version = 3
|
69
76
|
|
70
77
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
71
78
|
s.add_runtime_dependency(%q<net-scp>, [">= 0"])
|
72
79
|
s.add_runtime_dependency(%q<highline>, [">= 0"])
|
73
|
-
s.add_runtime_dependency(%q<
|
80
|
+
s.add_runtime_dependency(%q<copier>, [">= 0"])
|
74
81
|
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
75
82
|
s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
|
76
83
|
s.add_development_dependency(%q<rcov>, [">= 0"])
|
77
84
|
s.add_development_dependency(%q<mocha>, [">= 0"])
|
85
|
+
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
86
|
+
s.add_development_dependency(%q<test-unit>, [">= 0"])
|
78
87
|
else
|
79
88
|
s.add_dependency(%q<net-scp>, [">= 0"])
|
80
89
|
s.add_dependency(%q<highline>, [">= 0"])
|
81
|
-
s.add_dependency(%q<
|
90
|
+
s.add_dependency(%q<copier>, [">= 0"])
|
82
91
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
83
92
|
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
84
93
|
s.add_dependency(%q<rcov>, [">= 0"])
|
85
94
|
s.add_dependency(%q<mocha>, [">= 0"])
|
95
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
96
|
+
s.add_dependency(%q<test-unit>, [">= 0"])
|
86
97
|
end
|
87
98
|
else
|
88
99
|
s.add_dependency(%q<net-scp>, [">= 0"])
|
89
100
|
s.add_dependency(%q<highline>, [">= 0"])
|
90
|
-
s.add_dependency(%q<
|
101
|
+
s.add_dependency(%q<copier>, [">= 0"])
|
91
102
|
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
92
103
|
s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
|
93
104
|
s.add_dependency(%q<rcov>, [">= 0"])
|
94
105
|
s.add_dependency(%q<mocha>, [">= 0"])
|
106
|
+
s.add_dependency(%q<cucumber>, [">= 0"])
|
107
|
+
s.add_dependency(%q<test-unit>, [">= 0"])
|
95
108
|
end
|
96
109
|
end
|
97
110
|
|
data/lib/keyrack/database.rb
CHANGED
@@ -10,18 +10,56 @@ module Keyrack
|
|
10
10
|
|
11
11
|
def add(site, username, password, options = {})
|
12
12
|
hash = options[:group] ? @data[options[:group]] ||= {} : @data
|
13
|
-
hash
|
13
|
+
if hash.has_key?(site)
|
14
|
+
site_entry = hash[site]
|
15
|
+
if site_entry.is_a?(Array)
|
16
|
+
# Multiple entries for this site
|
17
|
+
user_entry = site_entry.detect { |e| e[:username] == username }
|
18
|
+
if user_entry
|
19
|
+
# Update existing entry
|
20
|
+
user_entry[:password] = password
|
21
|
+
else
|
22
|
+
# Add new entry
|
23
|
+
site_entry.push({:username => username, :password => password})
|
24
|
+
end
|
25
|
+
elsif site_entry[:username] == username
|
26
|
+
# Update existing entry
|
27
|
+
site_entry[:password] = password
|
28
|
+
else
|
29
|
+
# Convert single entry into an array, then add new entry
|
30
|
+
hash[site] = [site_entry, {:username => username, :password => password}]
|
31
|
+
end
|
32
|
+
else
|
33
|
+
hash[site] = { :username => username, :password => password }
|
34
|
+
end
|
14
35
|
@dirty = true
|
15
36
|
end
|
16
37
|
|
17
|
-
def get(
|
18
|
-
|
38
|
+
def get(*args)
|
39
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
40
|
+
site, username = args
|
41
|
+
|
42
|
+
site_entry = (options[:group] ? @data[options[:group]] : @data)[site]
|
43
|
+
if username
|
44
|
+
if site_entry.is_a?(Array)
|
45
|
+
site_entry.find { |e| e[:username] == username }
|
46
|
+
elsif site_entry[:username] == username
|
47
|
+
site_entry
|
48
|
+
else
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
else
|
52
|
+
site_entry
|
53
|
+
end
|
19
54
|
end
|
20
55
|
|
21
56
|
def sites(options = {})
|
22
57
|
hash = options[:group] ? @data[options[:group]] : @data
|
23
58
|
if hash
|
24
|
-
hash.keys.select
|
59
|
+
hash.keys.select do |key|
|
60
|
+
val = hash[key]
|
61
|
+
val.is_a?(Array) || (val.is_a?(Hash) && val.has_key?(:username))
|
62
|
+
end.sort
|
25
63
|
else
|
26
64
|
# new groups are empty
|
27
65
|
[]
|
@@ -29,7 +67,10 @@ module Keyrack
|
|
29
67
|
end
|
30
68
|
|
31
69
|
def groups
|
32
|
-
@data.keys.reject
|
70
|
+
@data.keys.reject do |key|
|
71
|
+
val = @data[key]
|
72
|
+
val.is_a?(Array) || (val.is_a?(Hash) && val.has_key?(:username))
|
73
|
+
end.sort
|
33
74
|
end
|
34
75
|
|
35
76
|
def dirty?
|
@@ -43,10 +84,31 @@ module Keyrack
|
|
43
84
|
@dirty = false
|
44
85
|
end
|
45
86
|
|
46
|
-
def delete(site, options = {})
|
87
|
+
def delete(site, username, options = {})
|
47
88
|
hash = options[:group] ? @data[options[:group]] : @data
|
48
|
-
hash
|
49
|
-
|
89
|
+
site_entry = hash[site]
|
90
|
+
|
91
|
+
if site_entry.is_a?(Array)
|
92
|
+
site_entry.each_with_index do |entry, i|
|
93
|
+
if entry[:username] == username
|
94
|
+
case site_entry.length
|
95
|
+
when 2
|
96
|
+
site_entry.delete_at(i)
|
97
|
+
hash[site] = site_entry[0]
|
98
|
+
when 1
|
99
|
+
hash.delete(site)
|
100
|
+
else
|
101
|
+
site_entry.delete_at(i)
|
102
|
+
end
|
103
|
+
|
104
|
+
@dirty = true
|
105
|
+
break
|
106
|
+
end
|
107
|
+
end
|
108
|
+
elsif site_entry[:username] == username
|
109
|
+
hash.delete(site)
|
110
|
+
@dirty = true
|
111
|
+
end
|
50
112
|
end
|
51
113
|
|
52
114
|
private
|
data/lib/keyrack/runner.rb
CHANGED
@@ -13,8 +13,8 @@ module Keyrack
|
|
13
13
|
if Dir.exist?(@config_path)
|
14
14
|
@options = YAML.load_file(File.join(@config_path, "config"))
|
15
15
|
password = @ui.get_password
|
16
|
-
rsa_key = Utils.open_rsa_key(@options['rsa'], password)
|
17
|
-
aes_data = Utils.open_aes_data(@options['aes'], rsa_key)
|
16
|
+
rsa_key = Utils.open_rsa_key(File.expand_path(@options['rsa'], @config_path), password)
|
17
|
+
aes_data = Utils.open_aes_data(File.expand_path(@options['aes'], @config_path), rsa_key)
|
18
18
|
else
|
19
19
|
Dir.mkdir(@config_path)
|
20
20
|
@options = {}
|
data/lib/keyrack/ui/console.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
module Keyrack
|
2
2
|
module UI
|
3
3
|
class Console
|
4
|
-
attr_accessor :database
|
4
|
+
attr_accessor :database, :mode
|
5
|
+
|
5
6
|
def initialize
|
6
7
|
@highline = HighLine.new
|
8
|
+
@mode = :copy
|
7
9
|
end
|
8
10
|
|
9
11
|
def get_password
|
@@ -11,52 +13,64 @@ module Keyrack
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def menu(options = {})
|
14
|
-
choices = {'n' => :new, 'q' => :quit}
|
16
|
+
choices = {'n' => :new, 'q' => :quit, 'm' => :mode}
|
15
17
|
index = 1
|
16
18
|
|
19
|
+
sites = @database.sites(options)
|
20
|
+
count = sites.length
|
21
|
+
count += @database.groups.length if !options[:group]
|
22
|
+
width = count / 10
|
23
|
+
|
17
24
|
if !options[:group]
|
18
25
|
# Can't have subgroups (yet?).
|
19
26
|
@highline.say("=== #{@highline.color("Keyrack Main Menu", :yellow)} ===")
|
20
27
|
@database.groups.each do |group|
|
21
28
|
choices[index.to_s] = {:group => group}
|
22
|
-
@highline.say("
|
29
|
+
@highline.say(" %#{width}d. %s" % [index, @highline.color(group, :green)])
|
23
30
|
index += 1
|
24
31
|
end
|
25
32
|
else
|
26
33
|
@highline.say("===== #{@highline.color(options[:group], :green)} =====")
|
27
34
|
end
|
28
35
|
|
29
|
-
sites = @database.sites(options)
|
30
36
|
sites.each do |site|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
37
|
+
site_entry = @database.get(site, options)
|
38
|
+
site_entry = [site_entry] unless site_entry.is_a?(Array)
|
39
|
+
site_entry.each do |entry|
|
40
|
+
choices[index.to_s] = entry
|
41
|
+
@highline.say(" %#{width}d. %s [%s]" % [index, site, entry[:username]])
|
42
|
+
index += 1
|
43
|
+
end
|
35
44
|
end
|
36
45
|
|
37
|
-
@highline.say("
|
46
|
+
@highline.say("Mode: #{mode}")
|
47
|
+
commands = "Commands: [n]ew"
|
38
48
|
if !sites.empty?
|
39
49
|
choices['d'] = :delete
|
40
|
-
|
50
|
+
commands << " [d]elete"
|
41
51
|
end
|
42
52
|
if !options[:group]
|
43
53
|
choices['g'] = :new_group
|
44
|
-
|
54
|
+
commands << " [g]roup"
|
45
55
|
else
|
46
56
|
choices['t'] = :top
|
47
|
-
|
57
|
+
commands << " [t]op"
|
48
58
|
end
|
49
59
|
if @database.dirty?
|
50
60
|
choices['s'] = :save
|
51
|
-
|
61
|
+
commands << " [s]ave"
|
52
62
|
end
|
53
|
-
|
54
|
-
|
63
|
+
commands << " [m]ode [q]uit"
|
64
|
+
@highline.say(commands)
|
65
|
+
answer = @highline.ask(" ? ") { |q| q.in = choices.keys }
|
55
66
|
result = choices[answer]
|
56
67
|
case result
|
57
68
|
when Symbol
|
58
69
|
if result == :quit && @database.dirty? && !@highline.agree("Really quit? You have unsaved changes! [yn] ")
|
59
70
|
nil
|
71
|
+
elsif result == :mode
|
72
|
+
@mode = @mode == :copy ? :print : :copy
|
73
|
+
nil
|
60
74
|
else
|
61
75
|
result
|
62
76
|
end
|
@@ -64,8 +78,19 @@ module Keyrack
|
|
64
78
|
if result.has_key?(:group)
|
65
79
|
result
|
66
80
|
else
|
67
|
-
|
68
|
-
|
81
|
+
if mode == :copy
|
82
|
+
Copier(result[:password])
|
83
|
+
@highline.say("The password has been copied to your clipboard.")
|
84
|
+
elsif mode == :print
|
85
|
+
password = @highline.color(result[:password], :cyan)
|
86
|
+
@highline.ask("Here you go: #{password}. Done? ") do |question|
|
87
|
+
question.echo = false
|
88
|
+
if HighLine::SystemExtensions::CHARACTER_MODE != 'stty'
|
89
|
+
question.character = true
|
90
|
+
question.overwrite = true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
69
94
|
nil
|
70
95
|
end
|
71
96
|
end
|
@@ -83,7 +108,7 @@ module Keyrack
|
|
83
108
|
if @highline.agree("Generate password? [yn] ")
|
84
109
|
loop do
|
85
110
|
password = Utils.generate_password
|
86
|
-
if @highline.agree("Generated #{@highline.color(password, :
|
111
|
+
if @highline.agree("Generated #{@highline.color(password, :cyan)}. Sound good? [yn] ")
|
87
112
|
result[:password] = password
|
88
113
|
break
|
89
114
|
end
|
@@ -96,7 +121,7 @@ module Keyrack
|
|
96
121
|
result[:password] = password
|
97
122
|
break
|
98
123
|
end
|
99
|
-
@highline.say("Passwords didn't match.
|
124
|
+
@highline.say("Passwords didn't match. Try again!")
|
100
125
|
end
|
101
126
|
end
|
102
127
|
result
|
@@ -120,7 +145,7 @@ module Keyrack
|
|
120
145
|
def store_setup
|
121
146
|
result = {}
|
122
147
|
result['type'] = @highline.choose do |menu|
|
123
|
-
menu.header = "Choose storage type
|
148
|
+
menu.header = "Choose storage type"
|
124
149
|
menu.choices("filesystem", "ssh")
|
125
150
|
end
|
126
151
|
|
@@ -141,10 +166,13 @@ module Keyrack
|
|
141
166
|
index = 1
|
142
167
|
@highline.say("Choose entry to delete:")
|
143
168
|
@database.sites(options).each do |site|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
169
|
+
site_entry = @database.get(site, options)
|
170
|
+
site_entry = [site_entry] unless site_entry.is_a?(Array)
|
171
|
+
site_entry.each do |entry|
|
172
|
+
choices[index.to_s] = {:site => site, :username => entry[:username]}
|
173
|
+
@highline.say("% 2d. %s [%s]" % [index, site, entry[:username]])
|
174
|
+
index += 1
|
175
|
+
end
|
148
176
|
end
|
149
177
|
@highline.say(" c. Cancel")
|
150
178
|
|
@@ -153,7 +181,7 @@ module Keyrack
|
|
153
181
|
if result != :cancel
|
154
182
|
entry = @highline.color("#{result[:site]} [#{result[:username]}]", :red)
|
155
183
|
if @highline.agree("You're about to delete #{entry}. Are you sure? [yn] ")
|
156
|
-
@database.delete(result[:site], options)
|
184
|
+
@database.delete(result[:site], result[:username], options)
|
157
185
|
end
|
158
186
|
end
|
159
187
|
end
|
@@ -26,7 +26,7 @@ module Keyrack
|
|
26
26
|
def test_reading_existing_database
|
27
27
|
database = Keyrack::Database.new(@key, @iv, @store)
|
28
28
|
expected = {:username => 'username', :password => 'password'}
|
29
|
-
assert_equal(expected, database.get('Twitter'))
|
29
|
+
assert_equal(expected, database.get('Twitter', 'username'))
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_sites
|
@@ -61,20 +61,51 @@ module Keyrack
|
|
61
61
|
def test_add_with_top_level_group
|
62
62
|
@database.add('Twitter', 'dudeguy', 'secret', :group => "Social")
|
63
63
|
expected = {:username => 'dudeguy', :password => 'secret'}
|
64
|
-
assert_equal expected, @database.get('Twitter', :group => "Social")
|
64
|
+
assert_equal expected, @database.get('Twitter', 'dudeguy', :group => "Social")
|
65
65
|
end
|
66
66
|
|
67
67
|
def test_delete
|
68
|
-
@database.delete('Twitter')
|
68
|
+
@database.delete('Twitter', 'username')
|
69
69
|
assert_equal [], @database.sites
|
70
70
|
assert @database.dirty?
|
71
71
|
end
|
72
72
|
|
73
|
+
def test_delete_non_existant_entry
|
74
|
+
@database.delete('Twitter', 'foobar')
|
75
|
+
assert_equal ['Twitter'], @database.sites
|
76
|
+
assert !@database.dirty?
|
77
|
+
end
|
78
|
+
|
73
79
|
def test_delete_group_entry
|
74
80
|
@database.add('Facebook', 'dudeguy', 'secret', :group => "Social")
|
75
|
-
@database.delete('Facebook', :group => 'Social')
|
81
|
+
@database.delete('Facebook', 'dudeguy', :group => 'Social')
|
76
82
|
assert_equal [], @database.sites(:group => 'Social')
|
77
83
|
assert_equal ['Twitter'], @database.sites
|
78
84
|
end
|
85
|
+
|
86
|
+
def test_multiple_entries_with_the_same_site
|
87
|
+
@database.add('Facebook', 'dudeguy', 'secret')
|
88
|
+
@database.add('Facebook', 'foobar', 'secret')
|
89
|
+
|
90
|
+
expected_1 = {:username => 'dudeguy', :password => 'secret'}
|
91
|
+
assert_equal expected_1, @database.get('Facebook', 'dudeguy')
|
92
|
+
expected_2 = {:username => 'foobar', :password => 'secret'}
|
93
|
+
assert_equal expected_2, @database.get('Facebook', 'foobar')
|
94
|
+
assert_equal [expected_1, expected_2], @database.get('Facebook')
|
95
|
+
assert_equal ['Facebook', 'Twitter'], @database.sites
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_get_missing_entry_by_site_and_username
|
99
|
+
@database.add('Facebook', 'dudeguy', 'secret')
|
100
|
+
assert_nil @database.get('Facebook', 'foobar')
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_deleting_one_of_two_entries_with_the_same_site
|
104
|
+
@database.add('Facebook', 'dudeguy', 'secret')
|
105
|
+
@database.add('Facebook', 'foobar', 'secret')
|
106
|
+
@database.delete('Facebook', 'dudeguy')
|
107
|
+
assert_nil @database.get('Facebook', 'dudeguy')
|
108
|
+
assert_equal({:username => 'foobar', :password => 'secret'}, @database.get('Facebook', 'foobar'))
|
109
|
+
end
|
79
110
|
end
|
80
111
|
end
|
data/test/keyrack/test_runner.rb
CHANGED
@@ -30,9 +30,9 @@ module Keyrack
|
|
30
30
|
seq = sequence('ui sequence')
|
31
31
|
@console.expects(:get_password).returns('secret').in_sequence(seq)
|
32
32
|
rsa = mock("rsa key")
|
33
|
-
Utils.expects(:open_rsa_key).with(rsa_path, 'secret').returns(rsa).in_sequence(seq)
|
33
|
+
Utils.expects(:open_rsa_key).with(File.expand_path(rsa_path, keyrack_dir), 'secret').returns(rsa).in_sequence(seq)
|
34
34
|
aes = {'key' => '12345', 'iv' => '54321'}
|
35
|
-
Utils.expects(:open_aes_data).with(aes_path, rsa).returns(aes).in_sequence(seq)
|
35
|
+
Utils.expects(:open_aes_data).with(File.expand_path(aes_path, keyrack_dir), rsa).returns(aes).in_sequence(seq)
|
36
36
|
store = mock('filesystem store')
|
37
37
|
Store::Filesystem.expects(:new).with('path' => store_path).returns(store).in_sequence(seq)
|
38
38
|
Database.expects(:new).with('12345', '54321', store).returns(@database).in_sequence(seq)
|
data/test/keyrack/test_utils.rb
CHANGED
@@ -4,10 +4,14 @@ module Keyrack
|
|
4
4
|
module UI
|
5
5
|
class TestConsole < Test::Unit::TestCase
|
6
6
|
def setup
|
7
|
-
@database = stub('database', :sites => %w{Twitter}, :groups => [], :dirty? => false) do
|
7
|
+
@database = stub('database', :sites => %w{Twitter Google}, :groups => [], :dirty? => false) do
|
8
8
|
stubs(:get).with('Twitter', {}).returns({
|
9
9
|
:username => 'username', :password => 'password'
|
10
10
|
})
|
11
|
+
stubs(:get).with('Google', {}).returns([
|
12
|
+
{ :username => 'username_1', :password => 'password' },
|
13
|
+
{ :username => 'username_2', :password => 'password' }
|
14
|
+
])
|
11
15
|
end
|
12
16
|
@highline = stub('highline')
|
13
17
|
@highline.stubs(:color).with("Keyrack Main Menu", :yellow).returns("yellowKeyrack Main Menu")
|
@@ -17,21 +21,50 @@ module Keyrack
|
|
17
21
|
end
|
18
22
|
|
19
23
|
def test_select_entry_from_menu
|
24
|
+
seq = sequence('say')
|
20
25
|
@console.database = @database
|
21
26
|
@highline.expects(:say).with("=== yellowKeyrack Main Menu ===")
|
22
27
|
@highline.expects(:say).with(" 1. Twitter [username]")
|
23
|
-
@highline.expects(:say).with("
|
24
|
-
@highline.expects(:say).with("
|
25
|
-
@highline.expects(:say).with("
|
26
|
-
@highline.expects(:say).with(" q
|
28
|
+
@highline.expects(:say).with(" 2. Google [username_1]")
|
29
|
+
@highline.expects(:say).with(" 3. Google [username_2]")
|
30
|
+
@highline.expects(:say).with("Mode: copy")
|
31
|
+
@highline.expects(:say).with("Commands: [n]ew [d]elete [g]roup [m]ode [q]uit")
|
27
32
|
|
28
33
|
question = mock('question')
|
29
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d g}) }).returns('1')
|
34
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g}) }).returns('1')
|
30
35
|
@console.expects(:Copier).with('password')
|
31
36
|
@highline.expects(:say).with("The password has been copied to your clipboard.")
|
32
37
|
assert_nil @console.menu
|
33
38
|
end
|
34
39
|
|
40
|
+
def test_select_entry_from_menu_in_print_mode
|
41
|
+
seq = sequence('say')
|
42
|
+
@console.database = @database
|
43
|
+
@console.mode = :print
|
44
|
+
@highline.expects(:say).with("=== yellowKeyrack Main Menu ===")
|
45
|
+
@highline.expects(:say).with(" 1. Twitter [username]")
|
46
|
+
@highline.expects(:say).with(" 2. Google [username_1]")
|
47
|
+
@highline.expects(:say).with(" 3. Google [username_2]")
|
48
|
+
@highline.expects(:say).with("Mode: print")
|
49
|
+
@highline.expects(:say).with("Commands: [n]ew [d]elete [g]roup [m]ode [q]uit")
|
50
|
+
|
51
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g}) }).returns('1')
|
52
|
+
@highline.expects(:color).with('password', :cyan).returns('cyan[password]').in_sequence(seq)
|
53
|
+
question = mock do
|
54
|
+
expects(:echo=).with(false)
|
55
|
+
if HighLine::SystemExtensions::CHARACTER_MODE != 'stty'
|
56
|
+
expects(:character=).with(true)
|
57
|
+
expects(:overwrite=).with(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
@highline.expects(:ask).
|
61
|
+
with('Here you go: cyan[password]. Done? ').
|
62
|
+
yields(question).
|
63
|
+
returns('')
|
64
|
+
|
65
|
+
assert_nil @console.menu
|
66
|
+
end
|
67
|
+
|
35
68
|
def test_select_new_from_menu
|
36
69
|
@console.database = @database
|
37
70
|
|
@@ -42,7 +75,7 @@ module Keyrack
|
|
42
75
|
# g. New group
|
43
76
|
# q. Quit
|
44
77
|
|
45
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d g}) }).returns('n')
|
78
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g}) }).returns('n')
|
46
79
|
assert_equal :new, @console.menu
|
47
80
|
end
|
48
81
|
|
@@ -57,7 +90,7 @@ module Keyrack
|
|
57
90
|
# q. Quit
|
58
91
|
|
59
92
|
question = mock('question')
|
60
|
-
question.expects(:in=).with(%w{n q 1 d g})
|
93
|
+
question.expects(:in=).with(%w{n q m 1 2 3 d g})
|
61
94
|
@highline.expects(:ask).yields(question).returns('d')
|
62
95
|
assert_equal :delete, @console.menu
|
63
96
|
end
|
@@ -72,7 +105,7 @@ module Keyrack
|
|
72
105
|
# g. New group
|
73
106
|
# q. Quit
|
74
107
|
|
75
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d g}) }).returns('q')
|
108
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g}) }).returns('q')
|
76
109
|
assert_equal :quit, @console.menu
|
77
110
|
end
|
78
111
|
|
@@ -80,7 +113,7 @@ module Keyrack
|
|
80
113
|
@console.database = @database
|
81
114
|
@database.stubs(:dirty?).returns(true)
|
82
115
|
|
83
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d g s}) }).returns('q')
|
116
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g s}) }).returns('q')
|
84
117
|
@highline.expects(:agree).with("Really quit? You have unsaved changes! [yn] ").returns(false)
|
85
118
|
assert_equal nil, @console.menu
|
86
119
|
end
|
@@ -89,8 +122,8 @@ module Keyrack
|
|
89
122
|
@console.database = @database
|
90
123
|
@database.stubs(:dirty?).returns(true)
|
91
124
|
|
92
|
-
@highline.expects(:say).with
|
93
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d g s}) }).returns('s')
|
125
|
+
@highline.expects(:say).with { |string| string =~ /\[s\]ave/ }
|
126
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g s}) }).returns('s')
|
94
127
|
assert_equal :save, @console.menu
|
95
128
|
end
|
96
129
|
|
@@ -100,7 +133,7 @@ module Keyrack
|
|
100
133
|
|
101
134
|
@highline.expects(:color).with('Blargh', :green).returns('greenBlargh')
|
102
135
|
@highline.expects(:say).with(" 1. greenBlargh")
|
103
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 2 d g}) }).returns('1')
|
136
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 4 d g}) }).returns('1')
|
104
137
|
assert_equal({:group => 'Blargh'}, @console.menu)
|
105
138
|
end
|
106
139
|
|
@@ -112,12 +145,10 @@ module Keyrack
|
|
112
145
|
@highline.expects(:color).with("Foo", :green).returns("greenFoo")
|
113
146
|
@highline.expects(:say).with("===== greenFoo =====")
|
114
147
|
@highline.expects(:say).with(" 1. Facebook [username]")
|
115
|
-
@highline.expects(:say).with("
|
116
|
-
@highline.expects(:say).with(" d
|
117
|
-
@highline.expects(:say).with(" t. Top level menu")
|
118
|
-
@highline.expects(:say).with(" q. Quit")
|
148
|
+
@highline.expects(:say).with("Mode: copy")
|
149
|
+
@highline.expects(:say).with("Commands: [n]ew [d]elete [t]op [m]ode [q]uit")
|
119
150
|
|
120
|
-
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q 1 d t}) }).returns('1')
|
151
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 d t}) }).returns('1')
|
121
152
|
@console.expects(:Copier).with('password')
|
122
153
|
@highline.expects(:say).with("The password has been copied to your clipboard.")
|
123
154
|
assert_nil @console.menu(:group => 'Foo')
|
@@ -137,7 +168,7 @@ module Keyrack
|
|
137
168
|
@highline.expects(:agree).with("Generate password? [yn] ").returns(false).in_sequence(seq)
|
138
169
|
@highline.expects(:ask).with("Password: ").yields(mock { expects(:echo=).with(false) }).returns("baz").in_sequence(seq)
|
139
170
|
@highline.expects(:ask).with("Password (again): ").yields(mock { expects(:echo=).with(false) }).returns("bar").in_sequence(seq)
|
140
|
-
@highline.expects(:say).with("Passwords didn't match.
|
171
|
+
@highline.expects(:say).with("Passwords didn't match. Try again!").in_sequence(seq)
|
141
172
|
@highline.expects(:ask).with("Password: ").yields(mock { expects(:echo=).with(false) }).returns("baz").in_sequence(seq)
|
142
173
|
@highline.expects(:ask).with("Password (again): ").yields(mock { expects(:echo=).with(false) }).returns("baz").in_sequence(seq)
|
143
174
|
assert_equal({:site => "Foo", :username => "bar", :password => "baz"}, @console.get_new_entry)
|
@@ -149,11 +180,11 @@ module Keyrack
|
|
149
180
|
@highline.expects(:ask).with("Username: ").returns("bar").in_sequence(seq)
|
150
181
|
@highline.expects(:agree).with("Generate password? [yn] ").returns(true).in_sequence(seq)
|
151
182
|
Utils.expects(:generate_password).returns('foobar').in_sequence(seq)
|
152
|
-
@highline.expects(:color).with('foobar', :
|
153
|
-
@highline.expects(:agree).with("Generated
|
183
|
+
@highline.expects(:color).with('foobar', :cyan).returns('cyanfoobar').in_sequence(seq)
|
184
|
+
@highline.expects(:agree).with("Generated cyanfoobar. Sound good? [yn] ").returns(false).in_sequence(seq)
|
154
185
|
Utils.expects(:generate_password).returns('foobar').in_sequence(seq)
|
155
|
-
@highline.expects(:color).with('foobar', :
|
156
|
-
@highline.expects(:agree).with("Generated
|
186
|
+
@highline.expects(:color).with('foobar', :cyan).returns('cyanfoobar').in_sequence(seq)
|
187
|
+
@highline.expects(:agree).with("Generated cyanfoobar. Sound good? [yn] ").returns(true).in_sequence(seq)
|
157
188
|
assert_equal({:site => "Foo", :username => "bar", :password => "foobar"}, @console.get_new_entry)
|
158
189
|
end
|
159
190
|
|
@@ -175,7 +206,7 @@ module Keyrack
|
|
175
206
|
|
176
207
|
def test_store_setup_for_filesystem
|
177
208
|
@highline.expects(:choose).yields(mock {
|
178
|
-
expects(:header=).with("Choose storage type
|
209
|
+
expects(:header=).with("Choose storage type")
|
179
210
|
expects(:choices).with("filesystem", "ssh")
|
180
211
|
}).returns("filesystem")
|
181
212
|
|
@@ -186,7 +217,7 @@ module Keyrack
|
|
186
217
|
def test_store_setup_for_ssh
|
187
218
|
seq = sequence("store setup")
|
188
219
|
@highline.expects(:choose).yields(mock {
|
189
|
-
expects(:header=).with("Choose storage type
|
220
|
+
expects(:header=).with("Choose storage type")
|
190
221
|
expects(:choices).with("filesystem", "ssh")
|
191
222
|
}).returns("ssh").in_sequence(seq)
|
192
223
|
@highline.expects(:ask).with("Host: ").returns("example.com").in_sequence(seq)
|
@@ -221,7 +252,25 @@ module Keyrack
|
|
221
252
|
}).returns('1').in_sequence(seq)
|
222
253
|
@highline.expects(:color).with("Twitter [username]", :red).returns("redTwitter").in_sequence(seq)
|
223
254
|
@highline.expects(:agree).with("You're about to delete redTwitter. Are you sure? [yn] ").returns(true).in_sequence(seq)
|
224
|
-
@database.expects(:delete).with("Twitter", {}).in_sequence(seq)
|
255
|
+
@database.expects(:delete).with("Twitter", 'username', {}).in_sequence(seq)
|
256
|
+
@console.delete_entry
|
257
|
+
end
|
258
|
+
|
259
|
+
def test_delete_one_entry_from_site_with_multiple_entries
|
260
|
+
@console.database = @database
|
261
|
+
|
262
|
+
seq = sequence("deleting")
|
263
|
+
@highline.expects(:say).with("Choose entry to delete:").in_sequence(seq)
|
264
|
+
@highline.expects(:say).with(" 1. Twitter [username]").in_sequence(seq)
|
265
|
+
@highline.expects(:say).with(" 2. Google [username_1]").in_sequence(seq)
|
266
|
+
@highline.expects(:say).with(" 3. Google [username_2]").in_sequence(seq)
|
267
|
+
@highline.expects(:say).with(" c. Cancel").in_sequence(seq)
|
268
|
+
@highline.expects(:ask).yields(mock {
|
269
|
+
expects(:in=).with(%w{c 1 2 3})
|
270
|
+
}).returns('3').in_sequence(seq)
|
271
|
+
@highline.expects(:color).with("Google [username_2]", :red).returns("redGoogle").in_sequence(seq)
|
272
|
+
@highline.expects(:agree).with("You're about to delete redGoogle. Are you sure? [yn] ").returns(true).in_sequence(seq)
|
273
|
+
@database.expects(:delete).with("Google", 'username_2', {}).in_sequence(seq)
|
225
274
|
@console.delete_entry
|
226
275
|
end
|
227
276
|
|
@@ -242,9 +291,18 @@ module Keyrack
|
|
242
291
|
}).returns('2').in_sequence(seq)
|
243
292
|
@highline.expects(:color).with("Foursquare [username]", :red).returns("redFoursquare").in_sequence(seq)
|
244
293
|
@highline.expects(:agree).with("You're about to delete redFoursquare. Are you sure? [yn] ").returns(true).in_sequence(seq)
|
245
|
-
@database.expects(:delete).with("Foursquare", :group => 'Social').in_sequence(seq)
|
294
|
+
@database.expects(:delete).with("Foursquare", 'username', :group => 'Social').in_sequence(seq)
|
246
295
|
@console.delete_entry(:group => 'Social')
|
247
296
|
end
|
297
|
+
|
298
|
+
def test_switch_mode_from_menu
|
299
|
+
@console.database = @database
|
300
|
+
@console.mode = :copy
|
301
|
+
|
302
|
+
@highline.expects(:ask).yields(mock { expects(:in=).with(%w{n q m 1 2 3 d g}) }).returns('m')
|
303
|
+
assert_nil @console.menu
|
304
|
+
assert_equal :print, @console.mode
|
305
|
+
end
|
248
306
|
end
|
249
307
|
end
|
250
308
|
end
|
metadata
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keyrack
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.3.0.pre
|
5
|
+
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Jeremy Stephens
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-01-
|
12
|
+
date: 2012-01-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: net-scp
|
16
|
-
requirement: &
|
16
|
+
requirement: &18759120 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *18759120
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: highline
|
27
|
-
requirement: &
|
27
|
+
requirement: &18757080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *18757080
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: copier
|
38
|
-
requirement: &
|
38
|
+
requirement: &18755860 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *18755860
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
|
-
requirement: &
|
49
|
+
requirement: &18755260 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: 1.0.0
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *18755260
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: jeweler
|
60
|
-
requirement: &
|
60
|
+
requirement: &18754600 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: 1.5.1
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *18754600
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rcov
|
71
|
-
requirement: &
|
71
|
+
requirement: &18754000 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,10 +76,10 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *18754000
|
80
80
|
- !ruby/object:Gem::Dependency
|
81
81
|
name: mocha
|
82
|
-
requirement: &
|
82
|
+
requirement: &18753200 !ruby/object:Gem::Requirement
|
83
83
|
none: false
|
84
84
|
requirements:
|
85
85
|
- - ! '>='
|
@@ -87,10 +87,10 @@ dependencies:
|
|
87
87
|
version: '0'
|
88
88
|
type: :development
|
89
89
|
prerelease: false
|
90
|
-
version_requirements: *
|
90
|
+
version_requirements: *18753200
|
91
91
|
- !ruby/object:Gem::Dependency
|
92
92
|
name: cucumber
|
93
|
-
requirement: &
|
93
|
+
requirement: &18752620 !ruby/object:Gem::Requirement
|
94
94
|
none: false
|
95
95
|
requirements:
|
96
96
|
- - ! '>='
|
@@ -98,7 +98,18 @@ dependencies:
|
|
98
98
|
version: '0'
|
99
99
|
type: :development
|
100
100
|
prerelease: false
|
101
|
-
version_requirements: *
|
101
|
+
version_requirements: *18752620
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: test-unit
|
104
|
+
requirement: &18751900 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
type: :development
|
111
|
+
prerelease: false
|
112
|
+
version_requirements: *18751900
|
102
113
|
description: Simple password manager with local/remote database storage and RSA encryption.
|
103
114
|
email: viking@pillageandplunder.net
|
104
115
|
executables:
|
@@ -159,16 +170,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
159
170
|
version: '0'
|
160
171
|
segments:
|
161
172
|
- 0
|
162
|
-
hash: -
|
173
|
+
hash: -4508723743310181824
|
163
174
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
164
175
|
none: false
|
165
176
|
requirements:
|
166
|
-
- - ! '
|
177
|
+
- - ! '>'
|
167
178
|
- !ruby/object:Gem::Version
|
168
|
-
version:
|
179
|
+
version: 1.3.1
|
169
180
|
requirements: []
|
170
181
|
rubyforge_project:
|
171
|
-
rubygems_version: 1.8.
|
182
|
+
rubygems_version: 1.8.11
|
172
183
|
signing_key:
|
173
184
|
specification_version: 3
|
174
185
|
summary: Simple password manager
|