boom 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/License ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) Zach Holman, http://zachholman.com
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,150 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/test_*.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Generate RCov test coverage and open in your browser"
56
+ task :coverage do
57
+ require 'rcov'
58
+ sh "rm -fr coverage"
59
+ sh "rcov test/test_*.rb"
60
+ sh "open coverage/index.html"
61
+ end
62
+
63
+ require 'rake/rdoctask'
64
+ Rake::RDocTask.new do |rdoc|
65
+ rdoc.rdoc_dir = 'rdoc'
66
+ rdoc.title = "#{name} #{version}"
67
+ rdoc.rdoc_files.include('README*')
68
+ rdoc.rdoc_files.include('lib/**/*.rb')
69
+ end
70
+
71
+ desc "Open an irb session preloaded with this library"
72
+ task :console do
73
+ sh "irb -rubygems -r ./lib/#{name}.rb"
74
+ end
75
+
76
+ #############################################################################
77
+ #
78
+ # Custom tasks (add your own tasks here)
79
+ #
80
+ #############################################################################
81
+
82
+
83
+
84
+ #############################################################################
85
+ #
86
+ # Packaging tasks
87
+ #
88
+ #############################################################################
89
+
90
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
91
+ task :release => :build do
92
+ unless `git branch` =~ /^\* master$/
93
+ puts "You must be on the master branch to release!"
94
+ exit!
95
+ end
96
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
97
+ sh "git tag v#{version}"
98
+ sh "git push origin master"
99
+ sh "git push origin v#{version}"
100
+ sh "gem push pkg/#{name}-#{version}.gem"
101
+ end
102
+
103
+ desc "Build #{gem_file} into the pkg directory"
104
+ task :build => :gemspec do
105
+ sh "mkdir -p pkg"
106
+ sh "gem build #{gemspec_file}"
107
+ sh "mv #{gem_file} pkg"
108
+ end
109
+
110
+ desc "Generate #{gemspec_file}"
111
+ task :gemspec => :validate do
112
+ # read spec file and split out manifest section
113
+ spec = File.read(gemspec_file)
114
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
115
+
116
+ # replace name version and date
117
+ replace_header(head, :name)
118
+ replace_header(head, :version)
119
+ replace_header(head, :date)
120
+ #comment this out if your rubyforge_project has a different name
121
+ replace_header(head, :rubyforge_project)
122
+
123
+ # determine file list from git ls-files
124
+ files = `git ls-files`.
125
+ split("\n").
126
+ sort.
127
+ reject { |file| file =~ /^\./ }.
128
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
129
+ map { |file| " #{file}" }.
130
+ join("\n")
131
+
132
+ # piece file back together and write
133
+ manifest = " s.files = %w[\n#{files}\n ]\n"
134
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
135
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
136
+ puts "Updated #{gemspec_file}"
137
+ end
138
+
139
+ desc "Validate #{gemspec_file}"
140
+ task :validate do
141
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
142
+ unless libfiles.empty?
143
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
144
+ exit!
145
+ end
146
+ unless Dir['VERSION*'].empty?
147
+ puts "A `VERSION` file at root level violates Gem best practices."
148
+ exit!
149
+ end
150
+ end
data/Readme.markdown ADDED
@@ -0,0 +1,116 @@
1
+ # B O O M
2
+
3
+ See? Look, a tiny explosion: \*
4
+
5
+ ## What's a boom?
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
+ ** It's just the command line, silly **
85
+
86
+ So don't forget all your other favorites are there to help you, too:
87
+
88
+ $ boom all | grep @luckiestmonkey
89
+ @luckiestmonkey: she hates recycling.
90
+
91
+ ## Install
92
+
93
+ gem install boom
94
+
95
+ ## Current Status
96
+
97
+ Precarious. I'm starting to use this a bunch now, but if you're tossing in
98
+ important business information (say, a carefully cultivated list of animated
99
+ .gifs), you'd be sitting pretty if you made a backup of `~/.boom` every now and
100
+ then, just in case. We're living on the edge, baby.
101
+
102
+ Also, don't mistype anything. Everything should be 100% perfect if you type all
103
+ the commands correctly. If you don't, it'll probably ungracefully error out and
104
+ a puppy in Kansas will probably be murdered. You should be fine, though. As
105
+ long as you don't have butter fingers.
106
+
107
+ Soon enough, though, this'll be stable to the point where I can truthfully tell
108
+ myself that this time baby, `boom` will be, bulletttttttttttttproof ♫.
109
+
110
+ ## I love you
111
+
112
+ [Zach Holman](http://zachholman.com) made this. Ping me on Twitter —
113
+ [@holman](http://twitter.com/holman) — if you're having issues, want me to
114
+ merge in your pull request, or are using boom in a cool way. I'm kind of hoping
115
+ this is generic enough that people do some fun things with it. First one to use
116
+ `boom` to calculate their tax liability wins.
data/bin/boom ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
4
+
5
+ require 'boom'
6
+
7
+ storage = Boom::Storage.new
8
+ Boom::Command.execute(storage,*ARGV)
data/boom.gemspec ADDED
@@ -0,0 +1,90 @@
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 = 'boom'
16
+ s.version = '0.0.1'
17
+ s.date = '2010-11-24'
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"]
35
+ s.email = 'github.com@zachholman.com'
36
+ s.homepage = 'https://github.com/holman/boom'
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"]
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]
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('yajl-ruby', "~> 0.7.8")
58
+
59
+ ## List your development dependencies here. Development dependencies are
60
+ ## those that are only needed during development
61
+ s.add_development_dependency('mocha', "~> 0.9.9")
62
+
63
+ ## Leave this section as-is. It will be automatically generated from the
64
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
65
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
66
+ # = MANIFEST =
67
+ s.files = %w[
68
+ License
69
+ Rakefile
70
+ Readme.markdown
71
+ bin/boom
72
+ boom.gemspec
73
+ lib/boom.rb
74
+ lib/boom/clipboard.rb
75
+ lib/boom/command.rb
76
+ lib/boom/item.rb
77
+ lib/boom/list.rb
78
+ lib/boom/storage.rb
79
+ test/examples/urls.json
80
+ test/helper.rb
81
+ test/test_command.rb
82
+ test/test_item.rb
83
+ test/test_list.rb
84
+ ]
85
+ # = MANIFEST =
86
+
87
+ ## Test files will be grabbed from the file list. Make sure the path glob
88
+ ## matches what you actually use.
89
+ s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
90
+ end
@@ -0,0 +1,22 @@
1
+ # Clipboard is a centralized point to shell out to each individual platform's
2
+ # clipboard, pasteboard, or whatever they decide to call it.
3
+ #
4
+ module Boom
5
+ class Clipboard
6
+ class << self
7
+
8
+ # Public: copies a given Item's value to the clipboard. This method is
9
+ # designed to handle multiple platforms.
10
+ #
11
+ # Returns nothing.
12
+ def copy(item)
13
+ if RUBY_PLATFORM =~ /darwin/
14
+ `echo '#{item.value}' | tr -d "\n" | pbcopy`
15
+ end
16
+
17
+ "Boom! We just copied #{item.value} to your clipboard."
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,212 @@
1
+ # Command is the main point of entry for boom commands; shell arguments are
2
+ # passd through to Command, which then filters and parses through indivdual
3
+ # commands and reroutes them to constituent object classes.
4
+ #
5
+ # Command also keeps track of one connection to Storage, which is how new data
6
+ # changes are persisted to disk. It takes care of any data changes by calling
7
+ # Boom::Command#save!.
8
+ #
9
+ module Boom
10
+ class Command
11
+ class << self
12
+
13
+ attr_accessor :storage
14
+
15
+ # Public: executes a command.
16
+ #
17
+ # storage - The Storage instance off which to run commands. This is
18
+ # likely just Boom::Storage.new, since Boom::Storage should
19
+ # pick up the appropriate JSON file paths on its own.
20
+ # args - The actual commands to operate on. Can be as few as zero
21
+ # arguments or as many as three.
22
+ def execute(storage,*args)
23
+ @storage = storage
24
+
25
+ command = args[0]
26
+ major = args[1]
27
+ minor = args[2]
28
+
29
+ return overview unless command
30
+ delegate(command, major, minor)
31
+ end
32
+
33
+ # Public: prints any given string.
34
+ #
35
+ # s = String output
36
+ #
37
+ # Prints to STDOUT and returns. This method exists to standardize output
38
+ # and for easy mocking or overriding.
39
+ def output(s)
40
+ puts(s)
41
+ end
42
+
43
+ # Public: prints a tidy overview of your Lists in descending order of
44
+ # number of Items.
45
+ #
46
+ # Returns nothing.
47
+ def overview
48
+ storage.lists.each do |list|
49
+ output " #{list.name} (#{list.items.size})"
50
+ end
51
+ end
52
+
53
+ # Public: prints the detailed view of all your Lists and all their
54
+ # Items.
55
+ #
56
+ # Returns nothing.
57
+ def all
58
+ storage.lists.each do |list|
59
+ output " #{list.name}"
60
+ list.items.each do |item|
61
+ output " #{item.name}: #{item.value}"
62
+ end
63
+ end
64
+ end
65
+
66
+ # Public: allows main access to most commands.
67
+ #
68
+ # Returns output based on method calls.
69
+ def delegate(command, major, minor)
70
+ return all if command == 'all'
71
+
72
+ # if we're operating on a List
73
+ if storage.list_exists?(command)
74
+ return list_delete(command) if major == 'delete'
75
+ return list_detail(command) unless major
76
+ unless minor == 'delete'
77
+ return add_item(command,major,minor) if minor
78
+ return search_list_for_item(command, major)
79
+ end
80
+ end
81
+
82
+ return search_items(command) if storage.item_exists?(command)
83
+
84
+ if minor == 'delete' and storage.item_exists?(major)
85
+ return item_delete(major)
86
+ end
87
+
88
+ return list_create(command)
89
+ end
90
+
91
+ # Public: prints all Items over a List.
92
+ #
93
+ # list - the List object to iterate over
94
+ #
95
+ # Returns nothing.
96
+ def list_detail(list_name)
97
+ list = storage.lists.first { |list| list.name == list_name }
98
+ list.items.sort{ |x,y| x.name <=> y.name }.each do |item|
99
+ output " #{item.name}: #{item.value}"
100
+ end
101
+ end
102
+
103
+ # Public: add a new List.
104
+ #
105
+ # name - the String name of the List.
106
+ #
107
+ # Example
108
+ #
109
+ # Commands.list_create("snippets")
110
+ #
111
+ # Returns the newly created List.
112
+ def list_create(name)
113
+ lists = (storage.lists << List.new(name))
114
+ storage.lists = lists
115
+ output "Boom! Created a new list called \"#{name}\"."
116
+ save!
117
+ end
118
+
119
+ # Public: remove a named List.
120
+ #
121
+ # name - the String name of the List.
122
+ #
123
+ # Example
124
+ #
125
+ # Commands.delete_list("snippets")
126
+ #
127
+ # Returns nothing.
128
+ def list_delete(name)
129
+ lists = storage.lists.reverse.reject { |list| list.name == name }
130
+ output "You sure you want to delete everything in \"#{name}\"? (y/n):"
131
+ if $stdin.gets.chomp == 'y'
132
+ storage.lists = lists
133
+ output "Boom! Deleted all your #{name}."
134
+ save!
135
+ else
136
+ output "Just kidding then."
137
+ end
138
+ end
139
+
140
+ # Public: add a new Item to a list.
141
+ #
142
+ # list - the String name of the List to associate with this Item
143
+ # name - the String name of the Item
144
+ # value - the String value of the Item
145
+ #
146
+ # Example
147
+ #
148
+ # Commands.add_item("snippets","sig","- @holman")
149
+ #
150
+ # Returns the newly created Item.
151
+ def add_item(list,name,value)
152
+ list = storage.lists.find{|storage_list| storage_list.name == list}
153
+ list.add_item(Item.new(name,value))
154
+ output "Boom! \"#{name}\" in \"#{list.name}\" is \"#{value}\". Got it."
155
+ save!
156
+ end
157
+
158
+ # Public: remove a named Item.
159
+ #
160
+ # name - the String name of the Item.
161
+ #
162
+ # Example
163
+ #
164
+ # Commands.delete_item("an-item-name")
165
+ #
166
+ # Returns nothing.
167
+ def item_delete(name)
168
+ storage.lists = storage.lists.each do |list|
169
+ list.items.reject! { |item| item.name == name }
170
+ end
171
+ output "Boom! \"#{name}\" is gone forever."
172
+ save!
173
+ end
174
+
175
+ # Public: search for an Item in all lists by name. Drops the
176
+ # corresponding entry into your clipboard.
177
+ #
178
+ # name - the String term to search for in all Item names
179
+ #
180
+ # Returns the matching Item.
181
+ def search_items(name)
182
+ item = storage.items.detect do |item|
183
+ item.name == name
184
+ end
185
+
186
+ output Clipboard.copy(item)
187
+ end
188
+
189
+ # Public: search for an Item in a particular list by name. Drops the
190
+ # corresponding entry into your clipboard.
191
+ #
192
+ # list_name - the String name of the List in which to scope the search
193
+ # item_name - the String term to search for in all Item names
194
+ #
195
+ # Returns the matching Item.
196
+ def search_list_for_item(list_name, item_name)
197
+ list = storage.lists.first { |list| list.name == list_name }
198
+ item = list.items.first { |item| item.name == item_name }
199
+
200
+ output Clipboard.copy(item)
201
+ end
202
+
203
+ # Public: save in-memory data to disk.
204
+ #
205
+ # Returns whether or not data was saved.
206
+ def save!
207
+ storage.save!
208
+ end
209
+
210
+ end
211
+ end
212
+ end
data/lib/boom/item.rb ADDED
@@ -0,0 +1,37 @@
1
+ # The representation of the base unit in boom. An Item contains just a name and
2
+ # a value. It doesn't know its parent relationship explicitly; the parent List
3
+ # object instead knows which Items it contains.
4
+ #
5
+ module Boom
6
+ class Item
7
+
8
+ # Public: the String name of the Item
9
+ attr_accessor :name
10
+
11
+ # Public: the String value of the Item
12
+ attr_accessor :value
13
+
14
+ # Public: creates a new Item object.
15
+ #
16
+ # name - the String name of the Item
17
+ # value - the String value of the Item
18
+ #
19
+ # Examples
20
+ #
21
+ # Item.new("github", "http://github.com")
22
+ #
23
+ # Returns the newly initialized Item.
24
+ def initialize(name,value)
25
+ @name = name
26
+ @value = value
27
+ end
28
+
29
+ # Public: creates a Hash for this Item.
30
+ #
31
+ # Returns a Hash of its data.
32
+ def to_hash
33
+ { @name => @value }
34
+ end
35
+
36
+ end
37
+ end
data/lib/boom/list.rb ADDED
@@ -0,0 +1,44 @@
1
+ # The List contains many Items. They exist as buckets in which to categorize
2
+ # individual Items. The relationship is maintained in a simple array on the
3
+ # List-level.
4
+ #
5
+ module Boom class List
6
+
7
+ # Public: creates a new List instance in-memory.
8
+ #
9
+ # name - The name of the List. Fails if already used.
10
+ #
11
+ # Returns the unpersisted List instance.
12
+ def initialize(name)
13
+ @items = []
14
+ @name = name
15
+ end
16
+
17
+ # Public: lets you access the array of items contained within this List.
18
+ #
19
+ # Returns an Array of Items.
20
+ attr_accessor :items
21
+
22
+ # Public: the name of the List.
23
+ #
24
+ # Returns the String name.
25
+ attr_accessor :name
26
+
27
+ # Public: associates an Item with this List.
28
+ #
29
+ # item - the Item object to associate with this List.
30
+ #
31
+ # Returns the current set of items.
32
+ def add_item(item)
33
+ @items << item
34
+ end
35
+
36
+ # Public: a Hash representation of this List.
37
+ #
38
+ # Returns a Hash of its own data and its child Items.
39
+ def to_hash
40
+ { name => items.collect(&:to_hash) }
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,125 @@
1
+ # Storage is the middleman between changes the client makes in-memory and how
2
+ # it's actually persisted to disk (and vice-versa). There are also a few
3
+ # convenience methods to run searches and operations on the in-memory hash.
4
+ #
5
+ module Boom
6
+ class Storage
7
+
8
+ JSON_FILE = "#{ENV['HOME']}/.boom"
9
+
10
+ # Public: the path to the JSON file used by boom.
11
+ #
12
+ # Returns the String path of boom's JSON representation.
13
+ def json_file
14
+ JSON_FILE
15
+ end
16
+
17
+ # Public: initializes a Storage instance by loading in your persisted data.
18
+ #
19
+ # Returns the Storage instance.
20
+ def initialize
21
+ @lists = []
22
+ explode_json(json_file)
23
+ end
24
+
25
+ # Public: the in-memory collection of all Lists attached to this Storage
26
+ # instance.
27
+ #
28
+ # lists - an Array of individual List items
29
+ #
30
+ # Returns nothing.
31
+ attr_writer :lists
32
+
33
+ # Public: the list of Lists in your JSON data, sorted by number of items
34
+ # descending.
35
+ #
36
+ # Returns an Array of List objects.
37
+ def lists
38
+ @lists.sort_by { |list| -list.items.size }
39
+ end
40
+
41
+ # Public: tests whether a named List exists.
42
+ #
43
+ # name - the String name of a List
44
+ #
45
+ # Returns true if found, false if not.
46
+ def list_exists?(name)
47
+ @lists.detect { |list| list.name == name }
48
+ end
49
+
50
+ # Public: all Items in storage.
51
+ #
52
+ # Returns an Array of all Items.
53
+ def items
54
+ @lists.collect(&:items).flatten
55
+ end
56
+
57
+ # Public: tests whether a named Item exists.
58
+ #
59
+ # name - the String name of an Item
60
+ #
61
+ # Returns true if found, false if not.
62
+ def item_exists?(name)
63
+ items.detect { |item| item.name == name }
64
+ end
65
+
66
+ # Public: persists your in-memory objects to disk in JSON format.
67
+ #
68
+ # Returns true if successful, false if unsuccessful.
69
+ def save!
70
+ File.open(json_file, 'w') {|f| f.write(to_json) }
71
+ end
72
+
73
+ # Public: the JSON representation of the current List and Item assortment
74
+ # attached to the Storage instance.
75
+ #
76
+ # Returns a String JSON representation of its Lists and their Items.
77
+ def to_json
78
+ Yajl::Encoder.encode(to_hash)
79
+ end
80
+
81
+ # Public: creates a Hash of the representation of the in-memory data
82
+ # structure. This percolates down to Items by calling to_hash on the List,
83
+ # which in tern calls to_hash on individual Items.
84
+ #
85
+ # Returns a Hash of the entire data set.
86
+ def to_hash
87
+ { :lists => lists.collect(&:to_hash) }
88
+ end
89
+
90
+
91
+ # INTERNAL METHODS ##########################################################
92
+
93
+ # Take a JSON representation of data and explode it out into the consituent
94
+ # Lists and Items for the given Storage instance.
95
+ #
96
+ # Returns nothing.
97
+ def explode_json(json)
98
+ bootstrap_json unless File.exist?(json)
99
+
100
+ storage = Yajl::Parser.new.parse(File.new(json, 'r'))
101
+
102
+ storage['lists'].each do |lists|
103
+ lists.each do |list_name, items|
104
+ @lists << list = List.new(list_name)
105
+
106
+ items.each do |item|
107
+ item.each do |name,value|
108
+ list.add_item(Item.new(name,value))
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Takes care of bootstrapping the JSON file, both in terms of creating the
116
+ # file and in terms of creating a skeleton JSON schema.
117
+ #
118
+ # Return true if successfully saved.
119
+ def bootstrap_json
120
+ FileUtils.touch json_file
121
+ save!
122
+ end
123
+
124
+ end
125
+ end
data/lib/boom.rb ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue LoadError
4
+ end
5
+
6
+ require 'fileutils'
7
+ require 'yajl'
8
+
9
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
10
+
11
+ require 'boom/clipboard'
12
+ require 'boom/command'
13
+ require 'boom/item'
14
+ require 'boom/list'
15
+ require 'boom/storage'
16
+
17
+ module Boom
18
+ VERSION = '0.0.1'
19
+ end
@@ -0,0 +1 @@
1
+ {"lists":[{"urls":[{"github":"https://github.com"},{"blog":"http://zachholman.com"}]}]}
data/test/helper.rb ADDED
@@ -0,0 +1,33 @@
1
+ require 'test/unit'
2
+
3
+ begin
4
+ require 'rubygems'
5
+ require 'redgreen'
6
+ rescue LoadError
7
+ end
8
+
9
+ require 'mocha'
10
+
11
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+
14
+ require 'boom'
15
+
16
+ # test/spec/mini 3
17
+ # http://gist.github.com/25455
18
+ # chris@ozmm.org
19
+ # file:lib/test/spec/mini.rb
20
+ def context(*args, &block)
21
+ return super unless (name = args.first) && block
22
+ require 'test/unit'
23
+ klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
24
+ def self.test(name, &block)
25
+ define_method("test_#{name.gsub(/\W/,'_')}", &block) if block
26
+ end
27
+ def self.xtest(*args) end
28
+ def self.setup(&block) define_method(:setup, &block) end
29
+ def self.teardown(&block) define_method(:teardown, &block) end
30
+ end
31
+ (class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
32
+ klass.class_eval &block
33
+ end
@@ -0,0 +1,85 @@
1
+ require 'helper'
2
+
3
+ # Intercept STDOUT and collect it
4
+ class Boom::Command
5
+
6
+ def self.capture_output
7
+ @output = ''
8
+ end
9
+
10
+ def self.captured_output
11
+ @output
12
+ end
13
+
14
+ def self.output(s)
15
+ @output << s
16
+ end
17
+
18
+ def self.save!
19
+ # no-op
20
+ end
21
+
22
+ end
23
+
24
+ class TestCommand < Test::Unit::TestCase
25
+
26
+ def setup
27
+ Boom::Storage.any_instance.stubs(:json_file).
28
+ returns('test/examples/urls.json')
29
+ @storage = Boom::Storage.new
30
+ end
31
+
32
+ def command(cmd)
33
+ cmd = cmd.split(' ') if cmd
34
+ Boom::Command.capture_output
35
+ Boom::Command.execute(@storage,*cmd)
36
+ Boom::Command.captured_output
37
+ end
38
+
39
+ def test_overview
40
+ assert_equal ' urls (2)', command(nil)
41
+ end
42
+
43
+ def test_list_detail
44
+ assert_match /github/, command('urls')
45
+ end
46
+
47
+ def test_list_all
48
+ cmd = command('all')
49
+ assert_match /urls/, cmd
50
+ assert_match /github/, cmd
51
+ end
52
+
53
+ def test_list_creation
54
+ assert_match /a new list called "newlist"/, command('newlist')
55
+ end
56
+
57
+ def test_item_access
58
+ assert_match /copied https:\/\/github\.com to your clipboard/,
59
+ command('github')
60
+ end
61
+
62
+ def test_item_access_scoped_by_list
63
+ assert_match /copied https:\/\/github\.com to your clipboard/,
64
+ command('urls github')
65
+ end
66
+
67
+ def test_item_creation
68
+ assert_match /"twitter" in "urls"/,
69
+ command('urls twitter http://twitter.com/holman')
70
+ end
71
+
72
+ def test_list_deletion_no
73
+ STDIN.stubs(:gets).returns('n')
74
+ assert_match /Just kidding then/, command('urls delete')
75
+ end
76
+
77
+ def test_list_deletion_yes
78
+ STDIN.stubs(:gets).returns('y')
79
+ assert_match /Deleted all your urls/, command('urls delete')
80
+ end
81
+
82
+ def test_item_deletion
83
+ assert_match /"github" is gone forever/, command('urls github delete')
84
+ end
85
+ end
data/test/test_item.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'helper'
2
+
3
+ class TestItem < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @item = Boom::Item.new('github','https://github.com')
7
+ end
8
+
9
+ def test_name
10
+ assert_equal 'github', @item.name
11
+ end
12
+
13
+ def test_value
14
+ assert_equal 'https://github.com', @item.value
15
+ end
16
+
17
+ def test_to_hash
18
+ assert_equal 1, @item.to_hash.size
19
+ end
20
+
21
+ end
data/test/test_list.rb ADDED
@@ -0,0 +1,26 @@
1
+ require 'helper'
2
+
3
+ class TestList < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @list = Boom::List.new('urls')
7
+ @item = Boom::Item.new('github','https://github.com')
8
+ end
9
+
10
+ def test_name
11
+ assert_equal 'urls', @list.name
12
+ end
13
+
14
+ def test_add_items
15
+ assert_equal 0, @list.items.size
16
+ @list.add_item(@item)
17
+ assert_equal 1, @list.items.size
18
+ end
19
+
20
+ def test_to_hash
21
+ assert_equal 0, @list.to_hash[@list.name].size
22
+ @list.add_item(@item)
23
+ assert_equal 1, @list.to_hash[@list.name].size
24
+ end
25
+
26
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: boom
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Zach Holman
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-24 00:00:00 -08:00
19
+ default_executable: boom
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: yajl-ruby
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 19
30
+ segments:
31
+ - 0
32
+ - 7
33
+ - 8
34
+ version: 0.7.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: mocha
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ hash: 41
46
+ segments:
47
+ - 0
48
+ - 9
49
+ - 9
50
+ version: 0.9.9
51
+ type: :development
52
+ version_requirements: *id002
53
+ description: |-
54
+ God it's about every day where I think to myself, gadzooks,
55
+ I keep typing *REPETITIVE_BORING_TASK* over and over. Wouldn't it be great if
56
+ I had something like boom to store all these commonly-used text snippets for
57
+ me? Then I realized that was a worthless idea since boom hadn't been created
58
+ yet and I had no idea what that statement meant. At some point I found the
59
+ code for boom in a dark alleyway and released it under my own name because I
60
+ wanted to look smart.
61
+ email: github.com@zachholman.com
62
+ executables:
63
+ - boom
64
+ extensions: []
65
+
66
+ extra_rdoc_files:
67
+ - Readme.markdown
68
+ - License
69
+ files:
70
+ - License
71
+ - Rakefile
72
+ - Readme.markdown
73
+ - bin/boom
74
+ - boom.gemspec
75
+ - lib/boom.rb
76
+ - lib/boom/clipboard.rb
77
+ - lib/boom/command.rb
78
+ - lib/boom/item.rb
79
+ - lib/boom/list.rb
80
+ - lib/boom/storage.rb
81
+ - test/examples/urls.json
82
+ - test/helper.rb
83
+ - test/test_command.rb
84
+ - test/test_item.rb
85
+ - test/test_list.rb
86
+ has_rdoc: true
87
+ homepage: https://github.com/holman/boom
88
+ licenses: []
89
+
90
+ post_install_message:
91
+ rdoc_options:
92
+ - --charset=UTF-8
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ hash: 3
110
+ segments:
111
+ - 0
112
+ version: "0"
113
+ requirements: []
114
+
115
+ rubyforge_project: boom
116
+ rubygems_version: 1.3.7
117
+ signing_key:
118
+ specification_version: 2
119
+ summary: boom lets you access text snippets over your command line.
120
+ test_files:
121
+ - test/test_command.rb
122
+ - test/test_item.rb
123
+ - test/test_list.rb