boom 0.0.10 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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