keyp 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bff8c5655d1b350b28208c20add4b0b3cf884fd2
4
- data.tar.gz: 53a122d43fda4ec7267bd77aea3626dc2e16dba6
3
+ metadata.gz: 2951607e2465cc3204c7f01d993802e75be59acb
4
+ data.tar.gz: efaa651f76f6d65eefde6316111bd62640790c43
5
5
  SHA512:
6
- metadata.gz: cf0bbf2bc59d7a578afd69641032677ab6ff61f7d78331603a63e30fab9bd3dfd68e88756e9c74d0566f189d438bf92ebe91c9ad43ee7bef74b9379d5dac793b
7
- data.tar.gz: baddf04bce4de7c751da7acf783a9527d00c31690cfa7d1a7697c402509c5cd912a9f2d1b4266d95d9f0eec08e79438b4628c2b1e3fd155559d04ef1797b2cc1
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 and is experimental. As such it is very much a work in progress. Not all features
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 a very early stage in development. Any and all functionality is subject to change.
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
- * Get basic functionality working soundly backed with effective tests
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
- # I'm trying out some of the different impementations for Keyp, learning how they work, pros and cons
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 == :setup
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
- command :create do |c|
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
- puts "delete bag is not supported at this time"
212
- # TODO: prompt for verification
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
- post do |global_options, command, options, args|
228
- # if bag.data is dirty, then save
229
- if $debug
230
- puts "post-hook, state of bag.dirty = #{$bag.dirty}"
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
- unless command.name == :setup
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
@@ -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
- ENV['KEPY_HOME'] || File.join(ENV['HOME'], DEFAULT_KEYP_DIR)
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
- nil
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
- File.open(bag_path(name), 'w') do |f|
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 store at #{filepath}. One already exists."
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
@@ -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
- attr_reader :keypdir, :dirty
16
- attr_accessor :name, :data, :file_hash
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, @name+@ext)
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(name, options = {})
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
- @name = name
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
- Keyp::create_bag(name)
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
- file_data = load(keypfile)
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 #{@name} is read only"
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
- unless @read_only
94
- if @data.key? key
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
- val
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
- # Give full path, attempt to load
169
- # sticking with YAML format for now
170
- # May add new file format later in which case we'll
171
- # TODO: consider changing to class method
172
- def load (config_path)
173
- #config_data = {}
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 = load(keypfile)
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
- end
239
- end
333
+
334
+
335
+ end # class Bag
336
+ end # module Keyp
@@ -1,3 +1,3 @@
1
1
  module Keyp
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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
- puts "before : is empty"
8
- @bag = Keyp::Bag.new 'testing123'
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
- #it "should have metadata" do
16
- # @bag.meta['created_on'].should_not == nil
17
- #end
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
- puts "before : is not empty"
23
- @bag = Keyp::Bag.new 'grue_eats_you'
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
- # TODO: pseudo random bag name generation to a helper
35
- bag_name = "grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
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 "bag state" do
90
+ context 'Bag state' do
56
91
 
57
92
  before (:each) do
58
- # TODO: pseudo random bag name generation to a helper
59
- bag_name = "grue_eats_you_when_it_is_dark_#{Time.now.strftime("%Y%m%d%H%M%S%L")}"
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
- it 'should allow assigning a key if not read only'
100
- it 'should not allow assigning a key if read only'
226
+
101
227
  end
@@ -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
- context "CONSTANTS" do
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.6'
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 "should specifify default store extension" do
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 "Keyp directory" do
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
- it 'should be able to override default keyp dir'
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 "Bag management" do
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 "should say a bag does not exist" do
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.6
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-04-03 00:00:00.000000000 Z
11
+ date: 2014-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gli