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.
- 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
|