kaboom 0.3.1

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.
@@ -0,0 +1,117 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.rubygems_version = '1.3.5'
11
+
12
+ ## Leave these as is they will be modified for you by the rake gemspec task.
13
+ ## If your rubyforge_project name is different, then edit it and comment out
14
+ ## the sub! line in the Rakefile
15
+ s.name = 'kaboom'
16
+ s.version = '0.3.1'
17
+ s.date = '2012-03-17'
18
+ s.rubyforge_project = 'boom'
19
+
20
+ ## Make sure your summary is short. The description may be as long
21
+ ## as you like.
22
+ s.summary = "boom lets you access text snippets over your command line."
23
+ s.description = "God it's about every day where I think to myself, gadzooks,
24
+ I keep typing *REPETITIVE_BORING_TASK* over and over. Wouldn't it be great if
25
+ I had something like boom to store all these commonly-used text snippets for
26
+ me? Then I realized that was a worthless idea since boom hadn't been created
27
+ yet and I had no idea what that statement meant. At some point I found the
28
+ code for boom in a dark alleyway and released it under my own name because I
29
+ wanted to look smart."
30
+
31
+ ## List the primary authors. If there are a bunch of authors, it's probably
32
+ ## better to set the email to an email list or something. If you don't have
33
+ ## a custom homepage, consider using your GitHub URL or the like.
34
+ s.authors = ["Zach Holman", "Mark Burns"]
35
+ s.email = 'markthedeveloper@gmail.com'
36
+ s.homepage = 'https://github.com/markburns/kaboom'
37
+
38
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
39
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
40
+ s.require_paths = %w[lib]
41
+
42
+ ## This sections is only necessary if you have C extensions.
43
+ #s.require_paths << 'ext'
44
+ #s.extensions = %w[ext/extconf.rb]
45
+
46
+ ## If your gem includes any executables, list them here.
47
+ s.executables = ["boom", "kaboom"]
48
+ s.default_executable = 'boom'
49
+
50
+ ## Specify any RDoc options here. You'll want to add your README and
51
+ ## LICENSE files to the extra_rdoc_files list.
52
+ s.rdoc_options = ["--charset=UTF-8"]
53
+ s.extra_rdoc_files = %w[README.markdown LICENSE.markdown]
54
+
55
+ ## List your runtime dependencies here. Runtime dependencies are those
56
+ ## that are needed for an end user to actually USE your code.
57
+ s.add_dependency('multi_json', "~> 1.0.3")
58
+ s.add_dependency('json_pure', "~> 1.5.3")
59
+
60
+ ## List your development dependencies here. Development dependencies are
61
+ ## those that are only needed during development
62
+ s.add_development_dependency('mocha', "~> 0.9.9")
63
+ s.add_development_dependency('rake', "~> 0.9.2")
64
+
65
+ ## Leave this section as-is. It will be automatically generated from the
66
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
67
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
68
+ # = MANIFEST =
69
+ s.files = %w[
70
+ CHANGELOG.markdown
71
+ Gemfile
72
+ Gemfile.lock
73
+ LICENSE.markdown
74
+ README.markdown
75
+ Rakefile
76
+ bin/boom
77
+ bin/kaboom
78
+ completion/README.md
79
+ completion/boom.bash
80
+ completion/boom.zsh
81
+ kaboom.gemspec
82
+ lib/kaboom.rb
83
+ lib/kaboom/color.rb
84
+ lib/kaboom/command.rb
85
+ lib/kaboom/config.rb
86
+ lib/kaboom/core_ext/symbol.rb
87
+ lib/kaboom/item.rb
88
+ lib/kaboom/list.rb
89
+ lib/kaboom/output.rb
90
+ lib/kaboom/platform.rb
91
+ lib/kaboom/remote.rb
92
+ lib/kaboom/storage.rb
93
+ lib/kaboom/storage/base.rb
94
+ lib/kaboom/storage/gist.rb
95
+ lib/kaboom/storage/json.rb
96
+ lib/kaboom/storage/keychain.rb
97
+ lib/kaboom/storage/mongodb.rb
98
+ lib/kaboom/storage/redis.rb
99
+ test/examples/config_json.json
100
+ test/examples/test_json.json
101
+ test/examples/urls.json
102
+ test/helper.rb
103
+ test/output_interceptor.rb
104
+ test/test_color.rb
105
+ test/test_command.rb
106
+ test/test_config.rb
107
+ test/test_item.rb
108
+ test/test_list.rb
109
+ test/test_platform.rb
110
+ test/test_remote.rb
111
+ ]
112
+ # = MANIFEST =
113
+
114
+ ## Test files will be grabbed from the file list. Make sure the path glob
115
+ ## matches what you actually use.
116
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
117
+ end
@@ -0,0 +1,59 @@
1
+ # coding: utf-8
2
+
3
+ begin
4
+ require 'rubygems'
5
+ rescue LoadError
6
+ end
7
+
8
+ require 'fileutils'
9
+ require 'multi_json'
10
+
11
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
12
+
13
+ require 'kaboom/output'
14
+ require 'kaboom/color'
15
+ require 'kaboom/platform'
16
+ require 'kaboom/command'
17
+ require 'kaboom/config'
18
+ require 'kaboom/item'
19
+ require 'kaboom/list'
20
+
21
+ require 'kaboom/storage'
22
+ require 'kaboom/storage/base'
23
+ require 'kaboom/storage/json'
24
+ require 'kaboom/storage/redis'
25
+ require 'kaboom/storage/mongodb'
26
+ require 'kaboom/storage/keychain'
27
+ require 'kaboom/storage/gist'
28
+
29
+ require 'kaboom/core_ext/symbol'
30
+ require 'kaboom/remote'
31
+
32
+ module Boom
33
+ VERSION = '0.3.1'
34
+
35
+ extend self
36
+
37
+ def storage
38
+ @storage ||= Boom::Storage.backend
39
+ end
40
+
41
+ # Public: tell Boom to use the storage specified in
42
+ # ~/.boom.remote.conf
43
+ # Returns a Config instance.
44
+ def use_remote remote=true
45
+ @config = Boom::Config.new remote
46
+ end
47
+
48
+ def config
49
+ @config ||= Boom::Config.new
50
+ end
51
+
52
+ def remote?
53
+ config.remote
54
+ end
55
+
56
+ def local?
57
+ !remote?
58
+ end
59
+ end
@@ -0,0 +1,52 @@
1
+ module Boom
2
+ # Color collects some methods for colorizing terminal output.
3
+ module Color
4
+ extend self
5
+
6
+ CODES = {
7
+ :reset => "\e[0m",
8
+
9
+ :cyan => "\e[36m",
10
+ :magenta => "\e[35m",
11
+ :red => "\e[31m",
12
+ :yellow => "\e[33m"
13
+ }
14
+
15
+ # Tries to enable Windows support if on that platform.
16
+ #
17
+ # Returns nothing.
18
+ def self.included(other)
19
+ if RUBY_PLATFORM =~ /win32/ || RUBY_PLATFORM =~ /mingw32/
20
+ require 'Win32/Console/ANSI'
21
+ end
22
+ rescue LoadError
23
+ # Oh well, we tried.
24
+ end
25
+
26
+ # Wraps the given string in ANSI color codes
27
+ #
28
+ # string - The String to wrap.
29
+ # color_code - The String representing he ANSI color code
30
+ #
31
+ # Examples
32
+ #
33
+ # colorize("Boom!", :magenta)
34
+ # # => "\e[35mBoom!\e[0m"
35
+ #
36
+ # Returns the wrapped String unless the the platform is windows and
37
+ # does not have Win32::Console, in which case, returns the String.
38
+ def colorize(string, color_code)
39
+ if !defined?(Win32::Console) && !!(RUBY_PLATFORM =~ /win32/ || RUBY_PLATFORM =~ /mingw32/)
40
+ # looks like this person doesn't have Win32::Console and is on windows
41
+ # just return the uncolorized string
42
+ return string
43
+ end
44
+ "#{CODES[color_code] || color_code}#{string}#{CODES[:reset]}"
45
+ end
46
+
47
+ # Set up shortcut methods to all the codes define in CODES.
48
+ self.class_eval(CODES.keys.reject {|color| color == :reset }.map do |color|
49
+ "def #{color}(string); colorize(string, :#{color}); end"
50
+ end.join("\n"))
51
+ end
52
+ end
@@ -0,0 +1,389 @@
1
+ # coding: utf-8
2
+
3
+ # Command is the main point of entry for boom commands; shell arguments are
4
+ # passd through to Command, which then filters and parses through indivdual
5
+ # commands and reroutes them to constituent object classes.
6
+ #
7
+ # Command also keeps track of one connection to Storage, which is how new data
8
+ # changes are persisted to disk. It takes care of any data changes by calling
9
+ # Boom::Command#save.
10
+ #
11
+ module Boom
12
+ class Command
13
+ class << self
14
+ include Boom::Color
15
+ include Boom::Output
16
+
17
+ # Public: accesses the in-memory JSON representation.
18
+ #
19
+ # Returns a Storage instance.
20
+ def storage
21
+ Boom.storage
22
+ end
23
+
24
+ # Public: executes a command.
25
+ #
26
+ # args - The actual commands to operate on. Can be as few as zero
27
+ # arguments or as many as three.
28
+ def execute(*args)
29
+ command = check_if_remote args
30
+
31
+ major = args.shift
32
+ minor = args.empty? ? nil : args.join(' ')
33
+
34
+ return overview unless command
35
+ delegate(command, major, minor)
36
+ end
37
+
38
+
39
+ def check_if_remote args
40
+ command = args.shift
41
+
42
+ if command == "remote"
43
+ Boom.use_remote
44
+ command = args.shift
45
+ end
46
+ command
47
+ end
48
+
49
+ private :check_if_remote
50
+
51
+ # Public: gets $stdin.
52
+ #
53
+ # Returns the $stdin object. This method exists to help with easy mocking
54
+ # or overriding.
55
+ def stdin
56
+ $stdin
57
+ end
58
+
59
+ # Public: prints a tidy overview of your Lists in descending order of
60
+ # number of Items.
61
+ #
62
+ # Returns nothing.
63
+ def overview
64
+ storage.lists.each do |list|
65
+ output " #{list.name} (#{list.items.size})"
66
+ end
67
+ s = "You don't have anything yet! To start out, create a new list:"
68
+ s << "\n $ boom <list-name>"
69
+ s << "\nAnd then add something to your list!"
70
+ s << "\n $ boom <list-name> <item-name> <item-value>"
71
+ s << "\nYou can then grab your new item:"
72
+ s << "\n $ boom <item-name>"
73
+ output s if storage.lists.size == 0
74
+ end
75
+
76
+ # Public: prints the detailed view of all your Lists and all their
77
+ # Items.
78
+ #
79
+ # Returns nothing.
80
+ def all
81
+ storage.lists.each do |list|
82
+ output " #{list.name}"
83
+ list.items.each do |item|
84
+ output " #{item.short_name}:#{item.spacer} #{item.value}"
85
+ end
86
+ end
87
+ end
88
+
89
+ # Public: allows main access to most commands.
90
+ #
91
+ # Returns output based on method calls.
92
+ def delegate(command, major, minor)
93
+ return all if command == 'all'
94
+ return edit if command == 'edit'
95
+ return switch(major) if command == 'switch'
96
+ return show_storage if command == 'storage'
97
+ return version if command == "-v"
98
+ return version if command == "--version"
99
+ return help if command == 'help'
100
+ return help if command[0] == 45 || command[0] == '-' # any - dash options are pleas for help
101
+ return echo(major,minor) if command == 'echo' || command == 'e'
102
+ return open(major,minor) if command == 'open' || command == 'o'
103
+ return random(major) if command == 'random' || command == 'rand' || command == 'r'
104
+
105
+ # if we're operating on a List
106
+ if storage.list_exists?(command)
107
+ return delete_list(command) if major == 'delete'
108
+ return detail_list(command) unless major
109
+ unless minor == 'delete'
110
+ return add_item(command,major,minor) if minor
111
+ return add_item(command,major,stdin.read) if stdin.stat.size > 0
112
+ return search_list_for_item(command, major)
113
+ end
114
+ end
115
+
116
+ if minor == 'delete' and storage.item_exists?(major)
117
+ return delete_item(command, major)
118
+ end
119
+
120
+ return search_items(command) if storage.item_exists?(command)
121
+
122
+ return create_list(command, major, stdin.read) if !minor && stdin.stat.size > 0
123
+ return create_list(command, major, minor)
124
+ end
125
+
126
+ # Public: shows the current user's storage.
127
+ #
128
+ # Returns nothing.
129
+ def show_storage
130
+ output "You're currently using #{Boom.config.attributes['backend']}."
131
+ end
132
+
133
+ # Public: switch to a new backend.
134
+ #
135
+ # backend - the String of the backend desired
136
+ #
137
+ # Returns nothing.
138
+ def switch(backend)
139
+ Storage.backend = backend
140
+ output "We've switched you over to #{backend}."
141
+ rescue NameError
142
+ output "We couldn't find that storage engine. Check the name and try again."
143
+ end
144
+
145
+ # Public: prints all Items over a List.
146
+ #
147
+ # name - the List object to iterate over
148
+ #
149
+ # Returns nothing.
150
+ def detail_list(name)
151
+ list = List.find(name)
152
+ list.items.sort{ |x,y| x.name <=> y.name }.each do |item|
153
+ output " #{item.short_name}:#{item.spacer} #{item.value}"
154
+ end
155
+ end
156
+
157
+ # Public: opens the Item.
158
+ #
159
+ # Returns nothing.
160
+ def open(major, minor)
161
+ if storage.list_exists?(major)
162
+ list = List.find(major)
163
+ if minor
164
+ item = storage.items.detect { |item| item.name == minor }
165
+ output "#{cyan("Boom!")} We just opened #{yellow(Platform.open(item))} for you."
166
+ else
167
+ list.items.each { |item| Platform.open(item) }
168
+ output "#{cyan("Boom!")} We just opened all of #{yellow(major)} for you."
169
+ end
170
+ else
171
+ item = storage.items.detect { |item| item.name == major }
172
+ output "#{cyan("Boom!")} We just opened #{yellow(Platform.open(item))} for you."
173
+ end
174
+ end
175
+
176
+ # Public: Opens a random item
177
+ #
178
+ # Returns nothing.
179
+ def random(major)
180
+ if major.nil?
181
+ index = rand(storage.items.size)
182
+ item = storage.items[index]
183
+ elsif storage.list_exists?(major)
184
+ list = List.find(major)
185
+ index = rand(list.items.size)
186
+ item = list.items[index]
187
+ else
188
+ output "We couldn't find that list."
189
+ end
190
+ open(item.name, nil) unless item.nil?
191
+ end
192
+
193
+ # Public: echoes only the Item's value without copying
194
+ #
195
+ # item_name - the String term to search for in all Item names
196
+ #
197
+ # Returns nothing
198
+ def echo(major, minor)
199
+ unless minor
200
+ item = storage.items.detect do |item|
201
+ item.name == major
202
+ end
203
+ return output "#{yellow(major)} #{red("not found")}" unless item
204
+ else
205
+ list = List.find(major)
206
+ item = list.find_item(minor)
207
+ return output "#{yellow(minor)} #{red("not found in")} #{yellow(major)}" unless item
208
+ end
209
+ output item.value
210
+ end
211
+
212
+ # Public: add a new List.
213
+ #
214
+ # name - the String name of the List.
215
+ # item - the String name of the Item
216
+ # value - the String value of Item
217
+ #
218
+ # Example
219
+ #
220
+ # Commands.list_create("snippets")
221
+ # Commands.list_create("hotness", "item", "value")
222
+ #
223
+ # Returns the newly created List and creates an item when asked.
224
+ def create_list(name, item = nil, value = nil)
225
+ lists = (storage.lists << List.new(name))
226
+ storage.lists = lists
227
+ output "#{cyan("Boom!")} Created a new list called #{yellow(name)}."
228
+ save
229
+ add_item(name, item, value) unless value.nil?
230
+ end
231
+
232
+ # Public: remove a named List.
233
+ #
234
+ # name - the String name of the List.
235
+ #
236
+ # Example
237
+ #
238
+ # Commands.delete_list("snippets")
239
+ #
240
+ # Returns nothing.
241
+ def delete_list(name)
242
+ output "You sure you want to delete everything in #{yellow(name)}? (y/n):"
243
+ if $stdin.gets.chomp == 'y'
244
+ List.delete(name)
245
+ output "#{cyan("Boom!")} Deleted all your #{yellow(name)}."
246
+ save
247
+ else
248
+ output "Just kidding then."
249
+ end
250
+ end
251
+
252
+ # Public: add a new Item to a list.
253
+ #
254
+ # list - the String name of the List to associate with this Item
255
+ # name - the String name of the Item
256
+ # value - the String value of the Item
257
+ #
258
+ # Example
259
+ #
260
+ # Commands.add_item("snippets","sig","- @holman")
261
+ #
262
+ # Returns the newly created Item.
263
+ def add_item(list,name,value)
264
+ list = List.find(list)
265
+ list.add_item(Item.new(name,value))
266
+ output "#{cyan("Boom!")} #{yellow(name)} in #{yellow(list.name)} is #{yellow(value)}. Got it."
267
+ save
268
+ end
269
+
270
+ # Public: remove a named Item.
271
+ #
272
+ # list_name - the String name of the List.
273
+ # name - the String name of the Item.
274
+ #
275
+ # Example
276
+ #
277
+ # Commands.delete_item("a-list-name", "an-item-name")
278
+ #
279
+ # Returns nothing.
280
+ def delete_item(list_name,name)
281
+ if storage.list_exists?(list_name)
282
+ list = List.find(list_name)
283
+ if list.delete_item(name)
284
+ output "#{cyan("Boom!")} #{yellow(name)} is gone forever."
285
+ save
286
+ else
287
+ output "#{yellow(name)} #{red("not found in")} #{yellow(list_name)}"
288
+ end
289
+ else
290
+ output "We couldn't find that list."
291
+ end
292
+ end
293
+
294
+ # Public: search for an Item in all lists by name. Drops the
295
+ # corresponding entry into your clipboard.
296
+ #
297
+ # name - the String term to search for in all Item names
298
+ #
299
+ # Returns the matching Item.
300
+ def search_items(name)
301
+ item = storage.items.detect do |item|
302
+ item.name == name
303
+ end
304
+
305
+ output "#{cyan("Boom!")} We just copied #{yellow(Platform.copy(item))} to your clipboard."
306
+ end
307
+
308
+ # Public: search for an Item in a particular list by name. Drops the
309
+ # corresponding entry into your clipboard if found.
310
+ #
311
+ # list_name - the String name of the List in which to scope the search
312
+ # item_name - the String term to search for in all Item names
313
+ #
314
+ # Returns the matching Item if found.
315
+ def search_list_for_item(list_name, item_name)
316
+ list = List.find(list_name)
317
+ item = list.find_item(item_name)
318
+
319
+ if item
320
+ output "#{cyan("Boom!")} We just copied #{yellow(Platform.copy(item))} to your clipboard."
321
+ else
322
+ output "#{yellow(item_name)} #{red("not found in")} #{yellow(list_name)}"
323
+ end
324
+ end
325
+
326
+ # Public: save in-memory data to disk.
327
+ #
328
+ # Returns whether or not data was saved.
329
+ def save
330
+ storage.save
331
+ end
332
+
333
+ # Public: the version of boom that you're currently running.
334
+ #
335
+ # Returns a String identifying the version number.
336
+ def version
337
+ output "You're running boom #{Boom::VERSION}. Congratulations!"
338
+ end
339
+
340
+ # Public: launches JSON file in an editor for you to edit manually.
341
+ #
342
+ # Returns nothing.
343
+ def edit
344
+ if storage.respond_to?("json_file")
345
+ output "#{cyan("Boom!")} #{Platform.edit(storage.json_file)}"
346
+ else
347
+ output "This storage backend does not store #{cyan("Boom!")} data on your computer"
348
+ end
349
+ end
350
+
351
+ # Public: prints all the commands of boom.
352
+ #
353
+ # Returns nothing.
354
+ def help
355
+ text = %{
356
+ - boom: help ---------------------------------------------------
357
+
358
+ boom display high-level overview
359
+ boom all show all items in all lists
360
+ boom edit edit the boom JSON file in $EDITOR
361
+ boom help this help text
362
+ boom storage shows which storage backend you're using
363
+ boom switch <storage> switches to a different storage backend
364
+
365
+ boom <list> create a new list
366
+ boom <list> show items for a list
367
+ boom <list> delete deletes a list
368
+
369
+ boom <list> <name> <value> create a new list item
370
+ boom <name> copy item's value to clipboard
371
+ boom <list> <name> copy item's value to clipboard
372
+ boom open <name> open item's url in browser
373
+ boom open <list> <name> open all item's url in browser for a list
374
+ boom random open a random item's url in browser
375
+ boom random <list> open a random item's url for a list in browser
376
+ boom echo <name> echo the item's value without copying
377
+ boom echo <list> <name> echo the item's value without copying
378
+ boom <list> <name> delete deletes an item
379
+
380
+ all other documentation is located at:
381
+ https://github.com/holman/boom
382
+ }.gsub(/^ {8}/, '') # strip the first eight spaces of every line
383
+
384
+ output text
385
+ end
386
+
387
+ end
388
+ end
389
+ end