kaboom 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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