purse 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +15 -0
- data/PostInstall.txt +2 -0
- data/README.txt +82 -0
- data/Rakefile +4 -0
- data/bin/purse +6 -0
- data/lib/purse.rb +14 -0
- data/lib/purse/cli.rb +238 -0
- data/lib/purse/error.rb +10 -0
- data/lib/purse/help.txt +54 -0
- data/lib/purse/note.rb +62 -0
- data/lib/purse/pocket.rb +80 -0
- data/lib/purse/settings.rb +57 -0
- data/lib/purse/version.rb +9 -0
- data/test/purse/test_cli.rb +51 -0
- data/test/purse/test_note.rb +206 -0
- data/test/purse/test_pocket.rb +168 -0
- data/test/purse/test_settings.rb +141 -0
- data/test/test_helper.rb +49 -0
- metadata +90 -0
data/lib/purse/note.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module Purse
|
2
|
+
class Note
|
3
|
+
attr_reader :path, :name, :encrypted, :options
|
4
|
+
attr_accessor :data
|
5
|
+
|
6
|
+
def initialize(path, name, data, options = {})
|
7
|
+
Purse.check_for_parameter('path', path)
|
8
|
+
Purse.check_for_parameter('name', name)
|
9
|
+
@path = path
|
10
|
+
@name = name
|
11
|
+
@options = options
|
12
|
+
if options[:encrypted]
|
13
|
+
@encrypted = data
|
14
|
+
else
|
15
|
+
@data = data
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.load(path, name, options = {})
|
20
|
+
note = Note.new(path, name, nil)
|
21
|
+
raise MissingFile, "Tried to load #{note.file_path} but it does not exist" unless File.readable?(note.file_path)
|
22
|
+
encrypted_data = File.read(note.file_path)
|
23
|
+
Note.new(path, name, encrypted_data, options.merge(:encrypted => true))
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(password)
|
27
|
+
Purse.check_for_parameter('password', password)
|
28
|
+
encrypt(password)
|
29
|
+
File.open(file_path, 'w') {|f| f << @encrypted }
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def encrypt(password)
|
34
|
+
Purse.check_for_parameter('password', password)
|
35
|
+
blowfish = Crypt::Blowfish.new(password)
|
36
|
+
@encrypted = blowfish.encrypt_string(@data)
|
37
|
+
end
|
38
|
+
|
39
|
+
def decrypt(password)
|
40
|
+
Purse.check_for_parameter('password', password)
|
41
|
+
blowfish = Crypt::Blowfish.new(password)
|
42
|
+
@data = blowfish.decrypt_string(@encrypted)
|
43
|
+
end
|
44
|
+
|
45
|
+
def encrypted?
|
46
|
+
@encrypted && (!@data || @data.blank?)
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete
|
50
|
+
File.unlink(file_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def file_path
|
54
|
+
File.join(path, file_name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_name
|
58
|
+
"#{name}.note"
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
data/lib/purse/pocket.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Purse
|
2
|
+
class Pocket
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
Purse.check_for_parameter('path', path)
|
7
|
+
@path = File.expand_path(path)
|
8
|
+
end
|
9
|
+
|
10
|
+
def init
|
11
|
+
FileUtils.mkdir_p(@path) unless File.readable?(@path)
|
12
|
+
git
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete
|
16
|
+
FileUtils.rm_rf(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def find(name)
|
20
|
+
Purse.check_for_parameter('name', name)
|
21
|
+
note = notes.find {|note| note.name == name }
|
22
|
+
note ? note : raise(Purse::MissingFile, "Could note find the note named #{note}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def edit(name)
|
26
|
+
Purse.check_for_parameter('name', name)
|
27
|
+
begin
|
28
|
+
note = find(name)
|
29
|
+
rescue Purse::MissingFile
|
30
|
+
note = Note.new(path, name, nil)
|
31
|
+
end
|
32
|
+
yield(note)
|
33
|
+
@notes = load_notes
|
34
|
+
end
|
35
|
+
|
36
|
+
def notes
|
37
|
+
@notes ||= load_notes
|
38
|
+
end
|
39
|
+
|
40
|
+
def notes_paths
|
41
|
+
Dir[File.join(@path, '*.note')]
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_notes
|
45
|
+
notes_paths.collect {|note_path| Note.load(File.dirname(note_path), File.basename(note_path, '.note'))}
|
46
|
+
end
|
47
|
+
|
48
|
+
def re_encrypt(password)
|
49
|
+
Purse.check_for_parameter('password', password)
|
50
|
+
notes.collect {|n| n.save(password) }
|
51
|
+
end
|
52
|
+
|
53
|
+
def commit
|
54
|
+
git.add('.')
|
55
|
+
return if git.status.changed.empty?
|
56
|
+
git.commit_all("Changes via Purse #{Time.now}")
|
57
|
+
end
|
58
|
+
|
59
|
+
def remote
|
60
|
+
git.remotes.first
|
61
|
+
end
|
62
|
+
|
63
|
+
def set_remote(remote_url)
|
64
|
+
git.add_remote('origin', remote_url)
|
65
|
+
end
|
66
|
+
|
67
|
+
def push
|
68
|
+
git.push('origin')
|
69
|
+
end
|
70
|
+
|
71
|
+
def pull
|
72
|
+
git.pull('origin')
|
73
|
+
end
|
74
|
+
|
75
|
+
protected
|
76
|
+
def git
|
77
|
+
@git ||= Git.init(@path)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Purse
|
2
|
+
class Settings
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def default_path
|
6
|
+
File.join(File.expand_path('~'), '.purse', 'settings.yml')
|
7
|
+
end
|
8
|
+
|
9
|
+
def path
|
10
|
+
@path ||= default_path
|
11
|
+
end
|
12
|
+
|
13
|
+
def path=(new_path)
|
14
|
+
raise MissingParameter unless new_path.is_a?(String)
|
15
|
+
@path = new_path
|
16
|
+
@loaded = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def load
|
20
|
+
save unless File.readable?(path)
|
21
|
+
@settings = YAML.load_file(path)
|
22
|
+
@loaded = true
|
23
|
+
end
|
24
|
+
|
25
|
+
def loaded?
|
26
|
+
@loaded ||= false
|
27
|
+
end
|
28
|
+
|
29
|
+
def save
|
30
|
+
FileUtils.mkdir_p(File.dirname(path))
|
31
|
+
File.open(path, 'w') {|f| f << YAML::dump(settings) }
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(key)
|
35
|
+
load unless loaded?
|
36
|
+
settings[key.to_s]
|
37
|
+
end
|
38
|
+
|
39
|
+
def set(key, value, should_save = true)
|
40
|
+
settings[key.to_s] = value
|
41
|
+
save if should_save
|
42
|
+
end
|
43
|
+
|
44
|
+
def settings
|
45
|
+
@settings ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def settings=(new_settings)
|
49
|
+
return unless new_settings.is_a?(Hash)
|
50
|
+
new_settings.each do |k, v|
|
51
|
+
set(k, v, false)
|
52
|
+
end
|
53
|
+
save
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
class TestCli < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Purse" do
|
6
|
+
context "Cli" do
|
7
|
+
context "run" do
|
8
|
+
setup do
|
9
|
+
HighLine.any_instance.stubs(:say)
|
10
|
+
@pocketname = 'test_purse_data'
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# purse pursename #=> initialize or pull
|
15
|
+
context "purse pursename" do
|
16
|
+
should "call list" do
|
17
|
+
Cli.expects(:list).with(@pocketname).once
|
18
|
+
Cli.run([@pocketname])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# purse pursename notename
|
23
|
+
context "purse pursename notename" do
|
24
|
+
|
25
|
+
context "if the note exists" do
|
26
|
+
should "call init then call find and then display" do
|
27
|
+
notename = 'test123'
|
28
|
+
Cli.expects(:find).with(@pocketname,notename).once
|
29
|
+
Cli.run([@pocketname, notename])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# purse pursename notename --edit #=> open in EDITOR save and push
|
35
|
+
context "purse pursename notename --edit" do
|
36
|
+
should "call init edit" do
|
37
|
+
notename = 'jagger'
|
38
|
+
Cli.expects(:edit).with(@pocketname,notename).once
|
39
|
+
Cli.run([@pocketname, notename, '--edit'])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# purse pursename push
|
44
|
+
# purse pursename pull
|
45
|
+
# purse settings/ purse # rerun settings
|
46
|
+
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,206 @@
|
|
1
|
+
require 'test_helper.rb'
|
2
|
+
|
3
|
+
class TestNote < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context "Purse" do
|
6
|
+
context "Note" do
|
7
|
+
|
8
|
+
context "an instance" do
|
9
|
+
setup do
|
10
|
+
@path = purse_path
|
11
|
+
@note = Note.new(@path, 'sander', 'Nonsense', :method => :blowfish, :store_as => :yaml)
|
12
|
+
end
|
13
|
+
|
14
|
+
context "new" do
|
15
|
+
|
16
|
+
should "require path" do
|
17
|
+
assert_raise(MissingParameter) do
|
18
|
+
Note.new(nil,'sander','Sander nonsense')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
should "require name" do
|
23
|
+
assert_raise(MissingParameter) do
|
24
|
+
Note.new(purse_path, '', 'Sander nonsense')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "initializing with regular data" do
|
29
|
+
should "set @path" do
|
30
|
+
assert_equal @path, @note.path
|
31
|
+
end
|
32
|
+
|
33
|
+
should "set @name" do
|
34
|
+
assert_equal 'sander', @note.name
|
35
|
+
end
|
36
|
+
|
37
|
+
should "set @data" do
|
38
|
+
assert_equal 'Nonsense', @note.data
|
39
|
+
end
|
40
|
+
|
41
|
+
should "save extra args as options" do
|
42
|
+
assert @note.options.is_a?(Hash)
|
43
|
+
assert_equal :blowfish, @note.options[:method]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context "initializing with encrypted data" do
|
48
|
+
setup do
|
49
|
+
@note = Note.new(purse_path, 'sander', 'ENCRYPTED DATA', :encrypted => true)
|
50
|
+
end
|
51
|
+
|
52
|
+
should "set @path" do
|
53
|
+
assert_equal purse_path, @note.path
|
54
|
+
end
|
55
|
+
|
56
|
+
should "set @name" do
|
57
|
+
assert_equal 'sander', @note.name
|
58
|
+
end
|
59
|
+
|
60
|
+
should "not set @data" do
|
61
|
+
assert_nil @note.data
|
62
|
+
end
|
63
|
+
|
64
|
+
should "set @encrypted" do
|
65
|
+
assert_equal 'ENCRYPTED DATA', @note.encrypted
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "file_name" do
|
71
|
+
should "return #name.note" do
|
72
|
+
assert_equal @note.name + '.note', @note.file_name
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "file_path" do
|
77
|
+
should "return path/name.note" do
|
78
|
+
assert_equal File.join(@note.path, @note.name + '.note'), @note.file_path
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context "save" do
|
83
|
+
|
84
|
+
should "require password" do
|
85
|
+
assert_raise(MissingParameter) do
|
86
|
+
@note.save(nil)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with password" do
|
91
|
+
setup do
|
92
|
+
@password = '12345'
|
93
|
+
Crypt::Blowfish.any_instance.expects(:encrypt_string).with(@note.data).returns('ENCRYPTED DATA')
|
94
|
+
@note.save(@password)
|
95
|
+
end
|
96
|
+
|
97
|
+
should "encrypt data and place in @encrypted" do
|
98
|
+
assert_equal 'ENCRYPTED DATA', @note.encrypted
|
99
|
+
end
|
100
|
+
|
101
|
+
should "write to file in path with #name" do
|
102
|
+
assert File.readable?(@note.file_path)
|
103
|
+
assert_equal @note.encrypted, File.read(@note.file_path)
|
104
|
+
end
|
105
|
+
|
106
|
+
# teardown do
|
107
|
+
# File.unlink(purse_path, 'sander')
|
108
|
+
# end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context "encrypt" do
|
113
|
+
# blowfish = Crypt::Blowfish.new("A key up to 56 bytes long")
|
114
|
+
# plainBlock = "ABCD1234"
|
115
|
+
# encryptedBlock = blowfish.encrypt_block(plainBlock)
|
116
|
+
# decryptedBlock = blowfish.decrypt_block(encryptedBlock)
|
117
|
+
|
118
|
+
should "require password" do
|
119
|
+
assert_raise(MissingParameter) do
|
120
|
+
@note.encrypt(nil)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context "with password" do
|
125
|
+
setup do
|
126
|
+
@password = 'Test123'
|
127
|
+
Crypt::Blowfish.any_instance.expects(:encrypt_string).with(@note.data).returns('ENCRYPTED DATA')
|
128
|
+
@note.encrypt(@password)
|
129
|
+
end
|
130
|
+
|
131
|
+
should "save encrypted data to encrypted" do
|
132
|
+
assert_equal 'ENCRYPTED DATA', @note.encrypted
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context "decrypt" do
|
138
|
+
setup do
|
139
|
+
@note = Note.new(purse_path, 'sander', 'ENCRYPTED DATA', :encrypted => true)
|
140
|
+
end
|
141
|
+
|
142
|
+
should "require password" do
|
143
|
+
assert_raise(MissingParameter) do
|
144
|
+
@note.decrypt(nil)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "with password" do
|
149
|
+
setup do
|
150
|
+
@password = 'Test123'
|
151
|
+
Crypt::Blowfish.any_instance.expects(:decrypt_string).with(@note.encrypted).returns('Nonsense')
|
152
|
+
@note.decrypt(@password)
|
153
|
+
end
|
154
|
+
|
155
|
+
should "save decrypted data to @data" do
|
156
|
+
assert_equal 'Nonsense', @note.data
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context "on the class" do
|
163
|
+
context "load" do
|
164
|
+
|
165
|
+
should "require path" do
|
166
|
+
assert_raise(MissingParameter) do
|
167
|
+
Note.load(nil, 'sander')
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
should "name" do
|
172
|
+
assert_raise(MissingParameter) do
|
173
|
+
Note.load(nil, 'sander')
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
should "raise error if file can not be found" do
|
178
|
+
assert_raise(MissingFile) do
|
179
|
+
Note.load(purse_path, "file_#{rand(5)}")
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
context "with a saved encrypted file" do
|
184
|
+
setup do
|
185
|
+
# Crypt::Blowfish.any_instance.expects(:decrypt_block).with('ENCRYPTED DATA').returns('Nonsense')
|
186
|
+
@note = Note.load(purse_path, 'sander')
|
187
|
+
end
|
188
|
+
|
189
|
+
should "return Note" do
|
190
|
+
assert @note.is_a?(Note)
|
191
|
+
end
|
192
|
+
|
193
|
+
should "set @encrypted" do
|
194
|
+
assert_equal 'ENCRYPTED DATA', @note.encrypted
|
195
|
+
end
|
196
|
+
|
197
|
+
should "not set @data" do
|
198
|
+
assert_nil @note.data
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|