henchman-sync 0.1.0 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +8 -4
- data/lib/applescript.rb +139 -30
- data/lib/core.rb +90 -53
- data/lib/dropbox.rb +5 -1
- data/lib/henchman/version.rb +1 -1
- data/lib/launchd_handler.rb +1 -1
- data/lib/templates.rb +19 -12
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83e05f1424105e5e73efd0efcd56291a7167d002
|
4
|
+
data.tar.gz: 8f5047f31c81304402b96a91563b8ac0614c0b59
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a69d6220254d81047b7826d8c318035e67de8e8f2d7e0aff3cdf37cfc3b38a2db60322a5fffb27e2388399455cde7394e6984fa971fa51e6ef74f4ebd00e0487
|
7
|
+
data.tar.gz: 46772a0505241b492ea0e3e4e4a49225acca0f46cbe63c895f57cc92352db3c2a962b210c1cc0995a9359c32ab95286db671e302205f545517566096d65021b5
|
data/README.md
CHANGED
@@ -6,11 +6,9 @@ When you (single) click on a track in iTunes the application will check if that
|
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
9
|
-
|
9
|
+
Install it yourself as:
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
$ gem install henchman
|
11
|
+
$ gem install henchman-sync
|
14
12
|
|
15
13
|
## Usage
|
16
14
|
|
@@ -36,6 +34,12 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
36
34
|
|
37
35
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
38
36
|
|
37
|
+
## TODO
|
38
|
+
|
39
|
+
- Add job to cleanup tracks that haven't been in X number of minutes
|
40
|
+
- Figure out how to download playlist with a "'" in them
|
41
|
+
- Move off of the 'dropbox-sdk' gem, since it uses their API V1, which will be depreciated June 28th, 2017 (or just buck up and update the gem myself)
|
42
|
+
|
39
43
|
## Contributing
|
40
44
|
|
41
45
|
Bug reports and pull requests are welcome on GitHub at https://github.com/gremerritt/henchman.
|
data/lib/applescript.rb
CHANGED
@@ -13,19 +13,16 @@ module Henchman
|
|
13
13
|
"end tell"
|
14
14
|
end
|
15
15
|
|
16
|
-
def prompt_script
|
16
|
+
def prompt_script prompt
|
17
17
|
"tell application \"iTunes\"\n"\
|
18
|
-
" display dialog \"Fetch?\"\n"\
|
18
|
+
" display dialog \"Fetch #{prompt}?\"\n"\
|
19
19
|
"end tell"
|
20
20
|
end
|
21
21
|
|
22
|
-
def update_track_location_script
|
22
|
+
def update_track_location_script track_id, local_file
|
23
23
|
"tell application \"iTunes\"\n"\
|
24
24
|
" try\n"\
|
25
|
-
" set data_tracks to "\
|
26
|
-
" (every track whose artist is \"#{selection[:artist].gsub(/'/){ %q('"'"') }}\" and "\
|
27
|
-
" album is \"#{selection[:album].gsub(/'/){ %q('"'"') }}\" and "\
|
28
|
-
" name is \"#{selection[:track].gsub(/'/){ %q('"'"') }}\")\n"\
|
25
|
+
" set data_tracks to (every track whose database ID is \"#{track_id}\")\n"\
|
29
26
|
" if (count of data_tracks) is 1 then\n"\
|
30
27
|
" set location of (item 1 of data_tracks) to "\
|
31
28
|
" (POSIX file \"#{local_file.gsub(/'/){ %q('"'"') }}\")\n"\
|
@@ -46,10 +43,12 @@ module Henchman
|
|
46
43
|
" set data_artist to artist of selection as string\n"\
|
47
44
|
" set data_album to album of selection as string\n"\
|
48
45
|
" set data_title to name of selection as string\n"\
|
46
|
+
" set data_id to database ID of selection as string\n"\
|
49
47
|
" set data_location to POSIX path of (location of selection as string)\n"\
|
50
48
|
" set str to data_artist & \"#{@delimiter}\" & "\
|
51
49
|
" data_album & \"#{@delimiter}\" & "\
|
52
50
|
" data_title & \"#{@delimiter}\" & "\
|
51
|
+
" data_id & \"#{@delimiter}\" & "\
|
53
52
|
" data_location\n"\
|
54
53
|
" return str\n"\
|
55
54
|
" end if\n"\
|
@@ -62,17 +61,94 @@ module Henchman
|
|
62
61
|
def get_album_tracks_script artist, album
|
63
62
|
"tell application \"iTunes\"\n"\
|
64
63
|
" try\n"\
|
65
|
-
" set
|
64
|
+
" set album_tracks to "\
|
66
65
|
" (every track whose artist is \"#{artist}\" "\
|
67
66
|
" and album is \"#{album}\")\n"\
|
68
|
-
" set
|
69
|
-
" repeat with
|
70
|
-
" set
|
71
|
-
|
72
|
-
|
73
|
-
|
67
|
+
" set str to \"\"\n"\
|
68
|
+
" repeat with album_track in album_tracks\n"\
|
69
|
+
" set data_location to location of album_track as string\n"\
|
70
|
+
" if data_location is equal to \"missing value\" then\n"\
|
71
|
+
" set data_artist to artist of album_track as string\n"\
|
72
|
+
" set data_album to album of album_track as string\n"\
|
73
|
+
" set data_title to name of album_track as string\n"\
|
74
|
+
" set data_id to database ID of album_track as string\n"\
|
75
|
+
" set str to str & data_artist & \"#{@delimiter}\" "\
|
76
|
+
" & data_album & \"#{@delimiter}\" "\
|
77
|
+
" & data_title & \"#{@delimiter}\" "\
|
78
|
+
" & data_id & \"#{@delimiter*2}\"\n"\
|
79
|
+
" end if\n"\
|
74
80
|
" end repeat\n"\
|
75
|
-
" return
|
81
|
+
" return str\n"\
|
82
|
+
" on error\n"\
|
83
|
+
" return 0\n"\
|
84
|
+
" end try\n"\
|
85
|
+
"end tell"
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_playlist_script
|
89
|
+
"tell application \"iTunes\"\n"\
|
90
|
+
" try\n"\
|
91
|
+
" set selected_playlist to (get view of front window)\n"\
|
92
|
+
" set playlist_name to name of selected_playlist as string\n"\
|
93
|
+
" set playlist_special to special kind of selected_playlist as string\n"\
|
94
|
+
" set str to playlist_name & \"#{@delimiter}\" & playlist_special\n"\
|
95
|
+
" return str\n"\
|
96
|
+
" on error\n"\
|
97
|
+
" return 0\n"\
|
98
|
+
" end try\n"\
|
99
|
+
"end tell"\
|
100
|
+
end
|
101
|
+
|
102
|
+
# def get_playlist_tracks_script playlist, offset, size
|
103
|
+
# "tell application \"iTunes\"\n"\
|
104
|
+
# " try\n"\
|
105
|
+
# " set playlist_tracks to every track in playlist \"#{playlist}\"\n"\
|
106
|
+
# " if (#{offset} + 1) * #{size} is less than (count of playlist_tracks) then\n"\
|
107
|
+
# " set max to (#{offset} + 1) * #{size}\n"\
|
108
|
+
# " else\n"\
|
109
|
+
# " set max to count of playlist_tracks\n"\
|
110
|
+
# " end if\n"\
|
111
|
+
# " set min to (#{offset} * #{size}) + 1\n"\
|
112
|
+
# " display dialog min\n"\
|
113
|
+
# " display dialog max\n"\
|
114
|
+
# " set str to \"\"\n"\
|
115
|
+
# " repeat with n from min to max\n"\
|
116
|
+
# " set data_track to item n of playlist_tracks\n"\
|
117
|
+
# " set data_artist to artist of data_track as string\n"\
|
118
|
+
# " set data_album to album of data_track as string\n"\
|
119
|
+
# " set data_title to name of data_track as string\n"\
|
120
|
+
# " set data_id to database ID of data_track as string\n"\
|
121
|
+
# " set data_location to POSIX path of (location of data_track as string)\n"\
|
122
|
+
# " set str to str & data_artist & \"#{@delimiter}\" "\
|
123
|
+
# " & data_album & \"#{@delimiter}\" "\
|
124
|
+
# " & data_title & \"#{@delimiter}\" "\
|
125
|
+
# " & data_id & \"#{@delimiter}\" "\
|
126
|
+
# " & data_location & \"#{@delimiter*2}\n"\
|
127
|
+
# " end repeat\n"\
|
128
|
+
# " on error\n"\
|
129
|
+
# " return 0\n"\
|
130
|
+
# " end try\n"\
|
131
|
+
# "end tell"
|
132
|
+
# end
|
133
|
+
def get_playlist_tracks_script playlist
|
134
|
+
"tell application \"iTunes\"\n"\
|
135
|
+
" try\n"\
|
136
|
+
" set playlist_tracks to every track in playlist \"#{playlist}\"\n"\
|
137
|
+
" set str to \"\"\n"\
|
138
|
+
" repeat with playlist_track in playlist_tracks\n"\
|
139
|
+
" set data_location to location of playlist_track as string\n"\
|
140
|
+
" if data_location is equal to \"missing value\" then\n"\
|
141
|
+
" set data_artist to artist of playlist_track as string\n"\
|
142
|
+
" set data_album to album of playlist_track as string\n"\
|
143
|
+
" set data_title to name of playlist_track as string\n"\
|
144
|
+
" set data_id to database ID of playlist_track as string\n"\
|
145
|
+
" set str to str & data_artist & \"#{@delimiter}\" "\
|
146
|
+
" & data_album & \"#{@delimiter}\" "\
|
147
|
+
" & data_title & \"#{@delimiter}\" "\
|
148
|
+
" & data_id & \"#{@delimiter*2}\"\n"\
|
149
|
+
" end if\n"\
|
150
|
+
" end repeat\n"\
|
151
|
+
" return str\n"\
|
76
152
|
" on error\n"\
|
77
153
|
" return 0\n"\
|
78
154
|
" end try\n"\
|
@@ -83,32 +159,65 @@ module Henchman
|
|
83
159
|
"osascript -e '#{script}' 2> /dev/null"
|
84
160
|
end
|
85
161
|
|
86
|
-
def get_selection
|
162
|
+
def get_selection
|
87
163
|
selection = %x( #{applescript_command(get_selection_script)} ).chomp
|
88
|
-
info = selection.split
|
89
|
-
|
164
|
+
info = selection.split @delimiter
|
165
|
+
track = Hash.new
|
166
|
+
if !info.empty?
|
167
|
+
track[:artist] = info[0]
|
168
|
+
track[:album] = info[1]
|
169
|
+
track[:track] = info[2]
|
170
|
+
track[:id] = info[3]
|
171
|
+
track[:path] = info[4]
|
172
|
+
end
|
173
|
+
track
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_playlist
|
177
|
+
playlist = %x(#{applescript_command(get_playlist_script)}).chomp
|
178
|
+
playlist = playlist.split @delimiter
|
179
|
+
if playlist[1] != 'none'
|
90
180
|
false
|
91
|
-
elsif info[3] == "/missing value" || !File.exists?(info[3])
|
92
|
-
ret[:artist] = info[0]
|
93
|
-
ret[:album] = info[1]
|
94
|
-
ret[:track] = info[2]
|
95
|
-
true
|
96
181
|
else
|
97
|
-
|
182
|
+
playlist[0]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def get_playlist_tracks playlist
|
187
|
+
tracks = Array.new
|
188
|
+
tmp_tracks = %x(#{applescript_command(get_playlist_tracks_script playlist)}).chomp
|
189
|
+
tmp_tracks = tmp_tracks.split @delimiter*2
|
190
|
+
tmp_tracks.each_with_index do |track, index|
|
191
|
+
next if track.empty?
|
192
|
+
tmp_track = track.split @delimiter
|
193
|
+
tracks.push( {:artist => tmp_track[0],
|
194
|
+
:album => tmp_track[1],
|
195
|
+
:track => tmp_track[2],
|
196
|
+
:id => tmp_track[3]} )
|
98
197
|
end
|
198
|
+
tracks
|
99
199
|
end
|
100
200
|
|
101
201
|
def get_album_tracks_of selection
|
102
202
|
artist = selection[:artist]
|
103
203
|
album = selection[:album]
|
104
|
-
tracks =
|
105
|
-
|
106
|
-
|
204
|
+
tracks = Array.new
|
205
|
+
tmp_tracks = %x(#{applescript_command(get_album_tracks_script artist, album)}).chomp
|
206
|
+
tmp_tracks = tmp_tracks.split @delimiter*2
|
207
|
+
tmp_tracks.each_with_index do |track, index|
|
208
|
+
next if track.empty?
|
209
|
+
tmp_track = track.split @delimiter
|
210
|
+
next if tmp_track[3] == selection[:id]
|
211
|
+
tracks.push( {:artist => tmp_track[0],
|
212
|
+
:album => tmp_track[1],
|
213
|
+
:track => tmp_track[2],
|
214
|
+
:id => tmp_track[3]} )
|
215
|
+
end
|
107
216
|
tracks
|
108
217
|
end
|
109
218
|
|
110
|
-
def fetch?
|
111
|
-
(%x(#{applescript_command(prompt_script)}).chomp == "button returned:OK") ? true : false
|
219
|
+
def fetch? prompt
|
220
|
+
(%x(#{applescript_command(prompt_script prompt)}).chomp == "button returned:OK") ? true : false
|
112
221
|
end
|
113
222
|
|
114
223
|
def get_active_app
|
@@ -116,7 +225,7 @@ module Henchman
|
|
116
225
|
end
|
117
226
|
|
118
227
|
def set_track_location selection, local_file
|
119
|
-
ret = %x(#{applescript_command(update_track_location_script selection, local_file)}).chomp
|
228
|
+
ret = %x(#{applescript_command(update_track_location_script selection[:id], local_file)}).chomp
|
120
229
|
if ret.empty? || ret == '0'
|
121
230
|
puts "Could not update location of #{selection.values.join(':')} to #{local_file}"
|
122
231
|
false
|
data/lib/core.rb
CHANGED
@@ -12,11 +12,13 @@ module Henchman
|
|
12
12
|
begin
|
13
13
|
cache_file = File.expand_path("~/.henchman/cache")
|
14
14
|
@ignore = YAML.load_file(cache_file)
|
15
|
+
raise "Incorrectly formatted cache" if !(@ignore.include? :artist)
|
16
|
+
|
17
|
+
@ignore.each_value { |val| val.default = 0 }
|
15
18
|
rescue StandardError => err
|
16
19
|
puts "Error opening cache file (#{err})"
|
17
|
-
@ignore =
|
20
|
+
@ignore = Henchman::Templates.cache
|
18
21
|
end
|
19
|
-
@ignore.default = 0
|
20
22
|
|
21
23
|
threads = []
|
22
24
|
update_cache = false
|
@@ -37,40 +39,50 @@ module Henchman
|
|
37
39
|
return
|
38
40
|
end
|
39
41
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
42
|
+
track = @appleScript.get_selection
|
43
|
+
|
44
|
+
if track_selected? track
|
45
|
+
if (missing_track_selected? track) && !(ignore? :artist, track[:artist])
|
46
|
+
update_cache = true
|
47
|
+
update_ignore :artist, track[:artist]
|
48
|
+
if @appleScript.fetch? "#{track[:album]} by #{track[:artist]}"
|
49
|
+
begin
|
50
|
+
# first download the selected track
|
51
|
+
dropbox_path = @dropbox.search_for track
|
52
|
+
file_save_path = @dropbox.download track, dropbox_path
|
53
|
+
|
54
|
+
# if we downloaded it, update the location of the track in iTunes
|
55
|
+
unless !file_save_path
|
56
|
+
updated = @appleScript.set_track_location track, file_save_path
|
57
|
+
# if the update failed, cleanup that directory and don't bother
|
58
|
+
# doing the rest of the album
|
59
|
+
if !updated
|
60
|
+
cleanup file_save_path
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
# now that we've gotten the selected track, spawn off another process
|
65
|
+
# to download the rest of the tracks on the album - spatial locality FTW
|
66
|
+
album_tracks = @appleScript.get_album_tracks_of track
|
67
|
+
threads << Thread.new{ download_tracks album_tracks }
|
61
68
|
end
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
69
|
+
rescue StandardError => err
|
70
|
+
puts err
|
71
|
+
next
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
playlist = @appleScript.get_playlist
|
77
|
+
if playlist
|
78
|
+
playlist_tracks = @appleScript.get_playlist_tracks playlist
|
79
|
+
if (!playlist_tracks.empty?) && !(ignore? :playlist, playlist)
|
80
|
+
update_cache = true
|
81
|
+
update_ignore :playlist, playlist
|
82
|
+
if @appleScript.fetch? playlist
|
83
|
+
threads << Thread.new{ download_tracks playlist_tracks }
|
67
84
|
end
|
68
|
-
rescue StandardError => err
|
69
|
-
puts err
|
70
|
-
next
|
71
85
|
end
|
72
|
-
else
|
73
|
-
@ignore[selection[:artist]] = Time.now.to_i
|
74
86
|
end
|
75
87
|
end
|
76
88
|
sleep @config[:poll_track]
|
@@ -80,31 +92,56 @@ module Henchman
|
|
80
92
|
File.open(cache_file, "w") { |f| f.write( @ignore.to_yaml ) } if update_cache
|
81
93
|
end
|
82
94
|
|
95
|
+
def self.update_ignore type, identifier
|
96
|
+
return false if !(valid_ignore_type? type)
|
97
|
+
@ignore[type][identifier] = Time.now.to_i
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.ignore? type, identifier
|
101
|
+
return false if !(valid_ignore_type? type)
|
102
|
+
@ignore[type][identifier] >= (Time.now.to_i - @config[:reprompt_timeout])
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.valid_ignore_type? type
|
106
|
+
if !(Henchman::Templates.cache.keys.include? type)
|
107
|
+
puts "Invalid type '#{type}' for ignore cache check"
|
108
|
+
false
|
109
|
+
else
|
110
|
+
true
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def self.track_selected? track
|
115
|
+
!track.empty?
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.missing_track_selected? track
|
119
|
+
track[:path] == '/missing value'
|
120
|
+
end
|
121
|
+
|
83
122
|
def self.itunes_is_active?
|
84
123
|
@appleScript.get_active_app == 'iTunes'
|
85
124
|
end
|
86
125
|
|
87
|
-
def self.
|
88
|
-
album_tracks.each
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
end
|
104
|
-
rescue StandardError => err
|
105
|
-
puts err
|
106
|
-
next
|
126
|
+
def self.download_tracks album_tracks
|
127
|
+
album_tracks.each { |album_track| download_and_update album_track }
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.download_and_update track
|
131
|
+
begin
|
132
|
+
# first download the selected track
|
133
|
+
dropbox_path = @dropbox.search_for track
|
134
|
+
file_save_path = @dropbox.download track, dropbox_path
|
135
|
+
|
136
|
+
# if we downloaded it, update the location of the track in iTunes
|
137
|
+
unless !file_save_path
|
138
|
+
updated = @appleScript.set_track_location track, file_save_path
|
139
|
+
|
140
|
+
# if the update failed, remove that file
|
141
|
+
cleanup file_save_path if !updated
|
107
142
|
end
|
143
|
+
rescue StandardError => err
|
144
|
+
puts err
|
108
145
|
end
|
109
146
|
end
|
110
147
|
|
data/lib/dropbox.rb
CHANGED
@@ -44,7 +44,11 @@ module Henchman
|
|
44
44
|
|
45
45
|
def search_for selection
|
46
46
|
# search Dropbox for the file
|
47
|
-
|
47
|
+
begin
|
48
|
+
results = @client.search(@config[:dropbox][:root], selection[:track])
|
49
|
+
rescue DropboxError => msg
|
50
|
+
raise "Error accessing Dropbox Search API on #{selection.values.join(':')}: #{msg}"
|
51
|
+
end
|
48
52
|
|
49
53
|
# get rid of any results that are directories
|
50
54
|
results.reject! { |result| result['is_dir'] }
|
data/lib/henchman/version.rb
CHANGED
data/lib/launchd_handler.rb
CHANGED
@@ -20,7 +20,7 @@ module Henchman
|
|
20
20
|
cache_path = File.expand_path("~/.henchman/cache")
|
21
21
|
File.write(plist_path, plist)
|
22
22
|
File.write(shell_script_path, Henchman::Templates.shell_script)
|
23
|
-
File.open(cache_path, "w") { |f| f.write(
|
23
|
+
File.open(cache_path, "w") { |f| f.write( Henchman::Templates.cache.to_yaml ) }
|
24
24
|
|
25
25
|
puts "Launching agent"
|
26
26
|
`chmod +x #{shell_script_path}`
|
data/lib/templates.rb
CHANGED
@@ -30,18 +30,25 @@ module Henchman
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.config
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
33
|
+
{
|
34
|
+
:dropbox => {:key => '',
|
35
|
+
:secret => '',
|
36
|
+
:access_token => '',
|
37
|
+
:user_id => '',
|
38
|
+
:root => ''},
|
39
|
+
:root => '',
|
40
|
+
:poll_itunes_open => 10,
|
41
|
+
:poll_track => 3,
|
42
|
+
:reprompt_timeout => 300,
|
43
|
+
:delimiter => '|~|'
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.cache
|
48
|
+
{
|
49
|
+
:artist => Hash.new(0),
|
50
|
+
:playlist => Hash.new(0)
|
51
|
+
}
|
45
52
|
end
|
46
53
|
end
|
47
54
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: henchman-sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Greg Merritt
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: commander
|