keyrack 0.2.3 → 0.3.0.pre
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/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
|