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/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
|
+
|