keyp 0.0.6 → 0.0.7
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.
- checksums.yaml +4 -4
- data/README.md +8 -6
- data/bin/keyp +91 -20
- data/lib/keyp.rb +44 -19
- data/lib/keyp/bag.rb +168 -71
- data/lib/keyp/version.rb +1 -1
- data/spec/bag_spec.rb +144 -18
- data/spec/keyp_spec.rb +90 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2951607e2465cc3204c7f01d993802e75be59acb
|
4
|
+
data.tar.gz: efaa651f76f6d65eefde6316111bd62640790c43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d69defc81b8351eeeb6171738687dd2577c910e145ec67808ff56c72a6052c399d8be9bb399a38b857912d41d7de4d9574b50ea876206157ee383e8c122a8174
|
7
|
+
data.tar.gz: c4d11396115a4ebe2b0a1ed49f24cc2e1e72dbbada753aa0796d1fdaa301d5bfccd822f5d43168578b8dfca0f52249bf4d94e8d44b7e33442d7153f33c35e3f4
|
data/README.md
CHANGED
@@ -12,12 +12,14 @@ Web applications generally need sensitive information, like salts and keys. Mana
|
|
12
12
|
|
13
13
|
## Important
|
14
14
|
|
15
|
-
Keyp is still in early development
|
15
|
+
Keyp is still in early development. As such it is very much a work in progress. Not all features
|
16
16
|
expressed in the documentation may be working. The documentation may be plain wrong. I don't recommend you use Keyp
|
17
17
|
for any kind of production code at this point in this gem's lifecycle.
|
18
18
|
|
19
19
|
## Quick Tour
|
20
20
|
|
21
|
+
To install, see "Installation" below
|
22
|
+
|
21
23
|
Keyp manages key value pairs in collections. As of version 0.0.4, Keyp refers to collections as *bags*. There is a
|
22
24
|
default bag called *default*. Unless you specify a bag name, *default* will be used.
|
23
25
|
|
@@ -51,7 +53,8 @@ Keyp is not limited to just access keys. Any kind of string based name/value pai
|
|
51
53
|
|
52
54
|
TODO: Improve description
|
53
55
|
|
54
|
-
_CAVEAT:_ This gem is at
|
56
|
+
_CAVEAT:_ This gem is at an early stage in development. Functionality is
|
57
|
+
subject to change depending on need and clarity.
|
55
58
|
|
56
59
|
## Installation
|
57
60
|
|
@@ -93,15 +96,14 @@ TODO: Write more detailed documentation, For now, see the quick start above and
|
|
93
96
|
$ keyp --help
|
94
97
|
|
95
98
|
## Release Notes
|
99
|
+
* v 0.0.7 - Added loading a bag from the ENV. Added renaming bags Fixed error with keys of less than three characters.
|
100
|
+
* v 0.0.6 - Added copying key:values to the Ruby ENV in the library (still have to implement in the CLI)
|
96
101
|
* v 0.0.5 - Fixed iso8601 error
|
97
102
|
* v 0.0.4 - Fixed missing file error
|
98
103
|
|
99
104
|
## Development plan/Features to implement
|
100
105
|
|
101
|
-
*
|
102
|
-
* Implement .keyp directory setup
|
103
|
-
* Add ENV vars to bag
|
104
|
-
* Add bag vars to ENV
|
106
|
+
* Fill out rspec tests
|
105
107
|
* Incorporate with shell configuration
|
106
108
|
* Write very clear documentation
|
107
109
|
* Implement import/Export to YAML and/or JSON
|
data/bin/keyp
CHANGED
@@ -6,16 +6,18 @@
|
|
6
6
|
# implementations. A good number and a few well known use Thor, so seems like a good idea to
|
7
7
|
# figure out how to use Thor as well.
|
8
8
|
#
|
9
|
-
|
10
|
-
# Then I'll pick a single approach after exploring them
|
9
|
+
|
11
10
|
|
12
11
|
# TO run in the project dir:
|
13
12
|
# ruby -Ilib ./bin/keyp
|
13
|
+
# or
|
14
|
+
# bundle exec bin/keyp
|
14
15
|
|
15
16
|
# resolve bin path, ignoring symlinks
|
16
17
|
require 'pathname'
|
17
18
|
bin_file = Pathname.new(__FILE__).realpath
|
18
19
|
|
20
|
+
# The Keyp CLI uses GLI
|
19
21
|
# http://davetron5000.github.io/gli/
|
20
22
|
require 'gli'
|
21
23
|
|
@@ -24,13 +26,6 @@ require 'pp'
|
|
24
26
|
require 'keyp'
|
25
27
|
include GLI::App
|
26
28
|
|
27
|
-
# keyp set key=value
|
28
|
-
# keyp get key
|
29
|
-
# keyp
|
30
|
-
|
31
|
-
|
32
|
-
# keyp create bag
|
33
|
-
|
34
29
|
program_desc 'A command line interface for the Keyp key:value manager'
|
35
30
|
|
36
31
|
desc 'The Keyp bag to use'
|
@@ -42,6 +37,11 @@ switch [:d, :debug]
|
|
42
37
|
desc 'Suppress hints: If invalid parameters are set, no suggestive feedback will be provided'
|
43
38
|
switch [:s, :suppress]
|
44
39
|
|
40
|
+
|
41
|
+
|
42
|
+
version Keyp::VERSION
|
43
|
+
|
44
|
+
|
45
45
|
# TODO: fix this because this implies that we either already have a bag or it will create a new one
|
46
46
|
pre do |global_options, command, options, args|
|
47
47
|
# initialize our store
|
@@ -57,8 +57,7 @@ pre do |global_options, command, options, args|
|
|
57
57
|
puts "---- end prehook ---"
|
58
58
|
end
|
59
59
|
|
60
|
-
unless command.name
|
61
|
-
|
60
|
+
unless [:setup, :show, :delete].include? command.name
|
62
61
|
# check if we are configured
|
63
62
|
unless Keyp.configured?
|
64
63
|
puts "Please run setup to use Keyp. Run with --help to view help menu"
|
@@ -125,7 +124,7 @@ command :set do |c|
|
|
125
124
|
nvp = Keyp.parse_arg_string buff
|
126
125
|
|
127
126
|
if nvp
|
128
|
-
$bag[nvp[:key]] = nvp[:value]
|
127
|
+
$bag[nvp[:key]] = nvp[:value].strip
|
129
128
|
elsif global_options[:suppress] == false
|
130
129
|
# puts "Usage keyp set KEY1=VALUE1 [KEY2=VALUE2 ...]"
|
131
130
|
puts "Usage: keyp set KEY=VALUE"
|
@@ -190,8 +189,11 @@ command :setup do |c|
|
|
190
189
|
end
|
191
190
|
end
|
192
191
|
|
193
|
-
|
192
|
+
create_desc = 'Creates a new bag'
|
194
193
|
|
194
|
+
desc create_desc
|
195
|
+
command :create do |c|
|
196
|
+
c.desc create_desc
|
195
197
|
c.action do |global_options, options, args|
|
196
198
|
puts "keyp create"
|
197
199
|
# TODO: add checking in case store already exists
|
@@ -206,14 +208,56 @@ command :create do |c|
|
|
206
208
|
end
|
207
209
|
end
|
208
210
|
|
211
|
+
delete_desc = 'Deletes an existing bag or set of bags matching the delete parameter'
|
212
|
+
desc delete_desc
|
209
213
|
command :delete do |c|
|
214
|
+
c.desc 'forces delete (no prompting)'
|
215
|
+
c.switch [:f, :force]
|
216
|
+
c.desc "outputs the name of each file as it's deleted"
|
217
|
+
c.switch [:v, :verbose]
|
218
|
+
c.desc "Confirm deletion of each file. Currently not enabled"
|
219
|
+
c.switch [:c, :confirm]
|
220
|
+
c.desc delete_desc
|
210
221
|
c.action do |global_options, options, args|
|
211
|
-
|
212
|
-
# TODO:
|
222
|
+
# TODO: add * for all bags
|
223
|
+
# TODO: enable prompting per file. Confirm skips the global confirm
|
224
|
+
|
225
|
+
help_now!('filename pattern is required') if args.empty?
|
226
|
+
|
227
|
+
if options[:force]
|
228
|
+
do_delete = true
|
229
|
+
else
|
230
|
+
puts "This will delete all the bags matching \"#{args[0]}\"."
|
231
|
+
print "Are you sure? (yes to confirm. Anything else to cancel) "
|
232
|
+
do_delete = STDIN.gets.chomp.upcase == 'YES'
|
233
|
+
end
|
234
|
+
|
235
|
+
if do_delete
|
236
|
+
# experimenting with different techniques
|
237
|
+
delete_type = 1
|
238
|
+
case delete_type
|
239
|
+
when 1
|
240
|
+
files = Dir["#{File.join(Keyp.home,args[0]+Keyp.ext)}"]
|
241
|
+
puts "going to delete files #{files}" if $debug
|
242
|
+
files.each do |f|
|
243
|
+
File.delete(f)
|
244
|
+
puts "deleted #{File.basename(f, Keyp.ext)}" if options[:verbose]
|
245
|
+
end
|
246
|
+
when 2
|
247
|
+
result = File.delete(args[0])
|
248
|
+
puts "delete results: #{result}"
|
249
|
+
#TODO: provide feedback
|
250
|
+
end
|
251
|
+
else
|
252
|
+
puts "delete bag(s) canceled."
|
253
|
+
end
|
213
254
|
end
|
214
255
|
end
|
215
256
|
|
257
|
+
show_desc = 'Lists all the bags in Keyp'
|
258
|
+
desc show_desc
|
216
259
|
command :show do |c|
|
260
|
+
c.desc show_desc
|
217
261
|
c.action do |global_options, options, args|
|
218
262
|
# TODO: implement filtering, stats (like number of items in a bag, last updated, etc)
|
219
263
|
# and just filename
|
@@ -224,13 +268,40 @@ command :show do |c|
|
|
224
268
|
end
|
225
269
|
end
|
226
270
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
271
|
+
rename_desc = 'Rename the given bag'
|
272
|
+
desc rename_desc
|
273
|
+
command :rename do |c|
|
274
|
+
#c.desc 'from bag name)'
|
275
|
+
#c.flag [:f, :from]
|
276
|
+
#c.desc 'to bag name'
|
277
|
+
#c.flag [:t, :to]
|
278
|
+
c.desc rename_desc
|
279
|
+
c.action do |global_options, options, args|
|
280
|
+
# get from_name and to_name
|
281
|
+
if args.count < 2
|
282
|
+
puts "TODO: rename help"
|
283
|
+
else
|
284
|
+
begin
|
285
|
+
res = Keyp::rename_bag(from: args[0], to: args[1])
|
286
|
+
puts "Renamed bag #{args[0]} to #{args[1]}"
|
287
|
+
rescue
|
288
|
+
puts "Unable to rename bag #{args[0]} to #{args[1]}"
|
289
|
+
# TODO: add reason
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
231
293
|
end
|
294
|
+
end
|
295
|
+
|
232
296
|
|
233
|
-
|
297
|
+
post do |global_options, command, options, args|
|
298
|
+
|
299
|
+
unless [:setup, :show, :delete].include? command.name
|
300
|
+
if $debug
|
301
|
+
puts "post-hook, state of bag.dirty = #{$bag.dirty}"
|
302
|
+
end
|
303
|
+
# if bag.data is dirty, then save
|
304
|
+
# TODO: check bag.save. if it checks dirty, then we don't need to check here
|
234
305
|
$bag.save
|
235
306
|
else
|
236
307
|
true
|
data/lib/keyp.rb
CHANGED
@@ -43,6 +43,8 @@ module Keyp
|
|
43
43
|
# Default home directory that keyp uses to store bags
|
44
44
|
DEFAULT_KEYP_DIR = '.keyp'
|
45
45
|
|
46
|
+
# number of digits for the fractional seconds in decimal
|
47
|
+
TIMESTAMP_FS_DIGITS = 6
|
46
48
|
#TODO: set this
|
47
49
|
#ENV_VAR_NAME_REGEX =
|
48
50
|
|
@@ -64,7 +66,16 @@ module Keyp
|
|
64
66
|
# it is recommended you restrict permissions
|
65
67
|
#
|
66
68
|
def self.home
|
67
|
-
|
69
|
+
#puts "Keyp::home, KEYP_HOME=#{ENV['KEYP_HOME']}"
|
70
|
+
ENV['KEYP_HOME'] || File.join(ENV['HOME'], DEFAULT_KEYP_DIR)
|
71
|
+
=begin
|
72
|
+
puts "home_dir=#{home_dir}"
|
73
|
+
if ENV['KEYP_HOME']
|
74
|
+
ENV['KEYP_HOME']
|
75
|
+
else
|
76
|
+
File.join(ENV['HOME'], DEFAULT_KEYP_DIR)
|
77
|
+
end
|
78
|
+
=end
|
68
79
|
end
|
69
80
|
|
70
81
|
##
|
@@ -94,15 +105,13 @@ module Keyp
|
|
94
105
|
# check if keyp directory exists
|
95
106
|
|
96
107
|
unless Dir.exist?(home)
|
97
|
-
puts "making directory #{home}"
|
98
108
|
Dir.mkdir(home, 0700)
|
99
109
|
if Dir.exist?(home)
|
100
110
|
home
|
101
111
|
else
|
102
|
-
|
112
|
+
raise "unable to create Keyp directory #{home}"
|
103
113
|
end
|
104
114
|
else
|
105
|
-
Puts "#{home} already exists"
|
106
115
|
nil
|
107
116
|
end
|
108
117
|
end
|
@@ -159,25 +168,14 @@ module Keyp
|
|
159
168
|
##
|
160
169
|
# Creates a new bag if one does not already exist with the given name
|
161
170
|
#
|
171
|
+
# raises exception if bag already exists
|
172
|
+
# NOTE: We might want to change this
|
162
173
|
def self.create_bag(name, options = {} )
|
163
|
-
time_now = Time.now.utc.iso8601
|
164
|
-
file_data = {}
|
165
|
-
file_data['meta'] = {
|
166
|
-
'name' => name,
|
167
|
-
'description' => '',
|
168
|
-
'created_at' => time_now,
|
169
|
-
'updated_at' => time_now
|
170
|
-
}
|
171
|
-
file_data['data'] = nil
|
172
174
|
unless exist? name
|
173
|
-
|
174
|
-
f.write file_data.to_yaml
|
175
|
-
f.chmod(0600)
|
176
|
-
end
|
175
|
+
Bag.new(name)
|
177
176
|
else
|
178
|
-
raise "Unable to create a new
|
177
|
+
raise "Unable to create a new bag \"#{name}\". It already exists."
|
179
178
|
end
|
180
|
-
bag name
|
181
179
|
end
|
182
180
|
|
183
181
|
##
|
@@ -193,6 +191,33 @@ module Keyp
|
|
193
191
|
end
|
194
192
|
end
|
195
193
|
|
194
|
+
##
|
195
|
+
#
|
196
|
+
# == options
|
197
|
+
# +:from+ current name of the bag (rename from)
|
198
|
+
# +:to+ new name of the bag (rename to)
|
199
|
+
#
|
200
|
+
def self.rename_bag(options = {})
|
201
|
+
|
202
|
+
#TODO: Implement this
|
203
|
+
# validate_options(:from,:to)
|
204
|
+
|
205
|
+
from_name = options[:from]
|
206
|
+
to_name = options[:to]
|
207
|
+
|
208
|
+
unless from_name || to_name
|
209
|
+
raise "required parameters are missing. Requires both :from and :to"
|
210
|
+
end
|
211
|
+
|
212
|
+
# if current bag does not exist, we raise and exception
|
213
|
+
unless exist?(from_name)
|
214
|
+
raise ("cannot rename #{from_name} because it does not exist")
|
215
|
+
end
|
216
|
+
bag = bag(from_name)
|
217
|
+
bag.rename(to_name)
|
218
|
+
#bag(from_name).rename(to_name)
|
219
|
+
end
|
220
|
+
|
196
221
|
def self.parse_arg_string(arg_string, options={})
|
197
222
|
|
198
223
|
mode = options[:token_mode] || :default
|
data/lib/keyp/bag.rb
CHANGED
@@ -4,21 +4,27 @@ module Keyp
|
|
4
4
|
# Some inspiration:
|
5
5
|
# http://stackoverflow.com/questions/2680523/dry-ruby-initialization-with-hash-argument
|
6
6
|
#
|
7
|
-
# TODO: add handling so that keys (hierarchical keys too) are accessed as members instead of
|
8
|
-
# hash access
|
7
|
+
# TODO: add handling so that keys (hierarchical keys too) are accessed as members instead of hash access
|
9
8
|
#
|
10
|
-
# TODO: move to own file, rename to "Bag"
|
11
9
|
# TODO: i18n error messages
|
10
|
+
# TODO: handle symbol to string and back for keys. Need instance setting to handle validating keys as symbols
|
12
11
|
#
|
13
12
|
class Bag
|
14
13
|
|
15
|
-
|
16
|
-
|
14
|
+
# TODO: Make name attr_reader
|
15
|
+
# TODO: See if we need data as an attr_accessor If not then it makes
|
16
|
+
# it easier to encapsulate handling of string/symbol keys
|
17
|
+
attr_reader :keypdir, :dirty, :meta
|
18
|
+
attr_accessor :data, :file_hash
|
19
|
+
|
20
|
+
def name
|
21
|
+
@meta['name']
|
22
|
+
end
|
17
23
|
|
18
24
|
##
|
19
25
|
# Returns the full path of this Bag's file
|
20
26
|
def keypfile
|
21
|
-
File.join(@keypdir,
|
27
|
+
File.join(@keypdir, name+@ext)
|
22
28
|
end
|
23
29
|
|
24
30
|
##
|
@@ -28,39 +34,44 @@ module Keyp
|
|
28
34
|
# +keypdir+
|
29
35
|
# +read_only+
|
30
36
|
# +ext+
|
31
|
-
def initialize(
|
37
|
+
def initialize(bag_name, options = {})
|
32
38
|
# I'm not happy with how creating instance variables works. There must be a cleaner way
|
33
|
-
@
|
39
|
+
@meta = { 'name' => bag_name}
|
34
40
|
options.each do |k,v|
|
35
|
-
puts "processing options #{k} = #{v}"
|
41
|
+
#JB set debug log: puts "processing options #{k} = #{v}"
|
36
42
|
instance_variable_set("@#{k}", v) unless v.nil?
|
37
43
|
end
|
38
|
-
# set attributes not set by params
|
39
44
|
|
45
|
+
# set attributes not set by params
|
40
46
|
@keypdir ||= Keyp::home
|
41
47
|
@read_only ||= false
|
42
48
|
@ext ||= Keyp::DEFAULT_EXT
|
43
|
-
#@keypfile = config_path
|
44
|
-
# load our resource
|
45
|
-
|
46
|
-
# load config file into hashes
|
47
|
-
# not the most efficient thing, but simpler and safe enough for now
|
48
49
|
|
49
50
|
unless File.exist? keypfile
|
50
|
-
puts "Bag.initialize, create_bag #{keypfile}"
|
51
|
-
|
51
|
+
#JB set debug log: puts "Bag.initialize, create_bag #{keypfile}"
|
52
|
+
#TODO: move this to its own method
|
53
|
+
time_now = Time.now.utc.iso8601(TIMESTAMP_FS_DIGITS)
|
54
|
+
file_data = {}
|
55
|
+
file_data['meta'] = {
|
56
|
+
'name' => bag_name,
|
57
|
+
'description' => '',
|
58
|
+
'created_at' => time_now,
|
59
|
+
'updated_at' => time_now
|
60
|
+
}
|
61
|
+
file_data['data'] = nil
|
62
|
+
File.open(Keyp::bag_path(bag_name), 'w') do |f|
|
63
|
+
f.write file_data.to_yaml
|
64
|
+
f.chmod(0600)
|
65
|
+
end
|
52
66
|
end
|
53
|
-
|
54
|
-
|
55
|
-
@meta = file_data[:meta]
|
56
|
-
@data = file_data[:data]|| {}
|
57
|
-
@file_hash = file_data[:file_hash]
|
67
|
+
load_file
|
58
68
|
@dirty = false
|
59
69
|
end
|
60
70
|
|
61
71
|
##
|
62
72
|
#
|
63
73
|
def [](key)
|
74
|
+
# TODO: convert from any symbols to string
|
64
75
|
@data[key]
|
65
76
|
end
|
66
77
|
|
@@ -74,6 +85,7 @@ module Keyp
|
|
74
85
|
# Sets a key:value pair
|
75
86
|
# NOTE: This may be made protected
|
76
87
|
def set_prop(key, value)
|
88
|
+
# TODO
|
77
89
|
unless @read_only
|
78
90
|
# TODO: check if data has been modified
|
79
91
|
# maybe there is a way hash tells us its been modified. If not then
|
@@ -83,22 +95,19 @@ module Keyp
|
|
83
95
|
@dirty = true
|
84
96
|
end
|
85
97
|
else
|
86
|
-
raise "Bag #{
|
98
|
+
raise "Bag #{name} is read only"
|
87
99
|
end
|
88
100
|
end
|
89
101
|
|
90
102
|
##
|
91
103
|
# Deletes the key:value pair from this bag for the given key
|
92
104
|
def delete(key)
|
93
|
-
|
94
|
-
|
95
|
-
@dirty = true
|
96
|
-
end
|
97
|
-
val = @data.delete(key)
|
98
|
-
else
|
99
|
-
raise "Bag #{@name} is read only"
|
105
|
+
if @read_only
|
106
|
+
raise "Cannot delete key \"#{key}\". Bag \"#{name}\" is read only."
|
100
107
|
end
|
101
|
-
|
108
|
+
|
109
|
+
@dirty = true if @data.key? key
|
110
|
+
@data.delete(key)
|
102
111
|
end
|
103
112
|
|
104
113
|
##
|
@@ -108,6 +117,14 @@ module Keyp
|
|
108
117
|
end
|
109
118
|
|
110
119
|
|
120
|
+
def read_only?
|
121
|
+
@read_only
|
122
|
+
end
|
123
|
+
|
124
|
+
def key?(key)
|
125
|
+
@data.key?(key)
|
126
|
+
end
|
127
|
+
|
111
128
|
##
|
112
129
|
# Adds key/value pairs from this bag to the Ruby ENV
|
113
130
|
# NOTE: Currently in development.
|
@@ -136,7 +153,7 @@ module Keyp
|
|
136
153
|
overwrite = options[:overwrite] || false
|
137
154
|
pattern = options[:sysvar] if options.key?(:sysvar)
|
138
155
|
|
139
|
-
pattern ||= /(
|
156
|
+
pattern ||= /(..*)/
|
140
157
|
@data.each do |key,value|
|
141
158
|
if pattern.match(key)
|
142
159
|
# TODO: add overwrite checking
|
@@ -147,9 +164,71 @@ module Keyp
|
|
147
164
|
assigned
|
148
165
|
end
|
149
166
|
|
167
|
+
##
|
168
|
+
# load environment variables into this bag
|
169
|
+
# === Options
|
170
|
+
# +:pattern+ Applies keys matching this pattern
|
171
|
+
# +:overwrite+ true to overwrite existing keys, false (default) to not overwrite existing keys
|
172
|
+
#
|
173
|
+
#
|
174
|
+
def load_from_env(options = {})
|
175
|
+
assigned = {}
|
176
|
+
overwrite = options[:overwrite] || false
|
177
|
+
pattern = options[:pattern] || /(..*)/
|
178
|
+
ENV.each do | key, value|
|
179
|
+
if pattern.match(key)
|
180
|
+
unless overwrite && @data.key?(key)
|
181
|
+
@data[key] = value
|
182
|
+
assigned[key] = value
|
183
|
+
end
|
184
|
+
else
|
185
|
+
puts "load_from_env. No match for #{key}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
assigned
|
189
|
+
end
|
150
190
|
|
151
191
|
# TODO add from hash
|
152
192
|
|
193
|
+
##
|
194
|
+
# renames the bag to +new_name+
|
195
|
+
# returns new bag name if succesful, raises exception if not
|
196
|
+
#
|
197
|
+
def rename(new_name, options = {})
|
198
|
+
|
199
|
+
# Since we are not changing any key pairs, the only meta to be changed is +name+
|
200
|
+
#
|
201
|
+
# TODO: Check for valid key name
|
202
|
+
#TODO: change to try to create a new file and lock it. If we can, then we fill it
|
203
|
+
if Keyp::exist?(new_name)
|
204
|
+
raise "Bag rename error. Target \"#{new_name}\" already exists."
|
205
|
+
end
|
206
|
+
|
207
|
+
if @dirty
|
208
|
+
# We don't want to allow renaming a bag with modified values because it will complicate rollback
|
209
|
+
#TODO: improve error message
|
210
|
+
raise "Can't rename a modified bag. Save first"
|
211
|
+
end
|
212
|
+
|
213
|
+
# TODO: treat the following as a transaction so if the bag file can't be renamed then
|
214
|
+
# the old file is restored unmodified
|
215
|
+
#curr_name = @meta['name']
|
216
|
+
curr_name = name
|
217
|
+
old_file = Keyp::bag_path(curr_name)
|
218
|
+
to_file = Keyp::bag_path(new_name)
|
219
|
+
#TODO: Add file locking for concurrency protection
|
220
|
+
@meta['name'] = new_name
|
221
|
+
@meta['last_name'] =curr_name
|
222
|
+
@renaming = true
|
223
|
+
# saves with the new name
|
224
|
+
save
|
225
|
+
@renaming = nil
|
226
|
+
# now remove the old file
|
227
|
+
File.delete(old_file)
|
228
|
+
|
229
|
+
load_file
|
230
|
+
@meta['name']
|
231
|
+
end
|
153
232
|
|
154
233
|
def import(filename)
|
155
234
|
raise "import not yet supported"
|
@@ -165,39 +244,12 @@ module Keyp
|
|
165
244
|
# TODO: def to_s
|
166
245
|
|
167
246
|
##
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
# Get extension
|
175
|
-
file_ext = File.extname(config_path)
|
176
|
-
|
177
|
-
# check
|
178
|
-
# only YAML supported for initial version. Will consider adapters to
|
179
|
-
# abstract the persistence layer
|
180
|
-
|
181
|
-
|
182
|
-
# TODO: make this hardcoded case a hash of helpers
|
183
|
-
# TODO: Add two sections: Meta and data, then return as hash
|
184
|
-
|
185
|
-
if file_ext.downcase == Keyp::DEFAULT_EXT
|
186
|
-
|
187
|
-
# Either we are arbitrarily creating directories when
|
188
|
-
# given a path for a file that doesn't exist
|
189
|
-
# or we have special behavior for the default dir
|
190
|
-
# or we just fault and let the caller deal with it
|
191
|
-
unless File.exist? config_path
|
192
|
-
raise "Keyp config file not found: #{config_path}"
|
193
|
-
end
|
194
|
-
|
195
|
-
file_data = YAML.load_file(config_path)
|
196
|
-
|
197
|
-
else
|
198
|
-
raise "Keyp version x only supports YAML for config files. You tried a #{file_ext}"
|
199
|
-
end
|
200
|
-
{ meta: file_data['meta'], data: file_data['data']||{}, file_hash: file_data.hash }
|
247
|
+
#
|
248
|
+
def load_file
|
249
|
+
file_data = Bag::load_file(keypfile)
|
250
|
+
@meta = file_data[:meta]
|
251
|
+
@data = file_data[:data]|| {}
|
252
|
+
@file_hash = file_data[:file_hash]
|
201
253
|
end
|
202
254
|
|
203
255
|
##
|
@@ -209,22 +261,25 @@ module Keyp
|
|
209
261
|
if @read_only
|
210
262
|
raise "This bag instance is read only"
|
211
263
|
end
|
212
|
-
if @dirty
|
264
|
+
if @dirty || @renaming
|
213
265
|
# lock file
|
214
266
|
# read checksum
|
215
267
|
# if checksum matches our saved checksum then update file and release lock
|
216
268
|
# otherwise, raise
|
217
269
|
# TODO: implement merge from updated file and raise only if conflict
|
218
270
|
begin
|
219
|
-
file_data = { 'meta' => @meta, 'data' => @data }
|
220
|
-
|
221
271
|
if File.exist? keypfile
|
222
|
-
read_file_data =
|
272
|
+
read_file_data = Bag::load_file(keypfile)
|
223
273
|
unless @file_hash == read_file_data[:file_hash]
|
224
274
|
raise "Will not write to #{keypfile}\nHashes differ. Expected hash =#{@file_hash}\n" +
|
225
275
|
"found hash #{read_file_data[:file_hash]}"
|
226
276
|
end
|
227
277
|
end
|
278
|
+
if @dirty && !@renaming
|
279
|
+
@meta['updated_at'] = Time.now.utc.iso8601(Keyp::TIMESTAMP_FS_DIGITS)
|
280
|
+
end
|
281
|
+
|
282
|
+
file_data = { 'meta' => @meta, 'data' => @data }
|
228
283
|
File.open(keypfile, 'w') do |f|
|
229
284
|
f.write file_data.to_yaml
|
230
285
|
end
|
@@ -233,7 +288,49 @@ module Keyp
|
|
233
288
|
# TODO: log error
|
234
289
|
|
235
290
|
end
|
291
|
+
else
|
292
|
+
puts "save called but not saving (not dirty or renaming flag set"
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
|
297
|
+
# Class methods
|
298
|
+
##
|
299
|
+
# Give full path, attempt to load
|
300
|
+
# sticking with YAML format for now
|
301
|
+
# May add new file format later in which case we'll
|
302
|
+
# TODO: Make this a class method and an instance method
|
303
|
+
# that calls the class method and does the instance assignment
|
304
|
+
def self.load_file (config_path)
|
305
|
+
# Get extension
|
306
|
+
file_ext = File.extname(config_path)
|
307
|
+
|
308
|
+
# check
|
309
|
+
# only YAML supported for initial version. Will consider adapters to
|
310
|
+
# abstract the persistence layer
|
311
|
+
|
312
|
+
|
313
|
+
# TODO: make this hardcoded case a hash of helpers
|
314
|
+
# TODO: Add two sections: Meta and data, then return as hash
|
315
|
+
|
316
|
+
if file_ext.downcase == Keyp::DEFAULT_EXT
|
317
|
+
|
318
|
+
# Either we are arbitrarily creating directories when
|
319
|
+
# given a path for a file that doesn't exist
|
320
|
+
# or we have special behavior for the default dir
|
321
|
+
# or we just fault and let the caller deal with it
|
322
|
+
unless File.exist? config_path
|
323
|
+
raise "Keyp config file not found: #{config_path}"
|
324
|
+
end
|
325
|
+
|
326
|
+
file_data = YAML.load_file(config_path)
|
327
|
+
|
328
|
+
else
|
329
|
+
raise "Keyp version x only supports YAML for config files. You tried a #{file_ext}"
|
236
330
|
end
|
331
|
+
{ meta: file_data['meta'], data: file_data['data']||{}, file_hash: file_data.hash }
|
237
332
|
end
|
238
|
-
|
239
|
-
|
333
|
+
|
334
|
+
|
335
|
+
end # class Bag
|
336
|
+
end # module Keyp
|
data/lib/keyp/version.rb
CHANGED
data/spec/bag_spec.rb
CHANGED
@@ -2,44 +2,53 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Keyp::Bag do
|
4
4
|
|
5
|
+
def bag_name_for_testing
|
6
|
+
# TODO: pseudo random bag name generation to a helper
|
7
|
+
"grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
8
|
+
end
|
9
|
+
|
5
10
|
context "is empty" do
|
6
11
|
before (:each) do
|
7
|
-
|
8
|
-
@bag = Keyp::Bag.new
|
12
|
+
@bag_name = bag_name_for_testing
|
13
|
+
@bag = Keyp::Bag.new @bag_name
|
9
14
|
end
|
10
15
|
|
11
16
|
it "should return an empty hash" do
|
12
|
-
|
13
17
|
@bag.data.size.should == 0
|
14
18
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
19
|
+
after (:each) do
|
20
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
20
24
|
context "is not empty" do
|
21
25
|
before (:each) do
|
22
|
-
|
23
|
-
@bag = Keyp::Bag.new
|
26
|
+
@bag_name = bag_name_for_testing
|
27
|
+
@bag = Keyp::Bag.new @bag_name
|
24
28
|
@bag['LIGHTS'] = 'out'
|
25
29
|
end
|
26
30
|
|
27
31
|
it "should return a non-empty hash" do
|
28
32
|
@bag.data.size.should > 0
|
29
33
|
end
|
34
|
+
after (:each) do
|
35
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
36
|
+
end
|
30
37
|
end
|
31
38
|
|
32
39
|
context "environment variables" do
|
33
40
|
before (:each) do
|
34
|
-
|
35
|
-
|
36
|
-
@bag = Keyp::Bag.new bag_name
|
41
|
+
@bag_name = bag_name_for_testing
|
42
|
+
@bag = Keyp::Bag.new @bag_name
|
37
43
|
end
|
38
44
|
it "should copy all vars" do
|
39
45
|
testvars = {
|
40
46
|
'ALPHA' => 'First in the phonetic alphabet',
|
41
47
|
'BRAVO' => 'Second in the phonetic alphabet',
|
42
|
-
'CHARLIE' => 'Third in the phonetic alphabet'
|
48
|
+
'CHARLIE' => 'Third in the phonetic alphabet',
|
49
|
+
'_' => 'a single underscore',
|
50
|
+
'a' => 'first letter in the alphabet',
|
51
|
+
'bb' => 'two of the second letter in the alphabet'
|
43
52
|
}
|
44
53
|
|
45
54
|
testvars.each { |key, value| @bag[key] = value }
|
@@ -50,14 +59,39 @@ describe Keyp::Bag do
|
|
50
59
|
ENV[key].should == value
|
51
60
|
end
|
52
61
|
end
|
62
|
+
|
63
|
+
it 'should receive all vars' do
|
64
|
+
test_keyvals = {
|
65
|
+
'foo' => 'bar',
|
66
|
+
'biz' => 'baz',
|
67
|
+
'GRUE' => 'Eats you in the dark',
|
68
|
+
'a' => 'first letter in the alphabet',
|
69
|
+
'bb' => 'two of the second letter in the alphabet'
|
70
|
+
}
|
71
|
+
|
72
|
+
test_keyvals.each do |key,value|
|
73
|
+
ENV[key] = value
|
74
|
+
end
|
75
|
+
@bag.load_from_env
|
76
|
+
ENV.each do |key,value|
|
77
|
+
#puts "testing key=#{key}"
|
78
|
+
@bag.key?(key).should == true
|
79
|
+
@bag[key].should == value
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should not overwrite existing keys'
|
84
|
+
it 'should match filtering pattern'
|
85
|
+
after (:each) do
|
86
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
87
|
+
end
|
53
88
|
end
|
54
89
|
|
55
|
-
context
|
90
|
+
context 'Bag state' do
|
56
91
|
|
57
92
|
before (:each) do
|
58
|
-
|
59
|
-
|
60
|
-
@bag = Keyp::Bag.new bag_name
|
93
|
+
@bag_name = bag_name_for_testing
|
94
|
+
@bag = Keyp::Bag.new @bag_name
|
61
95
|
end
|
62
96
|
|
63
97
|
it 'should not set the dirty flag when no items are in the bag' do
|
@@ -92,10 +126,102 @@ describe Keyp::Bag do
|
|
92
126
|
@bag['KEY1'] = 'value1'
|
93
127
|
@bag.dirty.should == false
|
94
128
|
end
|
129
|
+
|
130
|
+
it 'should allow assigning a key if not read only'
|
131
|
+
it 'should not allow assigning a key if read only'
|
132
|
+
after (:each) do
|
133
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'Metadata' do
|
138
|
+
|
139
|
+
before (:each) do
|
140
|
+
@bag_name = bag_name_for_testing
|
141
|
+
@bag = Keyp::Bag.new @bag_name
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should have metadata" do
|
145
|
+
# @bag.meta['created_on'].should_not == nil
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'should not update created_at after bag is initially created' do
|
149
|
+
created_at = @bag.meta['created_at']
|
150
|
+
@bag['foo'] = 'bar'
|
151
|
+
@bag.save
|
152
|
+
@bag.meta['created_at'].should == created_at
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'should update updated_at when bag is saved' do
|
156
|
+
updated_at = @bag.meta['updated_at']
|
157
|
+
@bag['foo'] = 'bar'
|
158
|
+
@bag.save
|
159
|
+
@bag.meta['updated_at'].should > updated_at
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'should not update updated_at if bag has not been saved' do
|
163
|
+
updated_at = @bag.meta['updated_at']
|
164
|
+
@bag['foo'] = 'bar'
|
165
|
+
@bag.meta['updated_at'].should == updated_at
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'should use metadata for the bag name' do
|
169
|
+
def @bag.set_name(name)
|
170
|
+
@meta['name'] = name
|
171
|
+
end
|
172
|
+
test_name = 'norwegian_blue'
|
173
|
+
@bag.set_name(test_name)
|
174
|
+
@bag.name.should == test_name
|
175
|
+
end
|
176
|
+
after (:each) do
|
177
|
+
Keyp.delete_bag @bag_name if Keyp.exist? @bag_name
|
178
|
+
end
|
95
179
|
end
|
96
180
|
|
181
|
+
context 'Bag management' do
|
182
|
+
before (:each) do
|
183
|
+
@from_name = bag_name_for_testing
|
184
|
+
@from_bag = Keyp::Bag.new @from_name
|
185
|
+
sleep(1)
|
186
|
+
@to_name = bag_name_for_testing
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should rename if new name does not exist' do
|
190
|
+
bag = @from_bag
|
191
|
+
kp = {
|
192
|
+
'key1' => 'value1',
|
193
|
+
'key2' => 'value2'
|
194
|
+
}
|
195
|
+
|
196
|
+
# seed a couple of kv pairs just to keep it a bit real
|
197
|
+
# but we should also test with an empty bag
|
198
|
+
kp.each {|k,v| bag[k] = v }
|
199
|
+
bag.save
|
200
|
+
before_meta = {}
|
201
|
+
bag.meta.each { |k,v| before_meta[k] = v }
|
202
|
+
bag.name.should == @from_name
|
203
|
+
@to_name.should_not == @from_name
|
204
|
+
# we know the to_name does not exist
|
205
|
+
result = bag.rename(@to_name)
|
206
|
+
bag.name.should == @to_name
|
207
|
+
bag.meta['name'].should == @to_name
|
208
|
+
bag.meta['created_at'].should == before_meta['created_at']
|
209
|
+
# Since we are not changing any key pairs, updated_at doesn't change
|
210
|
+
bag.meta['updated_at'].should == before_meta['updated_at']
|
211
|
+
bag.meta['name'].should_not == before_meta['name']
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should not rename if new name exists' do
|
215
|
+
|
216
|
+
end
|
217
|
+
after (:each) do
|
218
|
+
Keyp.delete_bag @from_name if Keyp.exist? @from_name
|
219
|
+
Keyp.delete_bag @to_name if Keyp.exist? @to_name
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
|
97
224
|
it 'should return a key with data member'
|
98
225
|
it 'should return a key acting as a hash'
|
99
|
-
|
100
|
-
it 'should not allow assigning a key if read only'
|
226
|
+
|
101
227
|
end
|
data/spec/keyp_spec.rb
CHANGED
@@ -1,18 +1,25 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'fileutils'
|
2
4
|
|
3
5
|
describe Keyp do
|
4
6
|
|
5
|
-
|
7
|
+
def bag_name_for_testing
|
8
|
+
# TODO: pseudo random bag name generation to a helper
|
9
|
+
"bring_a_towel_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'CONSTANTS' do
|
6
13
|
it 'should return correct version string' do
|
7
14
|
#Keyp.version_string.should == "Keyp version #{Keyp::VERSION}"
|
8
|
-
Keyp::VERSION.should == '0.0.
|
15
|
+
Keyp::VERSION.should == '0.0.7'
|
9
16
|
end
|
10
17
|
|
11
18
|
it 'should specify default store' do
|
12
19
|
Keyp::DEFAULT_BAG.should == 'default'
|
13
20
|
end
|
14
21
|
|
15
|
-
it
|
22
|
+
it 'should specifify default store extension' do
|
16
23
|
Keyp::DEFAULT_EXT.should == '.yml'
|
17
24
|
end
|
18
25
|
|
@@ -21,16 +28,41 @@ describe Keyp do
|
|
21
28
|
end
|
22
29
|
end
|
23
30
|
|
24
|
-
context
|
31
|
+
context 'Default Keyp directory' do
|
25
32
|
it 'should return correct default keyp dir' do
|
26
33
|
# DEFAULT_KEYP_DIRNAME = ENV['KEYP_DIRNAME'] || '.keyp'
|
27
34
|
Keyp::home.should == File.join(ENV['HOME'], '.keyp')
|
28
35
|
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'Alternate Keyp directory' do
|
39
|
+
|
40
|
+
|
41
|
+
before (:each) do
|
42
|
+
@root_tempdir = Dir.mktmpdir
|
43
|
+
@temp_keyp_dir = File.join(@root_tempdir,Keyp::DEFAULT_KEYP_DIR)
|
44
|
+
ENV['KEYP_HOME'] = @temp_keyp_dir
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should be able to override default keyp dir' do
|
48
|
+
puts "ENV['KEYP_HOME']=#{ENV['KEYP_HOME']}"
|
49
|
+
keyp_dir = Keyp::setup
|
50
|
+
keyp_dir.should == Keyp::home
|
51
|
+
keyp_dir.should == @temp_keyp_dir
|
52
|
+
Keyp::home.should == ENV['KEYP_HOME']
|
53
|
+
|
54
|
+
end
|
29
55
|
|
30
|
-
|
56
|
+
#TODO: Keyp::setup should return nil when trying to setup to an existing dir
|
57
|
+
# Check if we can describe a method
|
58
|
+
after (:each) do
|
59
|
+
puts "AFTER called, going to try to delete #{@tempdir}"
|
60
|
+
FileUtils.rm_rf @root_tempdir
|
61
|
+
ENV['KEYP_HOME'] = nil
|
62
|
+
end
|
31
63
|
end
|
32
64
|
|
33
|
-
context
|
65
|
+
context 'Bag management' do
|
34
66
|
it 'should return default bag' do
|
35
67
|
keyper = Keyp::bag
|
36
68
|
keyper.name.should == 'default'
|
@@ -42,15 +74,65 @@ describe Keyp do
|
|
42
74
|
Keyp.exist?(keyper.name).should == true
|
43
75
|
end
|
44
76
|
|
45
|
-
it
|
77
|
+
it 'should say a bag does not exist' do
|
46
78
|
# TODO: add bag name generation to a helper
|
47
79
|
bag_name = "grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
|
48
80
|
Keyp.exist?(bag_name).should_not == true
|
49
81
|
end
|
50
|
-
|
51
82
|
end
|
52
83
|
|
84
|
+
context 'Renaming bags' do
|
85
|
+
# NOTE: Yeah, this code and the bag_spec code are redundant
|
86
|
+
before (:each) do
|
87
|
+
@from_name = bag_name_for_testing
|
88
|
+
if Keyp::exist?(@from_name)
|
89
|
+
puts "Rename bag, bag #{@from_name} already exists"
|
90
|
+
else
|
91
|
+
puts "Rename bag, bag #{@from_name} does NOT exist"
|
92
|
+
end
|
93
|
+
@from_bag = Keyp.create_bag(@from_name)
|
94
|
+
sleep(1)
|
95
|
+
@to_name = bag_name_for_testing
|
96
|
+
end
|
53
97
|
|
98
|
+
it 'should rename bag if it exists and new name does not' do
|
99
|
+
bag = @from_bag
|
100
|
+
kp = {
|
101
|
+
'key1' => 'value1',
|
102
|
+
'key2' => 'value2'
|
103
|
+
}
|
104
|
+
|
105
|
+
# seed a couple of kv pairs just to keep it a bit real
|
106
|
+
# but we should also test with an empty bag
|
107
|
+
kp.each {|k,v| bag[k] = v }
|
108
|
+
bag.save
|
109
|
+
before_meta = {}
|
110
|
+
bag.meta.each { |k,v| before_meta[k] = v }
|
111
|
+
bag.name.should == @from_name
|
112
|
+
@to_name.should_not == @from_name
|
113
|
+
result = Keyp.rename_bag(from: @from_name, to: @to_name)
|
114
|
+
Keyp.exist?(@from_name).should == false
|
115
|
+
Keyp.exist?(@to_name).should == true
|
116
|
+
bag = Keyp.bag @to_name
|
117
|
+
bag.name.should == @to_name
|
118
|
+
bag.meta['name'].should == @to_name
|
119
|
+
bag.meta['created_at'].should == before_meta['created_at']
|
120
|
+
# Since we are not changing any key pairs, updated_at doesn't change
|
121
|
+
bag.meta['updated_at'].should == before_meta['updated_at']
|
122
|
+
kp.each do |k,v|
|
123
|
+
bag.key?(k).should == true
|
124
|
+
bag[k].should == v
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'should not rename bag if current name does not exist'
|
129
|
+
it 'should not rename bag if new name exists'
|
130
|
+
|
131
|
+
after (:each) do
|
132
|
+
Keyp.delete_bag @from_name if Keyp.exist? @from_name
|
133
|
+
Keyp.delete_bag @to_name if Keyp.exist? @to_name
|
134
|
+
end
|
135
|
+
end
|
54
136
|
|
55
137
|
|
56
138
|
# Braindump of tests
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: keyp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Baldwin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-05-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gli
|