boom 0.0.10 → 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/CHANGELOG.markdown CHANGED
@@ -1,6 +1,11 @@
1
1
  # boom changes
2
2
 
3
- ## head
3
+ ## 0.1.0
4
+ - boom has been rewritten to use multiple backends. Use `boom switch <backend>`
5
+ to switch from the default JSON backend. Currently only Redis is supported.
6
+ Pull Requests are welcome.
7
+
8
+ ## 0.0.10
4
9
  - `boom open` will open the Item's URL in a browser, or it'll open all the URLs
5
10
  in a List for you. Thanks [lwe](https://github.com/lwe).
6
11
  - Values for item creation can have spaces, and then they get concat'ed as one
@@ -10,6 +15,8 @@
10
15
  - Also started `completion/`, a place to drop in scripts to set up completion
11
16
  support for boom. Starting out with [thbishop](https://github.com/thbishop)'s
12
17
  bash script, but if anyone has something for zsh I'd kiss them a bit.
18
+ - `boom echo` (and `boom e`) just echos the value; great for command-line
19
+ scripts and junk! Thanks [bschaeffer](https://github.com/bschaeffer).
13
20
 
14
21
  ## 0.0.9
15
22
  - Backport `Symbol#to_proc` for 1.8.6 support (thanks
data/README.markdown CHANGED
@@ -1,138 +1,18 @@
1
1
  # B O O M
2
2
 
3
- See? Look, a tiny explosion: \*
3
+ ## About
4
4
 
5
- ## What's a boom?
5
+ boom manages your text snippets. On the command line. I just blew your mind.
6
6
 
7
- boom lets you access text snippets over your command line. I'm personally
8
- aiming for exactly two use cases, but I'm almost positive there are thirteen
9
- more. Here's a couple examples:
10
-
11
- - **Your own [del.icio.us](http://delicious.com)-esque URL tracker.** When I
12
- make [clever animated
13
- gifs](http://github.com/holman/dotfiles/blob/master/bin/gifme) of my
14
- coworkers, I tend to lose the URL, which is a total bummer since I want to
15
- repeatedly repost these images well past their funny expiration date. boom
16
- lets me easily access the good stuff for years to come.
17
- - **Commonly-used email replies.** Everyone's got those stock replies in their
18
- pocket for a few common use cases. Rather than keep some files strewn about
19
- with the responses, boom gives me them on my ever-present command line.
20
- - **Simple todos.** You can super-quickly drop items into lists and remove them
21
- when finished. I'm a big fan of simple, straightforward stuff. Plus, it's a
22
- Dropbox away from simple cloud syncing. Someone get Cultured Code on the line
23
- THIS MAY BE RELEVANT TO THEIR INTERESTS!
24
-
25
- We store everything in one JSON file in your home directory: `~/.boom`. The
26
- structure is simple, too. Each individual item is tossed on a `list`, and you
27
- can have multiple lists.
28
-
29
- ## Show me the boom
30
-
31
- ** Overview **
32
-
33
- $ boom
34
- gifs (5)
35
- email (4)
36
-
37
- ** Create a list **
38
-
39
- $ boom urls
40
- Boom! Created a new list called "urls".
41
-
42
- ** Add an item **
43
-
44
- # boom <list> <name> <value>
45
- $ boom urls github https://github.com
46
- Boom! "github" in "urls" is "https://github.com". Got it.
47
-
48
- ** Copy an item's value to your clipboard **
49
-
50
- $ boom github
51
- Boom! Just copied https://github.com to your clipboard.
52
-
53
- $ boom urls github
54
- Boom! Just copied https://github.com to your clipboard.
55
-
56
- ** List items in a list **
57
-
58
- $ boom urls
59
- blog http://zachholman.com
60
- github http://github.com
61
-
62
- ** Delete a list **
63
-
64
- $ boom urls delete
65
- You sure you want to delete everything in "urls"? (y/n): y
66
- Boom! Deleted all your urls.
67
-
68
- ** Delete an item **
69
-
70
- # boom urls github delete
71
- Boom! "github" is gone forever.
72
-
73
- ** List everything **
74
-
75
- $ boom all
76
- enemies
77
- @kneath: he's got dreamy eyes. he must die.
78
- @rtomayko: i must murder him for his mac and cheese recipe.
79
- @luckiestmonkey: she hates recycling.
80
- urls
81
- blog: http://zachholman.com
82
- github: https://github.com
83
-
84
- ** Open in browser **
85
-
86
- $ boom open facebook-stalking
87
- Boom! We just opened all of "shoes" for you.
88
-
89
- In other words, this will open all the links in `facebook-stalking` in your
90
- browser. You creep. You can also just open up one:
91
-
92
- $ boom open twitter
93
- Boom! We just opened http://twitter.com for you.
94
-
95
- ** Echo an item**
96
-
97
- # boom echo <list> <name>
98
- # boom echo <name>
99
- $ git clone $(boom echo github)holman/boom
100
- Cloning into boom...
101
-
102
- Impossible... no. It's silly, but not impossible.
103
-
104
- ** Help **
105
-
106
- $ boom help
107
-
108
- ** Manual edit **
109
-
110
- If you want to edit the underlying JSON directly, make sure your `$EDITOR`
111
- environment variable is set, and run:
112
-
113
- $ boom edit
114
-
115
- ** It's just the command line, silly **
116
-
117
- So don't forget all your other favorites are there to help you, too:
118
-
119
- $ boom all | grep @luckiestmonkey
120
- @luckiestmonkey: she hates recycling.
7
+ For more details about what boom is and how it works, check out
8
+ [boom's website](http://holman.github.com/boom). For full usage details
9
+ (including a complete list of commands), check out
10
+ [boom's wiki](https://github.com/holman/boom/wiki).
121
11
 
122
12
  ## Install
123
13
 
124
14
  gem install boom
125
15
 
126
- ## Current Status
127
-
128
- Precarious. I'm starting to use this a bunch now, but if you're tossing in
129
- important business information (say, a carefully cultivated list of animated
130
- .gifs), you'd be sitting pretty if you made a backup of `~/.boom` every now and
131
- then, just in case. We're living on the edge, baby.
132
-
133
- Soon enough, though, this'll be stable to the point where I can truthfully tell
134
- myself that this time baby, `boom` will be, bulletttttttttttttproof ♫.
135
-
136
16
  ## Contribute
137
17
 
138
18
  I'd love to include your contributions, friend. Make sure your methods are
data/boom.gemspec CHANGED
@@ -13,8 +13,8 @@ Gem::Specification.new do |s|
13
13
  ## If your rubyforge_project name is different, then edit it and comment out
14
14
  ## the sub! line in the Rakefile
15
15
  s.name = 'boom'
16
- s.version = '0.0.10'
17
- s.date = '2011-01-26'
16
+ s.version = '0.1.0'
17
+ s.date = '2011-02-28'
18
18
  s.rubyforge_project = 'boom'
19
19
 
20
20
  ## Make sure your summary is short. The description may be as long
@@ -75,14 +75,22 @@ Gem::Specification.new do |s|
75
75
  completion/boom.zsh
76
76
  lib/boom.rb
77
77
  lib/boom/command.rb
78
+ lib/boom/config.rb
78
79
  lib/boom/core_ext/symbol.rb
79
80
  lib/boom/item.rb
80
81
  lib/boom/list.rb
81
82
  lib/boom/platform.rb
82
83
  lib/boom/storage.rb
84
+ lib/boom/storage/base.rb
85
+ lib/boom/storage/json.rb
86
+ lib/boom/storage/mongodb.rb
87
+ lib/boom/storage/redis.rb
88
+ test/examples/config_json.json
89
+ test/examples/test_json.json
83
90
  test/examples/urls.json
84
91
  test/helper.rb
85
92
  test/test_command.rb
93
+ test/test_config.rb
86
94
  test/test_item.rb
87
95
  test/test_list.rb
88
96
  ]
data/lib/boom/command.rb CHANGED
@@ -6,7 +6,7 @@
6
6
  #
7
7
  # Command also keeps track of one connection to Storage, which is how new data
8
8
  # changes are persisted to disk. It takes care of any data changes by calling
9
- # Boom::Command#save!.
9
+ # Boom::Command#save.
10
10
  #
11
11
  module Boom
12
12
  class Command
@@ -50,6 +50,13 @@ module Boom
50
50
  storage.lists.each do |list|
51
51
  output " #{list.name} (#{list.items.size})"
52
52
  end
53
+ s = "You don't have anything yet! To start out, create a new list:"
54
+ s << "\n $ boom <list-name>"
55
+ s << "\nAnd then add something to your list!"
56
+ s << "\n $ boom <list-name> <item-name> <item-value>"
57
+ s << "\nYou can then grab your new item:"
58
+ s << "\n $ boom <item-name>"
59
+ output s if storage.lists.size == 0
53
60
  end
54
61
 
55
62
  # Public: prints the detailed view of all your Lists and all their
@@ -71,10 +78,12 @@ module Boom
71
78
  def delegate(command, major, minor)
72
79
  return all if command == 'all'
73
80
  return edit if command == 'edit'
81
+ return switch(major) if command == 'switch'
82
+ return show_storage if command == 'storage'
74
83
  return help if command == 'help'
75
84
  return help if command[0] == 45 || command[0] == '-' # any - dash options are pleas for help
76
85
  return echo(major,minor) if command == 'echo' || command == 'e'
77
- return open(major,minor) if command == 'open'
86
+ return open(major,minor) if command == 'open' || command == 'o'
78
87
 
79
88
  # if we're operating on a List
80
89
  if storage.list_exists?(command)
@@ -95,6 +104,25 @@ module Boom
95
104
  return create_list(command)
96
105
  end
97
106
 
107
+ # Public: shows the current user's storage.
108
+ #
109
+ # Returns nothing.
110
+ def show_storage
111
+ output "You're currently using #{Boom.config.attributes['backend']}."
112
+ end
113
+
114
+ # Public: switch to a new backend.
115
+ #
116
+ # backend - the String of the backend desired
117
+ #
118
+ # Returns nothing.
119
+ def switch(backend)
120
+ Storage.backend = backend
121
+ output "We've switched you over to #{backend}."
122
+ rescue NameError
123
+ output "We couldn't find that storage engine. Check the name and try again."
124
+ end
125
+
98
126
  # Public: prints all Items over a List.
99
127
  #
100
128
  # name - the List object to iterate over
@@ -153,7 +181,7 @@ module Boom
153
181
  lists = (storage.lists << List.new(name))
154
182
  storage.lists = lists
155
183
  output "Boom! Created a new list called \"#{name}\"."
156
- save!
184
+ save
157
185
  end
158
186
 
159
187
  # Public: remove a named List.
@@ -170,7 +198,7 @@ module Boom
170
198
  if $stdin.gets.chomp == 'y'
171
199
  List.delete(name)
172
200
  output "Boom! Deleted all your #{name}."
173
- save!
201
+ save
174
202
  else
175
203
  output "Just kidding then."
176
204
  end
@@ -191,7 +219,7 @@ module Boom
191
219
  list = List.find(list)
192
220
  list.add_item(Item.new(name,value))
193
221
  output "Boom! \"#{name}\" in \"#{list.name}\" is \"#{value}\". Got it."
194
- save!
222
+ save
195
223
  end
196
224
 
197
225
  # Public: remove a named Item.
@@ -208,7 +236,7 @@ module Boom
208
236
  list = List.find(list_name)
209
237
  list.delete_item(name)
210
238
  output "Boom! \"#{name}\" is gone forever."
211
- save!
239
+ save
212
240
  end
213
241
 
214
242
  # Public: search for an Item in all lists by name. Drops the
@@ -246,8 +274,8 @@ module Boom
246
274
  # Public: save in-memory data to disk.
247
275
  #
248
276
  # Returns whether or not data was saved.
249
- def save!
250
- storage.save!
277
+ def save
278
+ storage.save
251
279
  end
252
280
 
253
281
  # Public: launches JSON file in an editor for you to edit manually. Uses
@@ -270,6 +298,8 @@ module Boom
270
298
  boom all show all items in all lists
271
299
  boom edit edit the boom JSON file in $EDITOR
272
300
  boom help this help text
301
+ boom storage shows which storage backend you're using
302
+ boom switch <storage> switches to a different storage backend
273
303
 
274
304
  boom <list> create a new list
275
305
  boom <list> show items for a list
@@ -0,0 +1,89 @@
1
+ # coding: utf-8
2
+
3
+ #
4
+ # Config manages all the config information for boom and its backends. It's a
5
+ # simple JSON Hash that gets persisted to `~/.boom` on-disk. You may access it
6
+ # as a Hash:
7
+ #
8
+ # config.attributes = { :backend => "JSON" }
9
+ # config.attributes[:backend]
10
+ # # => "json"
11
+ #
12
+ # config.attributes[:backend] = "Redis"
13
+ # config.attributes[:backend]
14
+ # # => "redis"
15
+ #
16
+ module Boom
17
+ class Config
18
+
19
+ # The main config file for boom
20
+ FILE = "#{ENV['HOME']}/.boom.conf"
21
+
22
+ # Public: The attributes Hash for configuration options. The attributes
23
+ # needed are dictated by each backend, but the `backend` option must be
24
+ # present.
25
+ attr_reader :attributes
26
+
27
+ # Public: creates a new instance of Config.
28
+ #
29
+ # This will load the attributes from boom's config file, or bootstrap it
30
+ # if this is a new install. Bootstrapping defaults to the JSON backend.
31
+ #
32
+ # Returns nothing.
33
+ def initialize
34
+ bootstrap unless File.exist?(file)
35
+ load_attributes
36
+ end
37
+
38
+ # Public: accessor for the configuration file.
39
+ #
40
+ # Returns the String file path.
41
+ def file
42
+ FILE
43
+ end
44
+
45
+ # Public: saves an empty, barebones hash to @attributes for the purpose of
46
+ # new user setup.
47
+ #
48
+ # Returns whether the attributes were saved.
49
+ def bootstrap
50
+ @attributes = {
51
+ :backend => 'JSON'
52
+ }
53
+ save
54
+ end
55
+
56
+ # Public: assigns a hash to the configuration attributes object. The
57
+ # contents of the attributes hash depends on what the backend needs. A
58
+ # `backend` key MUST be present, however.
59
+ #
60
+ # attrs - the Hash representation of attributes to persist to disk.
61
+ #
62
+ # Examples
63
+ #
64
+ # config.attributes = {"backend" => "json"}
65
+ #
66
+ # Returns whether the attributes were saved.
67
+ def attributes=(attrs)
68
+ @attributes = attrs
69
+ save
70
+ end
71
+
72
+ # Public: loads and parses the JSON tree from disk into memory and stores
73
+ # it in the attributes Hash.
74
+ #
75
+ # Returns nothing.
76
+ def load_attributes
77
+ @attributes = Yajl::Parser.new.parse(File.new(file, 'r'))
78
+ end
79
+
80
+ # Public: writes the in-memory JSON Hash to disk.
81
+ #
82
+ # Returns nothing.
83
+ def save
84
+ json = Yajl::Encoder.encode(attributes, :pretty => true)
85
+ File.open(file, 'w') {|f| f.write(json) }
86
+ end
87
+
88
+ end
89
+ end
@@ -0,0 +1,81 @@
1
+ # coding: utf-8
2
+
3
+ # Storage is the middleman between changes the client makes in-memory and how
4
+ # it's actually persisted to disk (and vice-versa). There are also a few
5
+ # convenience methods to run searches and operations on the in-memory hash.
6
+ #
7
+ module Boom
8
+ module Storage
9
+ class Base
10
+
11
+ # Public: initializes a Storage instance by loading in your persisted data from adapter.
12
+ #
13
+ # Returns the Storage instance.
14
+ def initialize
15
+ @lists = []
16
+ bootstrap
17
+ populate
18
+ end
19
+
20
+ # run bootstrap tasks for the storage
21
+ def bootstrap ; end
22
+
23
+ # populate the in-memory store with all the lists and items
24
+ def populate ; end
25
+
26
+ # save the data
27
+ def save ; end
28
+
29
+ # Public: the in-memory collection of all Lists attached to this Storage
30
+ # instance.
31
+ #
32
+ # lists - an Array of individual List items
33
+ #
34
+ # Returns nothing.
35
+ attr_writer :lists
36
+
37
+ # Public: the list of Lists in your JSON data, sorted by number of items
38
+ # descending.
39
+ #
40
+ # Returns an Array of List objects.
41
+ def lists
42
+ @lists.sort_by { |list| -list.items.size }
43
+ end
44
+
45
+ # Public: tests whether a named List exists.
46
+ #
47
+ # name - the String name of a List
48
+ #
49
+ # Returns true if found, false if not.
50
+ def list_exists?(name)
51
+ @lists.detect { |list| list.name == name }
52
+ end
53
+
54
+ # Public: all Items in storage.
55
+ #
56
+ # Returns an Array of all Items.
57
+ def items
58
+ @lists.collect(&:items).flatten
59
+ end
60
+
61
+ # Public: tests whether a named Item exists.
62
+ #
63
+ # name - the String name of an Item
64
+ #
65
+ # Returns true if found, false if not.
66
+ def item_exists?(name)
67
+ items.detect { |item| item.name == name }
68
+ end
69
+
70
+ # Public: creates a Hash of the representation of the in-memory data
71
+ # structure. This percolates down to Items by calling to_hash on the List,
72
+ # which in tern calls to_hash on individual Items.
73
+ #
74
+ # Returns a Hash of the entire data set.
75
+ def to_hash
76
+ { :lists => lists.collect(&:to_hash) }
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,69 @@
1
+ # coding: utf-8
2
+ #
3
+ # Json is the default storage option for boom. It writes a Json file to
4
+ # ~/.boom. Pretty neat, huh?
5
+ #
6
+ module Boom
7
+ module Storage
8
+ class Json < Base
9
+
10
+ JSON_FILE = "#{ENV['HOME']}/.boom"
11
+
12
+ # Public: the path to the Json file used by boom.
13
+ #
14
+ # Returns the String path of boom's Json representation.
15
+ def json_file
16
+ JSON_FILE
17
+ end
18
+
19
+ # Takes care of bootstrapping the Json file, both in terms of creating the
20
+ # file and in terms of creating a skeleton Json schema.
21
+ #
22
+ # Return true if successfully saved.
23
+ def bootstrap
24
+ return if File.exist?(json_file)
25
+ FileUtils.touch json_file
26
+ File.open(json_file, 'w') {|f| f.write(to_json) }
27
+ save
28
+ end
29
+
30
+ # Take a Json representation of data and explode it out into the consituent
31
+ # Lists and Items for the given Storage instance.
32
+ #
33
+ # Returns nothing.
34
+ def populate
35
+ storage = Yajl::Parser.new.parse(File.new(json_file, 'r'))
36
+
37
+ storage['lists'].each do |lists|
38
+ lists.each do |list_name, items|
39
+ @lists << list = List.new(list_name)
40
+
41
+ items.each do |item|
42
+ item.each do |name,value|
43
+ list.add_item(Item.new(name,value))
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Public: persists your in-memory objects to disk in Json format.
51
+ #
52
+ # lists_Json - list in Json format
53
+ #
54
+ # Returns true if successful, false if unsuccessful.
55
+ def save
56
+ File.open(json_file, 'w') {|f| f.write(to_json) }
57
+ end
58
+
59
+ # Public: the Json representation of the current List and Item assortment
60
+ # attached to the Storage instance.
61
+ #
62
+ # Returns a String Json representation of its Lists and their Items.
63
+ def to_json
64
+ Yajl::Encoder.encode(to_hash, :pretty => true)
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,139 @@
1
+ # coding: utf-8
2
+
3
+ # Storage adapter that saves data from boom to MongoDB instead of JSON file.
4
+ #
5
+ # This was grabbed from antonlindstrom's fork, but he wrote it before the 0.1.0
6
+ # changes to boom. It would need to be updated to use Boom::Config and to
7
+ # inherit methods from Boom::Storage::Base. Until then, this is chillin' here
8
+ # until it gets fixed.
9
+
10
+ module Boom
11
+ module Storage
12
+ class MongoDB
13
+
14
+ MONGO_CFG = "#{ENV['HOME']}/.boomdb.conf"
15
+
16
+ # Public: the path to the JSON Mongo config file, userdata.
17
+ #
18
+ # Returns the String path of boom's MongoDB userdata
19
+ def mongo_cfg
20
+ MONGO_CFG
21
+ end
22
+
23
+ # Public: initializes a Storage instance by loading in your persisted data.
24
+ #
25
+ # Returns the Storage instance.
26
+ def initialize
27
+ require 'mongo'
28
+
29
+ @lists = []
30
+ @mongo_coll = nil
31
+ mongo_initialize(mongo_cfg) # Initialize the MongoDB and set data in memory
32
+ collect
33
+ end
34
+
35
+ # Public: return the list from mongodb
36
+ #
37
+ # Returns list
38
+ def lists
39
+ @lists
40
+ end
41
+
42
+ # Public: Save to MongoDB
43
+ #
44
+ # lists_json - the list to be saved in JSON format
45
+ #
46
+ # Returns whatever mongo returns.
47
+ def save(lists_json)
48
+ doc = @mongo_coll.find_one()
49
+ @mongo_coll.update({"_id" => doc["_id"]}, {'boom' => lists_json})
50
+ end
51
+
52
+ # INTERNAL METHODS ##########################################################
53
+
54
+ # Take a JSON representation of data and explode it out into the consituent
55
+ # Lists and Items for the given Storage instance.
56
+ #
57
+ # Returns nothing.
58
+ def collect
59
+
60
+ s = @mongo_coll.find_one['boom']
61
+ storage = Yajl::Parser.new.parse(s)
62
+
63
+ return if storage['lists'].nil?
64
+
65
+ storage['lists'].each do |lists|
66
+ lists.each do |list_name, items|
67
+ @lists << list = List.new(list_name)
68
+
69
+ items.each do |item|
70
+ item.each do |name,value|
71
+ list.add_item(Item.new(name,value))
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ ##### MONGODB ######
79
+
80
+ # Initialize MongoDB and set data in memory
81
+ #
82
+ # config_file - The MongoDB config_file path defined
83
+ #
84
+ # Returns database obj.
85
+ def mongo_initialize(config_file)
86
+ bootstrap_config(config_file) unless File.exists?(config_file)
87
+ config = parse_mongo_cfg(config_file)
88
+
89
+ db = Mongo::Connection.new(config['host'], config['port']).db(config['database'])
90
+ auth = db.authenticate(config['username'], config['password'])
91
+
92
+ # Get collection collection;
93
+ @mongo_coll = db.collection(config['collection'])
94
+
95
+ @mongo_coll.insert("boom" => '{"lists": [{}]}') if @mongo_coll.find_one.nil?
96
+
97
+ return @mongo_coll
98
+ end
99
+
100
+ # Parse Mongo JSON Config
101
+ #
102
+ # config_file - The MongoDB config_file path defined
103
+ #
104
+ # Returns a hash of Mongo Userdata
105
+ def parse_mongo_cfg(config_file)
106
+
107
+ mongod = Hash.new
108
+ config = Yajl::Parser.new.parse(File.open(config_file, 'r'))
109
+
110
+ config.each_pair do |type, data|
111
+ mongod[type] = data
112
+ end
113
+ return mongod
114
+ end
115
+
116
+ # Run a default config
117
+ #
118
+ # config_file - The MongoDB config_file path defined
119
+ #
120
+ # Returns File obj.
121
+ def bootstrap_config(config_file)
122
+ config = Hash.new
123
+
124
+ config['host'] = 'localhost'
125
+ config['port'] = '27017'
126
+ config['database'] = 'boom'
127
+ config['username'] = 'boom'
128
+ config['password'] = 's3cr3t'
129
+ config['collection'] = 'boom'
130
+
131
+ # Write to CFG
132
+ json_cfg = Yajl::Encoder.encode(config, :pretty => true)
133
+ File.open(config_file, 'w') {|f| f.write(json_cfg) }
134
+ end
135
+
136
+
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,61 @@
1
+ # coding: utf-8
2
+ #
3
+ # Sup, Redis.
4
+ #
5
+ require 'digest'
6
+ require 'redis'
7
+
8
+ module Boom
9
+ module Storage
10
+ class Redis < Base
11
+
12
+ def redis
13
+ @redis ||= ::Redis.new :host => Boom.config.attributes["redis"]["host"],
14
+ :port => Boom.config.attributes["redis"]["port"]
15
+ end
16
+
17
+ def bootstrap
18
+ end
19
+
20
+ def populate
21
+ lists = redis.smembers("boom:lists") || []
22
+
23
+ lists.each do |sha|
24
+ list_name = redis.get("boom:lists:#{sha}:name")
25
+ @lists << list = List.new(list_name)
26
+
27
+ shas = redis.lrange("boom:lists:#{sha}:items",0,-1) || []
28
+
29
+ shas.each do |item_sha|
30
+ name = redis.get "boom:items:#{item_sha}:name"
31
+ value = redis.get "boom:items:#{item_sha}:value"
32
+ list.add_item(Item.new(name, value))
33
+ end
34
+ end
35
+ end
36
+
37
+ def clear
38
+ redis.del "boom:lists"
39
+ redis.del "boom:items"
40
+ end
41
+
42
+ def save
43
+ clear
44
+
45
+ lists.each do |list|
46
+ list_sha = Digest::SHA1.hexdigest(list.name)
47
+ redis.set "boom:lists:#{list_sha}:name", list.name
48
+ redis.sadd "boom:lists", list_sha
49
+
50
+ list.items.each do |item|
51
+ item_sha = Digest::SHA1.hexdigest(item.name)
52
+ redis.rpush "boom:lists:#{list_sha}:items", item_sha
53
+ redis.set "boom:items:#{item_sha}:name", item.name
54
+ redis.set "boom:items:#{item_sha}:value", item.value
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+ end
61
+ end
data/lib/boom/storage.rb CHANGED
@@ -1,126 +1,21 @@
1
1
  # coding: utf-8
2
2
 
3
- # Storage is the middleman between changes the client makes in-memory and how
4
- # it's actually persisted to disk (and vice-versa). There are also a few
5
- # convenience methods to run searches and operations on the in-memory hash.
3
+ #
4
+ # Storage is the interface between multiple Backends. You can use Storage
5
+ # directly without having to worry about which Backend is in use.
6
6
  #
7
7
  module Boom
8
- class Storage
8
+ module Storage
9
9
 
10
- JSON_FILE = "#{ENV['HOME']}/.boom"
11
-
12
- # Public: the path to the JSON file used by boom.
13
- #
14
- # Returns the String path of boom's JSON representation.
15
- def json_file
16
- JSON_FILE
17
- end
18
-
19
- # Public: initializes a Storage instance by loading in your persisted data.
20
- #
21
- # Returns the Storage instance.
22
- def initialize
23
- @lists = []
24
- explode_json(json_file)
25
- end
26
-
27
- # Public: the in-memory collection of all Lists attached to this Storage
28
- # instance.
29
- #
30
- # lists - an Array of individual List items
31
- #
32
- # Returns nothing.
33
- attr_writer :lists
34
-
35
- # Public: the list of Lists in your JSON data, sorted by number of items
36
- # descending.
37
- #
38
- # Returns an Array of List objects.
39
- def lists
40
- @lists.sort_by { |list| -list.items.size }
41
- end
42
-
43
- # Public: tests whether a named List exists.
44
- #
45
- # name - the String name of a List
46
- #
47
- # Returns true if found, false if not.
48
- def list_exists?(name)
49
- @lists.detect { |list| list.name == name }
50
- end
51
-
52
- # Public: all Items in storage.
53
- #
54
- # Returns an Array of all Items.
55
- def items
56
- @lists.collect(&:items).flatten
57
- end
58
-
59
- # Public: tests whether a named Item exists.
60
- #
61
- # name - the String name of an Item
62
- #
63
- # Returns true if found, false if not.
64
- def item_exists?(name)
65
- items.detect { |item| item.name == name }
66
- end
67
-
68
- # Public: persists your in-memory objects to disk in JSON format.
69
- #
70
- # Returns true if successful, false if unsuccessful.
71
- def save!
72
- File.open(json_file, 'w') {|f| f.write(to_json) }
73
- end
74
-
75
- # Public: the JSON representation of the current List and Item assortment
76
- # attached to the Storage instance.
77
- #
78
- # Returns a String JSON representation of its Lists and their Items.
79
- def to_json
80
- Yajl::Encoder.encode(to_hash, :pretty => true)
81
- end
82
-
83
- # Public: creates a Hash of the representation of the in-memory data
84
- # structure. This percolates down to Items by calling to_hash on the List,
85
- # which in tern calls to_hash on individual Items.
86
- #
87
- # Returns a Hash of the entire data set.
88
- def to_hash
89
- { :lists => lists.collect(&:to_hash) }
90
- end
91
-
92
-
93
- # INTERNAL METHODS ##########################################################
94
-
95
- # Take a JSON representation of data and explode it out into the consituent
96
- # Lists and Items for the given Storage instance.
97
- #
98
- # Returns nothing.
99
- def explode_json(json)
100
- bootstrap_json unless File.exist?(json)
101
-
102
- storage = Yajl::Parser.new.parse(File.new(json, 'r'))
103
-
104
- storage['lists'].each do |lists|
105
- lists.each do |list_name, items|
106
- @lists << list = List.new(list_name)
107
-
108
- items.each do |item|
109
- item.each do |name,value|
110
- list.add_item(Item.new(name,value))
111
- end
112
- end
113
- end
114
- end
10
+ def self.backend=(backend)
11
+ backend = backend.capitalize
12
+ Boom::Storage.const_get(backend)
13
+ Boom.config.attributes['backend'] = backend.downcase
14
+ Boom.config.save
115
15
  end
116
16
 
117
- # Takes care of bootstrapping the JSON file, both in terms of creating the
118
- # file and in terms of creating a skeleton JSON schema.
119
- #
120
- # Return true if successfully saved.
121
- def bootstrap_json
122
- FileUtils.touch json_file
123
- save!
17
+ def self.backend
18
+ Boom::Storage.const_get(Boom.config.attributes['backend'].capitalize).new
124
19
  end
125
20
 
126
21
  end
data/lib/boom.rb CHANGED
@@ -12,16 +12,29 @@ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
12
12
 
13
13
  require 'boom/platform'
14
14
  require 'boom/command'
15
+ require 'boom/config'
15
16
  require 'boom/item'
16
17
  require 'boom/list'
18
+
17
19
  require 'boom/storage'
20
+ require 'boom/storage/base'
21
+ require 'boom/storage/json'
22
+ require 'boom/storage/redis'
23
+ require 'boom/storage/mongodb'
18
24
 
19
25
  require 'boom/core_ext/symbol'
20
26
 
21
27
  module Boom
22
- VERSION = '0.0.10'
28
+ VERSION = '0.1.0'
29
+
30
+ extend self
23
31
 
24
- def self.storage
25
- @storage ||= Boom::Storage.new
32
+ def storage
33
+ @storage ||= Boom::Storage.backend
26
34
  end
35
+
36
+ def config
37
+ @config ||= Boom::Config.new
38
+ end
39
+
27
40
  end
@@ -0,0 +1,3 @@
1
+ {
2
+ backend: 'Json'
3
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "backend": "Json"
3
+ }
data/test/helper.rb CHANGED
@@ -16,7 +16,9 @@ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
16
16
  require 'boom'
17
17
 
18
18
  def boom_json(name)
19
- Boom::Storage.any_instance.stubs(:json_file).
20
- returns("test/examples/#{name}.json")
21
- Boom.stubs(:storage).returns(Boom::Storage.new)
19
+ root = File.expand_path(File.dirname(__FILE__))
20
+ Boom::Storage::Json.any_instance.stubs(:save).returns(true)
21
+ Boom::Storage::Json.any_instance.stubs(:json_file).
22
+ returns("#{root}/examples/#{name}.json")
23
+ Boom.stubs(:storage).returns(Boom::Storage::Json.new)
22
24
  end
data/test/test_command.rb CHANGED
@@ -36,6 +36,13 @@ class TestCommand < Test::Unit::TestCase
36
36
  Boom::Command.captured_output
37
37
  end
38
38
 
39
+ def test_overview_for_empty
40
+ storage = Boom::Storage
41
+ storage.stubs(:lists).returns([])
42
+ Boom::Command.stubs(:storage).returns(storage)
43
+ assert_match /have anything yet!/, command(nil)
44
+ end
45
+
39
46
  def test_overview
40
47
  assert_equal ' urls (2)', command(nil)
41
48
  end
@@ -139,4 +146,19 @@ class TestCommand < Test::Unit::TestCase
139
146
  def test_echo_list_item_does_not_exist
140
147
  assert_match /"wrong" not found in "urls"/, command('echo urls wrong')
141
148
  end
149
+
150
+ def test_show_storage
151
+ assert_match /You're currently using json/, command('storage')
152
+ end
153
+
154
+ def test_nonexistant_storage_switch
155
+ Boom::Config.any_instance.stubs(:save).returns(true)
156
+ assert_match /couldn't find that storage engine/, command('switch dkdkdk')
157
+ end
158
+
159
+ def test_storage_switch
160
+ Boom::Config.any_instance.stubs(:save).returns(true)
161
+ assert_match /We've switched you over to redis/, command('switch redis')
162
+ end
163
+
142
164
  end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+
3
+ require 'helper'
4
+
5
+ class TestConfig < Test::Unit::TestCase
6
+
7
+ def setup
8
+ Boom::Config.any_instance.stubs(:file).
9
+ returns("test/examples/test_json.json")
10
+
11
+ @config = Boom::Config.new
12
+ @config.stubs(:save).returns(true)
13
+ end
14
+
15
+ def test_bootstraps_config
16
+ @config.bootstrap
17
+ assert_equal ({:backend => 'JSON'}), @config.attributes
18
+ end
19
+
20
+ def test_attributes
21
+ @config.attributes[:wu_tang] = 'clan'
22
+ assert_equal 'clan', @config.attributes[:wu_tang]
23
+ end
24
+
25
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boom
3
3
  version: !ruby/object:Gem::Version
4
- hash: 11
4
+ hash: 27
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
+ - 1
8
9
  - 0
9
- - 10
10
- version: 0.0.10
10
+ version: 0.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Zach Holman
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2011-01-26 00:00:00 -08:00
18
+ date: 2011-02-28 00:00:00 -08:00
19
19
  default_executable: boom
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -77,14 +77,22 @@ files:
77
77
  - completion/boom.zsh
78
78
  - lib/boom.rb
79
79
  - lib/boom/command.rb
80
+ - lib/boom/config.rb
80
81
  - lib/boom/core_ext/symbol.rb
81
82
  - lib/boom/item.rb
82
83
  - lib/boom/list.rb
83
84
  - lib/boom/platform.rb
84
85
  - lib/boom/storage.rb
86
+ - lib/boom/storage/base.rb
87
+ - lib/boom/storage/json.rb
88
+ - lib/boom/storage/mongodb.rb
89
+ - lib/boom/storage/redis.rb
90
+ - test/examples/config_json.json
91
+ - test/examples/test_json.json
85
92
  - test/examples/urls.json
86
93
  - test/helper.rb
87
94
  - test/test_command.rb
95
+ - test/test_config.rb
88
96
  - test/test_item.rb
89
97
  - test/test_list.rb
90
98
  has_rdoc: true
@@ -123,5 +131,6 @@ specification_version: 2
123
131
  summary: boom lets you access text snippets over your command line.
124
132
  test_files:
125
133
  - test/test_command.rb
134
+ - test/test_config.rb
126
135
  - test/test_item.rb
127
136
  - test/test_list.rb