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/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 Quirkey NYC, LLC
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
PostInstall.txt
|
5
|
+
README.txt
|
6
|
+
Rakefile
|
7
|
+
bin/purse
|
8
|
+
lib/purse.rb
|
9
|
+
lib/purse/cli.rb
|
10
|
+
lib/purse/error.rb
|
11
|
+
lib/purse/note.rb
|
12
|
+
lib/purse/pocket.rb
|
13
|
+
lib/purse/settings.rb
|
14
|
+
lib/purse/version.rb
|
15
|
+
lib/purse/help.txt
|
data/PostInstall.txt
ADDED
data/README.txt
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
= purse
|
2
|
+
|
3
|
+
http://quirkey.rubyforge.org/purse
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
A simple way of storing private/sensitive info and then sharing it with others using git
|
8
|
+
|
9
|
+
== SYNOPSIS:
|
10
|
+
|
11
|
+
Purse stores information in a two level folder system. The basic piece of stored information is a 'note'.
|
12
|
+
A 'pocket' is a folder containing one to many notes. A *pocket* can be synced between mutlple users/computers
|
13
|
+
using a remote git repository.
|
14
|
+
|
15
|
+
To configure for the first time:
|
16
|
+
|
17
|
+
purse
|
18
|
+
|
19
|
+
and follow the handy on screen instructions.
|
20
|
+
|
21
|
+
To create a new pocket/note.
|
22
|
+
|
23
|
+
purse pocketname notename
|
24
|
+
|
25
|
+
It will prompt you asking if you want to create the note.
|
26
|
+
Once you've edited in your favorite editor and saved, purse
|
27
|
+
will use Blowfish to encrypt the note with the password you provide.
|
28
|
+
|
29
|
+
Then you can display the note with:
|
30
|
+
|
31
|
+
purse pocketname notename
|
32
|
+
|
33
|
+
or edit with:
|
34
|
+
|
35
|
+
purse pocketname notename --edit
|
36
|
+
|
37
|
+
Each time it will ask you for the password you initially provided to encrypt it.
|
38
|
+
|
39
|
+
For more commands/info run:
|
40
|
+
|
41
|
+
purse --help
|
42
|
+
|
43
|
+
== REQUIREMENTS:
|
44
|
+
|
45
|
+
purse requires a couple of gems, namely
|
46
|
+
highline -- for command line tools
|
47
|
+
termios
|
48
|
+
crypt -- for encryption
|
49
|
+
git -- for uh, git
|
50
|
+
|
51
|
+
== INSTALL:
|
52
|
+
|
53
|
+
sudo gem install purse
|
54
|
+
|
55
|
+
then run
|
56
|
+
|
57
|
+
purse
|
58
|
+
|
59
|
+
== LICENSE:
|
60
|
+
|
61
|
+
(The MIT License)
|
62
|
+
|
63
|
+
Copyright (c) 2008 Quirkey NYC, LLC
|
64
|
+
|
65
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
66
|
+
a copy of this software and associated documentation files (the
|
67
|
+
'Software'), to deal in the Software without restriction, including
|
68
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
69
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
70
|
+
permit persons to whom the Software is furnished to do so, subject to
|
71
|
+
the following conditions:
|
72
|
+
|
73
|
+
The above copyright notice and this permission notice shall be
|
74
|
+
included in all copies or substantial portions of the Software.
|
75
|
+
|
76
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
77
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
78
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
79
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
80
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
81
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
82
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/bin/purse
ADDED
data/lib/purse.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__)) unless
|
2
|
+
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'git'
|
6
|
+
require 'crypt/blowfish'
|
7
|
+
require 'highline'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'purse/error'
|
11
|
+
require 'purse/settings'
|
12
|
+
require 'purse/pocket'
|
13
|
+
require 'purse/note'
|
14
|
+
require 'purse/cli'
|
data/lib/purse/cli.rb
ADDED
@@ -0,0 +1,238 @@
|
|
1
|
+
module Purse
|
2
|
+
class Cli
|
3
|
+
|
4
|
+
ACTION_PREFIX = /^--/
|
5
|
+
|
6
|
+
# purse pursename #=> initialize or pull
|
7
|
+
# purse pursename notename #=> pocket.find(notename) if it doesnt exist first pull then ask if you want to create it
|
8
|
+
# purse pursename notename --edit #=> open in EDITOR save and push
|
9
|
+
# purse pursename push
|
10
|
+
# purse pursename pull
|
11
|
+
# purse settings/ purse # rerun settings
|
12
|
+
class << self
|
13
|
+
def run(args)
|
14
|
+
begin
|
15
|
+
banner
|
16
|
+
settings_exist?
|
17
|
+
action = args.detect {|a| a =~ ACTION_PREFIX }
|
18
|
+
args.reject! {|a| a == action}
|
19
|
+
pocket_name, note_name = args[0], args[1]
|
20
|
+
if !action.nil? && action != ''
|
21
|
+
action = action.gsub(ACTION_PREFIX,'')
|
22
|
+
send(action, *([pocket_name, note_name].compact))
|
23
|
+
else
|
24
|
+
case note_name
|
25
|
+
when 'push'
|
26
|
+
push(pocket_name)
|
27
|
+
when 'pull'
|
28
|
+
pull(pocket_name)
|
29
|
+
when ''
|
30
|
+
when nil
|
31
|
+
list(pocket_name)
|
32
|
+
else
|
33
|
+
find(pocket_name, note_name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
rescue MissingFile
|
37
|
+
say("Could not find note: #{pocket_name}/#{note_name}")
|
38
|
+
rescue NoMethodError => e
|
39
|
+
say("Sorry, there is no action #{action}.\nTry purse --help for a list of commands.")
|
40
|
+
end
|
41
|
+
hr
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
def settings
|
46
|
+
h1('Setup utility')
|
47
|
+
hr
|
48
|
+
say("loading settings from : #{Settings.path}")
|
49
|
+
Settings.load
|
50
|
+
settings = {}
|
51
|
+
settings[:root_path] = cli.ask("Root Path for pockets? ") {|q| q.default = Settings.get(:root_path) || File.join(File.expand_path('~'), '.purse', 'pockets') }
|
52
|
+
settings[:editor] = cli.ask("Editor for notes? ") {|q| q.default = Settings.get(:editor) || 'vim' }
|
53
|
+
hr
|
54
|
+
say("saving settings to : #{Settings.path}")
|
55
|
+
Settings.settings = settings
|
56
|
+
end
|
57
|
+
|
58
|
+
def init(pocket_name)
|
59
|
+
pocket = Pocket.new(pocket_path(pocket_name))
|
60
|
+
pocket.init
|
61
|
+
pocket
|
62
|
+
end
|
63
|
+
|
64
|
+
def find(pocket_name, note_name)
|
65
|
+
say("loading note #{pocket_name}/#{note_name}")
|
66
|
+
hr
|
67
|
+
pocket = init(pocket_name)
|
68
|
+
note = pocket.find(note_name)
|
69
|
+
display(note)
|
70
|
+
rescue MissingFile
|
71
|
+
say("could not find note: #{pocket_name}/#{note_name}")
|
72
|
+
if cli.agree("would you like to create it? (y/n)")
|
73
|
+
edit(pocket_name, note_name)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def display(note)
|
78
|
+
decrypt(note)
|
79
|
+
h1("Note: #{note.name}",:green)
|
80
|
+
hr
|
81
|
+
say(note.data)
|
82
|
+
hr
|
83
|
+
end
|
84
|
+
|
85
|
+
def save(note, password = nil)
|
86
|
+
password ||= cli.ask("Password: ") { |q| q.echo = false }
|
87
|
+
note.save(password)
|
88
|
+
end
|
89
|
+
|
90
|
+
def decrypt(note)
|
91
|
+
return unless note.encrypted?
|
92
|
+
password = cli.ask("Password: ") { |q| q.echo = false }
|
93
|
+
note.decrypt(password)
|
94
|
+
password
|
95
|
+
end
|
96
|
+
|
97
|
+
def edit(pocket_name, note_name)
|
98
|
+
say("loading note #{pocket_name}/#{note_name}")
|
99
|
+
hr
|
100
|
+
pocket = init(pocket_name)
|
101
|
+
pocket.edit(note_name) do |note|
|
102
|
+
password = decrypt(note)
|
103
|
+
tmp_path = empty_temp_path
|
104
|
+
File.open(tmp_path, 'w') {|f| f << note.data }
|
105
|
+
execute("#{editor} #{tmp_path}")
|
106
|
+
sleep(2)
|
107
|
+
data = ""
|
108
|
+
File.open(tmp_path, 'r') {|f| data << f.read }
|
109
|
+
note.data = data
|
110
|
+
hr
|
111
|
+
say("saving note #{pocket_name}/#{note_name}")
|
112
|
+
save(note, password)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete(pocket_name, note_name = nil)
|
117
|
+
pocket = init(pocket_name)
|
118
|
+
if note_name
|
119
|
+
note = pocket.find(note_name)
|
120
|
+
note.delete if cli.agree("are you sure you want to delete #{pocket_name}/#{note_name}? (y/n)")
|
121
|
+
else
|
122
|
+
pocket.delete if cli.agree("are you sure you want to delete /#{pocket_name}? (y/n)")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def push(pocket_name)
|
127
|
+
pocket = init(pocket_name)
|
128
|
+
say("Committing local changes for #{pocket_name}")
|
129
|
+
pocket.commit
|
130
|
+
hr
|
131
|
+
set_remote(pocket_name)
|
132
|
+
say("Pulling changes from remote (#{pocket.remote})")
|
133
|
+
pocket.pull
|
134
|
+
hr
|
135
|
+
sary("Pushing changes to remote (#{pocket.remote})")
|
136
|
+
pocket.push
|
137
|
+
end
|
138
|
+
|
139
|
+
def pull(pocket_name)
|
140
|
+
pocket = init(pocket_name)
|
141
|
+
say("Committing local changes for #{pocket_name}")
|
142
|
+
pocket.commit
|
143
|
+
hr
|
144
|
+
set_remote(pocket_name)
|
145
|
+
say("Pulling changes from remote (#{pocket.remote})")
|
146
|
+
pocket.pull
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_remote(pocket_name)
|
150
|
+
pocket = init(pocket_name)
|
151
|
+
unless pocket.remote
|
152
|
+
remote_url = cli.ask("please enter your remote git url (git@github ...)")
|
153
|
+
pocket.set_remote(remote_url)
|
154
|
+
say("set remote to #{remote_url}")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def help(*args)
|
159
|
+
h1("Help", :yellow)
|
160
|
+
hr
|
161
|
+
help_text = File.open(File.join(File.dirname(__FILE__),'help.txt')) {|f| f.read }
|
162
|
+
say(help_text)
|
163
|
+
end
|
164
|
+
|
165
|
+
def list(pocket_name = nil, note_name = nil)
|
166
|
+
unless pocket_name
|
167
|
+
h1("Listing your Pockets", :green)
|
168
|
+
hr
|
169
|
+
pockets = Dir[File.join(Settings.get(:root_path), '*')].find_all {|file| File.directory?(file) }
|
170
|
+
if pockets.empty?
|
171
|
+
say("no pockets yet. create one with 'purse pocketname'")
|
172
|
+
else
|
173
|
+
pockets.each {|p| say("/#{File.basename(p)}/") }
|
174
|
+
end
|
175
|
+
else
|
176
|
+
pocket = init(pocket_name)
|
177
|
+
say("Notes in <%= color '(#{pocket_name})', :red %>")
|
178
|
+
hr
|
179
|
+
pocket.notes.each do |note|
|
180
|
+
say("- #{note.name}")
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def settings_exist?
|
186
|
+
return if Settings.get(:root_path) && Settings.get(:editor)
|
187
|
+
h1("you must configure purse before you use it", :red)
|
188
|
+
settings
|
189
|
+
say("rerun this utility any time with purse --settings")
|
190
|
+
exit
|
191
|
+
end
|
192
|
+
|
193
|
+
def pocket_path(pocket_name)
|
194
|
+
File.join(Settings.get(:root_path), pocket_name)
|
195
|
+
end
|
196
|
+
|
197
|
+
def editor
|
198
|
+
Settings.get(:editor)
|
199
|
+
end
|
200
|
+
|
201
|
+
def banner
|
202
|
+
hr
|
203
|
+
h1('/ Purse /', :white)
|
204
|
+
hr
|
205
|
+
end
|
206
|
+
|
207
|
+
def h1(text, color = :red)
|
208
|
+
say("<%= color('#{text}', :#{color}) %>")
|
209
|
+
end
|
210
|
+
|
211
|
+
def hr(color = :magenta)
|
212
|
+
say("<%= color('-' * 40, :#{color}) %>")
|
213
|
+
end
|
214
|
+
|
215
|
+
def say(message)
|
216
|
+
cli.say(message)
|
217
|
+
end
|
218
|
+
|
219
|
+
def cli
|
220
|
+
@cli ||= HighLine.new
|
221
|
+
end
|
222
|
+
|
223
|
+
def execute(command)
|
224
|
+
`#{command}`
|
225
|
+
end
|
226
|
+
|
227
|
+
def random_temp_name
|
228
|
+
"#{rand Time.now.to_i}-#{rand(1000)}--"
|
229
|
+
end
|
230
|
+
|
231
|
+
def empty_temp_path
|
232
|
+
temp_dir = File.join(Settings.get(:root_path), 'tmp')
|
233
|
+
FileUtils.mkdir_p(temp_dir)
|
234
|
+
File.join(temp_dir, random_temp_name)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
data/lib/purse/error.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Purse
|
2
|
+
class Error < RuntimeError; end
|
3
|
+
|
4
|
+
class MissingParameter < Error; end;
|
5
|
+
class MissingFile < Error; end
|
6
|
+
|
7
|
+
def self.check_for_parameter(name, param)
|
8
|
+
raise MissingParameter, "You must set a #{name}" if param.nil? || param.strip == ''
|
9
|
+
end
|
10
|
+
end
|
data/lib/purse/help.txt
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
-- General --
|
2
|
+
|
3
|
+
Purse stores information in a two level folder system.
|
4
|
+
The basic piece of stored information is a 'note'.
|
5
|
+
A 'pocket' is a folder containing one to many notes.
|
6
|
+
A *pocket* can be synced between mutlple users/computers using a remote git repository.
|
7
|
+
|
8
|
+
-- Commands --
|
9
|
+
|
10
|
+
purse
|
11
|
+
purse --list
|
12
|
+
|
13
|
+
List Pockets
|
14
|
+
|
15
|
+
purse --help
|
16
|
+
|
17
|
+
Display this message
|
18
|
+
|
19
|
+
purse --settings
|
20
|
+
|
21
|
+
Run the settings utility
|
22
|
+
|
23
|
+
purse pocketname
|
24
|
+
|
25
|
+
List the notes in pocket 'pocketname'
|
26
|
+
|
27
|
+
purse pocketname --delete
|
28
|
+
|
29
|
+
Destroy the pocket: pocketname
|
30
|
+
|
31
|
+
purse pocketname --pull
|
32
|
+
|
33
|
+
Pull the pocket 'pocketname' from a remote git repository.
|
34
|
+
Will prompt for URL the first time its run.
|
35
|
+
|
36
|
+
purse pocketname --push
|
37
|
+
|
38
|
+
Push the pocket 'pocketname' to a remote git repository.
|
39
|
+
Will prompt for URL the first time its run.
|
40
|
+
|
41
|
+
purse pocketname notename
|
42
|
+
|
43
|
+
Display or create the note 'notename' in the pocket 'pocketname'.
|
44
|
+
Will prompt for password.
|
45
|
+
|
46
|
+
purse pocketname notename --edit
|
47
|
+
|
48
|
+
Edit or create the note 'notename' in the pocket 'pocketname'.
|
49
|
+
Will prompt for password.
|
50
|
+
|
51
|
+
purse pocketname notename --delete
|
52
|
+
|
53
|
+
Delete the note 'notename' in the pocket 'pocketname'.
|
54
|
+
|