kaboom 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.markdown +107 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +24 -0
- data/LICENSE.markdown +21 -0
- data/README.markdown +74 -0
- data/Rakefile +150 -0
- data/bin/boom +8 -0
- data/bin/kaboom +8 -0
- data/completion/README.md +7 -0
- data/completion/boom.bash +17 -0
- data/completion/boom.zsh +29 -0
- data/kaboom.gemspec +117 -0
- data/lib/kaboom.rb +59 -0
- data/lib/kaboom/color.rb +52 -0
- data/lib/kaboom/command.rb +389 -0
- data/lib/kaboom/config.rb +116 -0
- data/lib/kaboom/core_ext/symbol.rb +7 -0
- data/lib/kaboom/item.rb +72 -0
- data/lib/kaboom/list.rb +100 -0
- data/lib/kaboom/output.rb +13 -0
- data/lib/kaboom/platform.rb +103 -0
- data/lib/kaboom/remote.rb +47 -0
- data/lib/kaboom/storage.rb +22 -0
- data/lib/kaboom/storage/base.rb +91 -0
- data/lib/kaboom/storage/gist.rb +125 -0
- data/lib/kaboom/storage/json.rb +76 -0
- data/lib/kaboom/storage/keychain.rb +135 -0
- data/lib/kaboom/storage/mongodb.rb +96 -0
- data/lib/kaboom/storage/redis.rb +79 -0
- data/test/examples/config_json.json +3 -0
- data/test/examples/test_json.json +3 -0
- data/test/examples/urls.json +1 -0
- data/test/helper.rb +25 -0
- data/test/output_interceptor.rb +28 -0
- data/test/test_color.rb +30 -0
- data/test/test_command.rb +227 -0
- data/test/test_config.rb +27 -0
- data/test/test_item.rb +54 -0
- data/test/test_list.rb +79 -0
- data/test/test_platform.rb +52 -0
- data/test/test_remote.rb +30 -0
- metadata +151 -0
@@ -0,0 +1,116 @@
|
|
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
|
+
REMOTE_FILE = FILE.gsub(/\.conf$/, ".remote.conf")
|
22
|
+
|
23
|
+
#if set to true then we will use a different config file for storage
|
24
|
+
#engine
|
25
|
+
attr_accessor :remote
|
26
|
+
|
27
|
+
# Public: The attributes Hash for configuration options. The attributes
|
28
|
+
# needed are dictated by each backend, but the `backend` option must be
|
29
|
+
# present.
|
30
|
+
attr_reader :attributes
|
31
|
+
|
32
|
+
|
33
|
+
# Public: an alias for accessing Boom.config.attributes[key]
|
34
|
+
# like Boom.config[key] instead
|
35
|
+
#
|
36
|
+
# Returns the value in the attributes hash
|
37
|
+
def [] key
|
38
|
+
attributes[key]
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public: creates a new instance of Config.
|
42
|
+
#
|
43
|
+
# This will load the attributes from boom's config file, or bootstrap it
|
44
|
+
# if this is a new install. Bootstrapping defaults to the JSON backend.
|
45
|
+
#
|
46
|
+
# Returns nothing.
|
47
|
+
def initialize remote=false
|
48
|
+
@remote = remote
|
49
|
+
bootstrap unless File.exist?(file)
|
50
|
+
load_attributes
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: accessor for the configuration file.
|
54
|
+
#
|
55
|
+
# Returns the String file path.
|
56
|
+
def file
|
57
|
+
remote ? REMOTE_FILE : FILE
|
58
|
+
end
|
59
|
+
|
60
|
+
# Public: saves an empty, barebones hash to @attributes for the purpose of
|
61
|
+
# new user setup.
|
62
|
+
#
|
63
|
+
# Returns whether the attributes were saved.
|
64
|
+
def bootstrap
|
65
|
+
@attributes = {
|
66
|
+
:backend => 'json'
|
67
|
+
}
|
68
|
+
save
|
69
|
+
end
|
70
|
+
|
71
|
+
# Public: assigns a hash to the configuration attributes object. The
|
72
|
+
# contents of the attributes hash depends on what the backend needs. A
|
73
|
+
# `backend` key MUST be present, however.
|
74
|
+
#
|
75
|
+
# attrs - the Hash representation of attributes to persist to disk.
|
76
|
+
#
|
77
|
+
# Examples
|
78
|
+
#
|
79
|
+
# config.attributes = {"backend" => "json"}
|
80
|
+
#
|
81
|
+
# Returns whether the attributes were saved.
|
82
|
+
def attributes=(attrs)
|
83
|
+
@attributes = attrs
|
84
|
+
save
|
85
|
+
end
|
86
|
+
|
87
|
+
# Public: loads and parses the JSON tree from disk into memory and stores
|
88
|
+
# it in the attributes Hash.
|
89
|
+
#
|
90
|
+
# Returns nothing.
|
91
|
+
def load_attributes
|
92
|
+
@attributes = MultiJson.decode(File.new(file, 'r').read)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: writes the in-memory JSON Hash to disk.
|
96
|
+
#
|
97
|
+
# Returns nothing.
|
98
|
+
def save
|
99
|
+
json = MultiJson.encode(attributes)
|
100
|
+
File.open(file, 'w') {|f| f.write(json) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def invalid_message
|
104
|
+
%(#{red "Is your config correct? You said:"}
|
105
|
+
|
106
|
+
#{File.read Boom.config.file}
|
107
|
+
|
108
|
+
#{cyan "Our survey says:"}
|
109
|
+
|
110
|
+
#{self.class.sample_config}
|
111
|
+
|
112
|
+
#{yellow "Go edit "} #{Boom.config.file + yellow(" and make it all better") }
|
113
|
+
).gsub(/^ {8}/, '') # strip the first eight spaces of every line
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
data/lib/kaboom/item.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# The representation of the base unit in boom. An Item contains just a name and
|
4
|
+
# a value. It doesn't know its parent relationship explicitly; the parent List
|
5
|
+
# object instead knows which Items it contains.
|
6
|
+
#
|
7
|
+
module Boom
|
8
|
+
class Item
|
9
|
+
|
10
|
+
# Public: the String name of the Item
|
11
|
+
attr_accessor :name
|
12
|
+
|
13
|
+
# Public: the String value of the Item
|
14
|
+
attr_accessor :value
|
15
|
+
|
16
|
+
# Public: creates a new Item object.
|
17
|
+
#
|
18
|
+
# name - the String name of the Item
|
19
|
+
# value - the String value of the Item
|
20
|
+
#
|
21
|
+
# Examples
|
22
|
+
#
|
23
|
+
# Item.new("github", "https://github.com")
|
24
|
+
#
|
25
|
+
# Returns the newly initialized Item.
|
26
|
+
def initialize(name,value)
|
27
|
+
@name = name
|
28
|
+
@value = value
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: the shortened String name of the Item. Truncates with ellipses if
|
32
|
+
# larger.
|
33
|
+
#
|
34
|
+
# Examples
|
35
|
+
#
|
36
|
+
# item = Item.new("github's home page","https://github.com")
|
37
|
+
# item.short_name
|
38
|
+
# # => 'github's home p…'
|
39
|
+
#
|
40
|
+
# item = Item.new("github","https://github.com")
|
41
|
+
# item.short_name
|
42
|
+
# # => 'github'
|
43
|
+
#
|
44
|
+
# Returns the shortened name.
|
45
|
+
def short_name
|
46
|
+
name.length > 15 ? "#{name[0..14]}…" : name[0..14]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Public: the amount of consistent spaces to pad based on Item#short_name.
|
50
|
+
#
|
51
|
+
# Returns a String of spaces.
|
52
|
+
def spacer
|
53
|
+
name.length > 15 ? '' : ' '*(15-name.length+1)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: only return url part of value - if no url has been
|
57
|
+
# detected returns value.
|
58
|
+
#
|
59
|
+
# Returns a String which preferably is a URL.
|
60
|
+
def url
|
61
|
+
@url ||= value.split(/\s+/).detect { |v| v =~ %r{\A[a-z0-9]+:\S+}i } || value
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: creates a Hash for this Item.
|
65
|
+
#
|
66
|
+
# Returns a Hash of its data.
|
67
|
+
def to_hash
|
68
|
+
{ @name => @value }
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
data/lib/kaboom/list.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# The List contains many Items. They exist as buckets in which to categorize
|
4
|
+
# individual Items. The relationship is maintained in a simple array on the
|
5
|
+
# List-level.
|
6
|
+
#
|
7
|
+
module Boom
|
8
|
+
class List
|
9
|
+
|
10
|
+
# Public: creates a new List instance in-memory.
|
11
|
+
#
|
12
|
+
# name - The name of the List. Fails if already used.
|
13
|
+
#
|
14
|
+
# Returns the unpersisted List instance.
|
15
|
+
def initialize(name)
|
16
|
+
@items = []
|
17
|
+
@name = name
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: accesses the in-memory JSON representation.
|
21
|
+
#
|
22
|
+
# Returns a Storage instance.
|
23
|
+
def self.storage
|
24
|
+
Boom.storage
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: lets you access the array of items contained within this List.
|
28
|
+
#
|
29
|
+
# Returns an Array of Items.
|
30
|
+
attr_accessor :items
|
31
|
+
|
32
|
+
# Public: the name of the List.
|
33
|
+
#
|
34
|
+
# Returns the String name.
|
35
|
+
attr_accessor :name
|
36
|
+
|
37
|
+
# Public: associates an Item with this List. If the item name is already
|
38
|
+
# defined, then the value will be replaced
|
39
|
+
#
|
40
|
+
# item - the Item object to associate with this List.
|
41
|
+
#
|
42
|
+
# Returns the current set of items.
|
43
|
+
def add_item(item)
|
44
|
+
delete_item(item.name) if find_item(item.name)
|
45
|
+
@items << item
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: finds any given List by name.
|
49
|
+
#
|
50
|
+
# name - String name of the list to search for
|
51
|
+
#
|
52
|
+
# Returns the first instance of List that it finds.
|
53
|
+
def self.find(name)
|
54
|
+
storage.lists.find { |list| list.name == name }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Public: deletes a List by name.
|
58
|
+
#
|
59
|
+
# name - String name of the list to delete
|
60
|
+
#
|
61
|
+
# Returns whether one or more lists were removed.
|
62
|
+
def self.delete(name)
|
63
|
+
previous = storage.lists.size
|
64
|
+
storage.lists = storage.lists.reject { |list| list.name == name }
|
65
|
+
previous != storage.lists.size
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: deletes an Item by name.
|
69
|
+
#
|
70
|
+
# name - String name of the item to delete
|
71
|
+
#
|
72
|
+
# Returns whether an item was removed.
|
73
|
+
def delete_item(name)
|
74
|
+
previous = items.size
|
75
|
+
items.reject! { |item| item.name == name}
|
76
|
+
previous != items.size
|
77
|
+
end
|
78
|
+
|
79
|
+
# Public: finds an Item by name. If the name is typically truncated, also
|
80
|
+
# allow a search based on that truncated name.
|
81
|
+
#
|
82
|
+
# name - String name of the Item to find
|
83
|
+
#
|
84
|
+
# Returns the found item
|
85
|
+
def find_item(name)
|
86
|
+
items.find do |item|
|
87
|
+
item.name == name ||
|
88
|
+
item.short_name.gsub('…','') == name.gsub('…','')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Public: a Hash representation of this List.
|
93
|
+
#
|
94
|
+
# Returns a Hash of its own data and its child Items.
|
95
|
+
def to_hash
|
96
|
+
{ name => items.collect(&:to_hash) }
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Boom
|
2
|
+
module Output
|
3
|
+
# Public: prints any given string.
|
4
|
+
#
|
5
|
+
# s = String output
|
6
|
+
#
|
7
|
+
# Prints to STDOUT and returns. This method exists to standardize output
|
8
|
+
# and for easy mocking or overriding.
|
9
|
+
def output(s)
|
10
|
+
puts(s)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
# Platform is a centralized point to shell out platform specific functionality
|
4
|
+
# like clipboard access or commands to open URLs.
|
5
|
+
#
|
6
|
+
#
|
7
|
+
# Clipboard is a centralized point to shell out to each individual platform's
|
8
|
+
# clipboard, pasteboard, or whatever they decide to call it.
|
9
|
+
#
|
10
|
+
module Boom
|
11
|
+
class Platform
|
12
|
+
class << self
|
13
|
+
# Public: tests if currently running on darwin.
|
14
|
+
#
|
15
|
+
# Returns true if running on darwin (MacOS X), else false
|
16
|
+
def darwin?
|
17
|
+
!!(RUBY_PLATFORM =~ /darwin/)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: tests if currently running on windows.
|
21
|
+
#
|
22
|
+
# Apparently Windows RUBY_PLATFORM can be 'win32' or 'mingw32'
|
23
|
+
#
|
24
|
+
# Returns true if running on windows (win32/mingw32), else false
|
25
|
+
def windows?
|
26
|
+
!!(RUBY_PLATFORM =~ /mswin|mingw/)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Public: returns the command used to open a file or URL
|
30
|
+
# for the current platform.
|
31
|
+
#
|
32
|
+
# Currently only supports MacOS X and Linux with `xdg-open`.
|
33
|
+
#
|
34
|
+
# Returns a String with the bin
|
35
|
+
def open_command
|
36
|
+
if darwin?
|
37
|
+
'open'
|
38
|
+
elsif windows?
|
39
|
+
'start'
|
40
|
+
else
|
41
|
+
'xdg-open'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: opens a given Item's value in the browser. This
|
46
|
+
# method is designed to handle multiple platforms.
|
47
|
+
#
|
48
|
+
# Returns a String of the Item value.
|
49
|
+
def open(item)
|
50
|
+
unless windows?
|
51
|
+
system("#{open_command} '#{item.url.gsub("\'","'\\\\''")}'")
|
52
|
+
else
|
53
|
+
system("#{open_command} #{item.url.gsub("\'","'\\\\''")}")
|
54
|
+
end
|
55
|
+
|
56
|
+
item.value
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: returns the command used to copy a given Item's value to the
|
60
|
+
# clipboard for the current platform.
|
61
|
+
#
|
62
|
+
# Returns a String with the bin
|
63
|
+
def copy_command
|
64
|
+
if darwin?
|
65
|
+
'pbcopy'
|
66
|
+
elsif windows?
|
67
|
+
'clip'
|
68
|
+
else
|
69
|
+
'xclip -selection clipboard'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Public: copies a given Item's value to the clipboard. This method is
|
74
|
+
# designed to handle multiple platforms.
|
75
|
+
#
|
76
|
+
# Returns the String value of the Item.
|
77
|
+
def copy(item)
|
78
|
+
IO.popen(copy_command,"w") {|cc| cc.write(item.value)}
|
79
|
+
item.value
|
80
|
+
end
|
81
|
+
|
82
|
+
# Public: opens the JSON file in an editor for you to edit. Uses the
|
83
|
+
# $EDITOR environment variable, or %EDITOR% on Windows for editing.
|
84
|
+
# This method is designed to handle multiple platforms.
|
85
|
+
# If $EDITOR is nil, try to open using the open_command.
|
86
|
+
#
|
87
|
+
# Returns a String with a helpful message.
|
88
|
+
def edit(json_file)
|
89
|
+
unless $EDITOR.nil?
|
90
|
+
unless windows?
|
91
|
+
system("`echo $EDITOR` #{json_file} &")
|
92
|
+
else
|
93
|
+
system("start %EDITOR% #{json_file}")
|
94
|
+
end
|
95
|
+
else
|
96
|
+
system("#{open_command} #{json_file}")
|
97
|
+
end
|
98
|
+
|
99
|
+
"Make your edits, and do be sure to save."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Boom
|
2
|
+
module Remote
|
3
|
+
include Output
|
4
|
+
include Color
|
5
|
+
|
6
|
+
def allowed_storage_types
|
7
|
+
[Boom::Storage::Gist, Boom::Storage::Mongodb, Boom::Storage::Redis]
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns true if the user is using a sensible seeming storage backend
|
11
|
+
# Params storage, the current storage instance
|
12
|
+
# else exits with a warning if not
|
13
|
+
def allowed? storage
|
14
|
+
return true if Boom.local?
|
15
|
+
return true if allowed_storage_types.include? storage.class
|
16
|
+
|
17
|
+
output error_message storage
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def error_message storage
|
22
|
+
%(
|
23
|
+
#{red("<<----BOOOOOM!!!----->>>> ")} (as in explosion, rather than fast)
|
24
|
+
You probably don't want to use the #{storage.class} storage whilst
|
25
|
+
using boom in remote mode. Your config looks akin to:
|
26
|
+
|
27
|
+
#{red File.read Boom.config.file}
|
28
|
+
|
29
|
+
but things might be better with something a little more like:
|
30
|
+
|
31
|
+
#{cyan Boom::Storage::Redis.sample_config}
|
32
|
+
|
33
|
+
or
|
34
|
+
|
35
|
+
#{cyan Boom::Storage::Mongodb.sample_config}
|
36
|
+
|
37
|
+
Now head yourself on over to #{yellow Boom.config.file}
|
38
|
+
and edit some goodness into it
|
39
|
+
|
40
|
+
Meanwhile, please enjoy your ordinary boom service:
|
41
|
+
___________________________________________________
|
42
|
+
|
43
|
+
).gsub(/^ {8}/, '')
|
44
|
+
end
|
45
|
+
extend self
|
46
|
+
end
|
47
|
+
end
|