purse 0.1.0
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/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
|