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,22 @@
1
+ # coding: utf-8
2
+
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
+ #
7
+ module Boom
8
+ module Storage
9
+
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
15
+ end
16
+
17
+ def self.backend
18
+ Boom::Storage.const_get(Boom.config.attributes['backend'].capitalize).new
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,91 @@
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
+ Boom::Remote.allowed? self
18
+ populate
19
+ end
20
+
21
+ # run bootstrap tasks for the storage
22
+ def bootstrap ; end
23
+
24
+ # populate the in-memory store with all the lists and items
25
+ def populate ; end
26
+
27
+ # save the data
28
+ def save ; end
29
+
30
+
31
+ # Public: the in-memory collection of all Lists attached to this Storage
32
+ # instance.
33
+ #
34
+ # lists - an Array of individual List items
35
+ #
36
+ # Returns nothing.
37
+ attr_writer :lists
38
+
39
+ # Public: the list of Lists in your JSON data, sorted by number of items
40
+ # descending.
41
+ #
42
+ # Returns an Array of List objects.
43
+ def lists
44
+ @lists.sort_by { |list| -list.items.size }
45
+ end
46
+
47
+ # Public: tests whether a named List exists.
48
+ #
49
+ # name - the String name of a List
50
+ #
51
+ # Returns true if found, false if not.
52
+ def list_exists?(name)
53
+ @lists.detect { |list| list.name == name }
54
+ end
55
+
56
+ # Public: all Items in storage.
57
+ #
58
+ # Returns an Array of all Items.
59
+ def items
60
+ @lists.collect(&:items).flatten
61
+ end
62
+
63
+ # Public: tests whether a named Item exists.
64
+ #
65
+ # name - the String name of an Item
66
+ #
67
+ # Returns true if found, false if not.
68
+ def item_exists?(name)
69
+ items.detect { |item| item.name == name }
70
+ end
71
+
72
+ # Public: creates a Hash of the representation of the in-memory data
73
+ # structure. This percolates down to Items by calling to_hash on the List,
74
+ # which in tern calls to_hash on individual Items.
75
+ #
76
+ # Returns a Hash of the entire data set.
77
+ def to_hash
78
+ { :lists => lists.collect(&:to_hash) }
79
+ end
80
+
81
+ def handle error, message
82
+ case error
83
+ when NoMethodError
84
+ output cyan config_text
85
+ when NameError
86
+ output message
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,125 @@
1
+ # coding: utf-8
2
+ #
3
+ # Gist backend for Boom.
4
+ #
5
+ # Your .boom.conf file should look like this:
6
+ #
7
+ # {
8
+ # "backend": "gist",
9
+ # "gist": {
10
+ # "username": "your_github_username",
11
+ # "password": "your_github_password"
12
+ # }
13
+ # }
14
+ #
15
+ # There are two optional keys which can be under "gist":
16
+ #
17
+ # gist_id - The ID of an existing Gist to use. If not
18
+ # present, a Gist will be created the first time
19
+ # Boom is run and will be persisted to the config.
20
+ # public - Makes the Gist public. An absent value or
21
+ # any value other than boolean true will make
22
+ # the Gist private.
23
+ #
24
+
25
+ module Boom
26
+ module Storage
27
+ class Gist < Base
28
+
29
+ def bootstrap
30
+ begin
31
+ require "httparty"
32
+
33
+ self.class.send(:include, HTTParty)
34
+ self.class.base_uri "https://api.github.com"
35
+ rescue LoadError
36
+ puts "The Gist backend requires HTTParty: gem install httparty"
37
+ exit
38
+ end
39
+
40
+ unless Boom.config.attributes["gist"]
41
+ puts 'A "gist" data structure must be defined in ~/.boom.conf'
42
+ exit
43
+ end
44
+
45
+ set_up_auth
46
+ find_or_create_gist
47
+ end
48
+
49
+ def self.sample_config
50
+ %({
51
+ "backend": "gist",
52
+ "gist": {
53
+ "username": "your_github_username",
54
+ "password": "your_github_password"
55
+ }
56
+ }
57
+ )
58
+ end
59
+
60
+ def populate
61
+ @storage['lists'].each do |lists|
62
+ lists.each do |list_name, items|
63
+ @lists << list = List.new(list_name)
64
+
65
+ items.each do |item|
66
+ item.each do |name,value|
67
+ list.add_item(Item.new(name,value))
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ def save
75
+ self.class.post("/gists/#{@gist_id}", request_params)
76
+ end
77
+
78
+ private
79
+
80
+ def set_up_auth
81
+ username, password = Boom.config.attributes["gist"]["username"], Boom.config.attributes["gist"]["password"]
82
+
83
+ if username and password
84
+ self.class.basic_auth(username, password)
85
+ else
86
+ puts "GitHub username and password must be defined in ~/.boom.conf"
87
+ exit
88
+ end
89
+ end
90
+
91
+ def find_or_create_gist
92
+ @gist_id = Boom.config.attributes["gist"]["gist_id"]
93
+ @public = Boom.config.attributes["gist"]["public"] == true
94
+
95
+ if @gist_id.nil? or @gist_id.empty?
96
+ response = self.class.post("/gists", request_params)
97
+ else
98
+ response = self.class.get("/gists/#{@gist_id}", request_params)
99
+ end
100
+
101
+ @storage = MultiJson.decode(response["files"]["boom.json"]["content"]) if response["files"] and response["files"]["boom.json"]
102
+
103
+ unless @storage
104
+ puts "Boom data could not be obtained"
105
+ exit
106
+ end
107
+
108
+ unless @gist_id
109
+ Boom.config.attributes["gist"]["gist_id"] = @gist_id = response["id"]
110
+ Boom.config.save
111
+ end
112
+ end
113
+
114
+ def request_params
115
+ {
116
+ :body => MultiJson.encode({
117
+ :description => "boom!",
118
+ :public => @public,
119
+ :files => { "boom.json" => { :content => MultiJson.encode(to_hash) } }
120
+ })
121
+ }
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,76 @@
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
+ include Output
10
+ include Color
11
+
12
+ JSON_FILE = "#{ENV['HOME']}/.boom"
13
+
14
+ # Public: the path to the Json file used by boom.
15
+ #
16
+ # Returns the String path of boom's Json representation.
17
+ def json_file
18
+
19
+ JSON_FILE
20
+ end
21
+
22
+ def self.sample_config
23
+ %({"backend":"json"})
24
+ end
25
+
26
+ # Takes care of bootstrapping the Json file, both in terms of creating the
27
+ # file and in terms of creating a skeleton Json schema.
28
+ #
29
+ # Return true if successfully saved.
30
+ def bootstrap
31
+ return if File.exist?(json_file)
32
+ FileUtils.touch json_file
33
+ File.open(json_file, 'w') {|f| f.write(to_json) }
34
+ save
35
+ end
36
+
37
+ # Take a Json representation of data and explode it out into the consituent
38
+ # Lists and Items for the given Storage instance.
39
+ #
40
+ # Returns nothing.
41
+ def populate
42
+ storage = MultiJson.decode(File.new(json_file, 'r').read)
43
+
44
+ storage['lists'].each do |lists|
45
+ lists.each do |list_name, items|
46
+ @lists << list = List.new(list_name)
47
+
48
+ items.each do |item|
49
+ item.each do |name,value|
50
+ list.add_item(Item.new(name,value))
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ # Public: persists your in-memory objects to disk in Json format.
58
+ #
59
+ # lists_Json - list in Json format
60
+ #
61
+ # Returns true if successful, false if unsuccessful.
62
+ def save
63
+ File.open(json_file, 'w') {|f| f.write(to_json) }
64
+ end
65
+
66
+ # Public: the Json representation of the current List and Item assortment
67
+ # attached to the Storage instance.
68
+ #
69
+ # Returns a String Json representation of its Lists and their Items.
70
+ def to_json
71
+ MultiJson.encode(to_hash)
72
+ end
73
+
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,135 @@
1
+ # coding: utf-8
2
+
3
+ # Keychain provides methods for using Mac OS X's Keychain as a storage option.
4
+ # It saves lists as Keychain files in ~/Library/Keychains with the filename
5
+ # format being: "Boom.list.mylist.keychain"
6
+ #
7
+ module Boom
8
+ module Storage
9
+ class Keychain < Base
10
+
11
+ KEYCHAIN_FORMAT = %r{Boom\.list\.(.+)\.keychain}
12
+
13
+ # Opens Keychain app when json_file is called during `boom edit`
14
+ #
15
+ # Returns nothing
16
+ def open_keychain_app
17
+ `open /Applications/Utilities/'Keychain Access.app' &`
18
+ end
19
+
20
+ alias_method :json_file, :open_keychain_app
21
+
22
+ # Boostraps Keychain by checking if you're using a Mac which is a prereq
23
+ #
24
+ # Returns
25
+ def bootstrap
26
+ raise RuntimeError unless is_mac?
27
+ rescue
28
+ puts('No Keychain utility to access, maybe try another storage option?')
29
+ exit
30
+ end
31
+
32
+ # Asks if you're using Mac OS X
33
+ #
34
+ # Returns true on a Mac
35
+ def is_mac?
36
+ return Boom::Platform.darwin?
37
+ end
38
+
39
+ # Populate the in-memory store with all the lists and items from Keychain
40
+ #
41
+ # Returns Array of keychain names, i.e. ["Boom.list.mylist.keychain"]
42
+ def populate
43
+ stored_keychain_lists.each do |keychain|
44
+ @lists << list = List.new(keychain.scan(KEYCHAIN_FORMAT).flatten.first)
45
+ extract_keychain_items(keychain).each do |name|
46
+ list.add_item(Item.new(name, extract_keychain_value(name, keychain)))
47
+ end
48
+ end
49
+ end
50
+
51
+ # Saves the data from memory to the correct Keychain
52
+ #
53
+ # Returns nothing
54
+ def save
55
+ @lists.each do |list|
56
+ keychain_name = list_to_filename(list.name)
57
+ create_keychain_list(keychain_name) unless stored_keychain_lists.include?(keychain_name)
58
+ unless list.items.empty?
59
+ list.items.each do |item|
60
+ store_item(item, keychain_name)
61
+ end
62
+ end
63
+ delete_unwanted_items(list)
64
+ end
65
+ delete_unwanted_lists
66
+ rescue RuntimeError
67
+ puts(e "Couldn't save to your keychain, check Console.app or above for relevant messages")
68
+ end
69
+
70
+
71
+ private
72
+
73
+ # Returns an Array of keychains stored in ~/Library/Keychains:
74
+ # => ["Boom.list.mylist.keychain"]
75
+ def stored_keychain_lists
76
+ @stored_keychain_lists ||= `security -q list-keychains |grep Boom.list` \
77
+ .split(/[\/\n\"]/).select {|kc| kc =~ KEYCHAIN_FORMAT}
78
+ end
79
+
80
+ # Create the keychain list "Boom.list.mylist.keychain" in ~/Library/Keychains
81
+ def create_keychain_list(keychain_name)
82
+ `security -q create-keychain #{keychain_name}`
83
+ end
84
+
85
+ # Saves the individual item's value to the right list/keychain
86
+ def store_item(item, keychain_name)
87
+ `security 2>/dev/null -q add-generic-password -a '#{item.name}' -s '#{item.name}' -w '#{item.value}' #{keychain_name}`
88
+ end
89
+
90
+ # Retrieves the value of a particular item in a list
91
+ def extract_keychain_value(item_name, keychain)
92
+ `security 2>&1 >/dev/null find-generic-password -ga '#{item_name}' #{keychain}`.chomp.split('"').last
93
+ end
94
+
95
+ # Gets all items in a particular list
96
+ def extract_keychain_items(keychain_name)
97
+ @stored_items ||= {}
98
+ @stored_items[keychain_name] ||= `security dump-keychain -a #{keychain_name} |grep acct` \
99
+ .split(/\s|\\n|\\"|acct|<blob>=|\"/).reject {|f| f.empty?}
100
+ end
101
+
102
+ # Converts list name to the corresponding keychain filename format based
103
+ # on the KEYCHAIN_FORMAT
104
+ def list_to_filename(list_name)
105
+ KEYCHAIN_FORMAT.source.gsub(/\(\.\+\)/, list_name).gsub('\\','')
106
+ end
107
+
108
+ # Delete's a keychain file
109
+ def delete_list(keychain_filename)
110
+ `security delete-keychain #{keychain_filename}`
111
+ end
112
+
113
+ # Delete's all keychain files you don't want anymore
114
+ def delete_unwanted_lists
115
+ (stored_keychain_lists - @lists.map {|list| list_to_filename(list.name)}).each do |filename|
116
+ delete_list(filename)
117
+ end
118
+ end
119
+
120
+ # Removes unwanted items in a list
121
+ # security util doesn't have a delete password option so we'll have to
122
+ # drop it and recreate it with what is in memory
123
+ def delete_unwanted_items(list)
124
+ filename = list_to_filename(list.name)
125
+ if (list.items.size < extract_keychain_items(filename).size)
126
+ delete_list(filename)
127
+ create_keychain_list(filename)
128
+ list.items.each do |item|
129
+ store_item(item, filename)
130
+ end unless list.items.empty?
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end