itunes_store_transporter 0.0.1 → 0.0.2
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/Changes +12 -0
- data/README.rdoc +18 -11
- data/bin/itms +72 -45
- data/lib/itunes/store/transporter.rb +0 -1
- data/lib/itunes/store/transporter/command.rb +21 -22
- data/lib/itunes/store/transporter/command/lookup.rb +4 -3
- data/lib/itunes/store/transporter/command/status.rb +23 -9
- data/lib/itunes/store/transporter/command/verify.rb +8 -3
- data/lib/itunes/store/transporter/output_parser.rb +53 -54
- data/lib/itunes/store/transporter/shell.rb +5 -1
- data/lib/itunes/store/transporter/version.rb +1 -1
- data/spec/command_spec.rb +72 -39
- data/spec/fixtures/errors_and_warnings.yml +45 -0
- data/spec/fixtures/providers.yml +5 -0
- data/spec/fixtures/status.yml +29 -0
- data/spec/fixtures/stderr.yml +20 -0
- data/spec/fixtures/stdout.yml +5 -0
- data/spec/shell_spec.rb +17 -10
- data/spec/spec_helper.rb +14 -15
- metadata +27 -12
data/Changes
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
v0.0.2 2012-09-23
|
2
|
+
--------------------
|
3
|
+
Enhancements:
|
4
|
+
* `itms` added --no-config option
|
5
|
+
* `itms` allow boolean options to be set to false via --no-XXXX, e.g., --no-print-stderr
|
6
|
+
|
7
|
+
Bug Fixes:
|
8
|
+
* `itms status` passed the wrong arguments to the underlying method
|
9
|
+
* `itms` failed when the config file was empty
|
10
|
+
* Verify command :verify_assets => true would disable asset verification
|
11
|
+
* Status command ignored multiple status lines
|
12
|
+
* Lookup command failed to create tempdir
|
data/README.rdoc
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
= iTunes::Store::Transporter
|
2
2
|
|
3
|
+
{<img src="https://secure.travis-ci.org/sshaw/itunes_store_transporter.png"/>}[http://travis-ci.org/sshaw/itunes_store_transporter]
|
4
|
+
{<img src="https://codeclimate.com/badge.png"/>}[https://codeclimate.com/github/sshaw/itunes_store_transporter]
|
5
|
+
|
3
6
|
Upload and manage your assets in the iTunes Store using the iTunes Store's Transporter (+iTMSTransporter+).
|
4
7
|
|
5
8
|
=== Overview
|
@@ -36,7 +39,7 @@ supports the following operations:
|
|
36
39
|
* List providers
|
37
40
|
* Retrieve iTunes metadata schemas
|
38
41
|
|
39
|
-
It also includes +itms+, an executable that's sorta like using +iTMSTransporter+ directly
|
42
|
+
It also includes +itms+, an executable that's sorta like using +iTMSTransporter+ directly except
|
40
43
|
that it can send email notifications and allows one to set global/per-command defaults via <code>$HOME/.itms</code>.
|
41
44
|
|
42
45
|
=== Requirements
|
@@ -47,7 +50,7 @@ that it can send email notifications and allows one to set global/per-command de
|
|
47
50
|
=== Running on Windows
|
48
51
|
|
49
52
|
On Windows +iTMSTransporter+ is called via the +iTMSTransporter.CMD+ batch file. This file does not handle the
|
50
|
-
+iTMSTransporter+'s exit status correctly, causing <code>iTunes::Store::Transporter</code> to report everything as a success.
|
53
|
+
+iTMSTransporter+'s exit status correctly, causing <code>iTunes::Store::Transporter</code> to report everything as a success.
|
51
54
|
|
52
55
|
This can be fixed by modifying +iTMSTransporter.CMD+ (note that the following does not mimic the batch file exactly):
|
53
56
|
|
@@ -68,18 +71,18 @@ This can be fixed by modifying +iTMSTransporter.CMD+ (note that the following do
|
|
68
71
|
<code>itms COMMAND [OPTIONS]</code>
|
69
72
|
|
70
73
|
* +COMMAND+ - The command (<code>iTunes::Store::Transporter</code> method) to run
|
71
|
-
* +OPTIONS+ - These are
|
74
|
+
* +OPTIONS+ - These are quivalent to the given +COMMAND+'s options except they must be given in long option format. For example <code>:apple_id => "X123"</code> would be <code>--apple-id=X123</code>. Boolean options can be negated with the <code>--no-</code> prefix.
|
72
75
|
|
73
76
|
==== Config file
|
74
77
|
|
75
|
-
Default options and email notifications can be placed in a YAML file at <code>$HOME/.itms</code>.
|
78
|
+
Default options and email notifications can be placed in a YAML file at <code>$HOME/.itms</code>. To skip loading the config file use the <code>--no-config</code> option.
|
76
79
|
|
77
80
|
# Global command defaults
|
78
81
|
path: /usr/bin
|
79
82
|
username: sshaw
|
80
83
|
password: Pa55W0rd!
|
81
84
|
|
82
|
-
# Global email defaults
|
85
|
+
# Global email defaults
|
83
86
|
email:
|
84
87
|
to: everyone@example.com
|
85
88
|
from: no-reply@example.com
|
@@ -93,30 +96,34 @@ Default options and email notifications can be placed in a YAML file at <code>$H
|
|
93
96
|
upload:
|
94
97
|
shortname: enc0d3rz
|
95
98
|
transport: Aspera
|
96
|
-
rate:
|
99
|
+
rate: 750000
|
97
100
|
# Email notifications for the upload command
|
98
101
|
email:
|
99
102
|
success:
|
100
103
|
cc: assets@example.com
|
101
104
|
subject: iTunes Upload <%= @apple_id %>
|
102
|
-
message: |
|
103
|
-
|
105
|
+
message: |
|
106
|
+
<%= @username %> uploaded it using <%= @transport %>
|
104
107
|
|
105
108
|
Bye!
|
106
109
|
failure:
|
107
110
|
to: support@example.com
|
108
111
|
subject: Upload Failed!
|
109
112
|
message: |
|
110
|
-
Here's the problem:
|
113
|
+
Here's the problem:
|
111
114
|
|
112
|
-
<%= @error %>
|
115
|
+
<%= @error %>
|
113
116
|
|
114
117
|
Fix it!
|
115
118
|
|
119
|
+
As you can see, command options are turned into template variables.
|
120
|
+
|
116
121
|
=== More Info
|
117
122
|
|
118
|
-
*
|
123
|
+
* Docs: http://ruby-doc.org/gems/docs/i/itunes_store_transporter-0.0.2/README_rdoc.html
|
119
124
|
* Bugs: http://github.com/sshaw/itunes_store_transporter/issues
|
125
|
+
* Source Code: http://github.com/sshaw/itunes_store_transporter
|
126
|
+
* Web Based GUI: http://github.com/sshaw/itunes_store_transporter_web
|
120
127
|
|
121
128
|
=== Author
|
122
129
|
|
data/bin/itms
CHANGED
@@ -5,7 +5,7 @@ require "yaml"
|
|
5
5
|
require "net/smtp"
|
6
6
|
require "itunes/store/transporter"
|
7
7
|
|
8
|
-
# Command line interface to the ITunes::Store::Transporter library.
|
8
|
+
# Command line interface to the ITunes::Store::Transporter library.
|
9
9
|
# Using this is sorta like using iTMSTransporter except it can send email notifications and allows
|
10
10
|
# one to set global/per-command defaults via $HOME/.itms
|
11
11
|
|
@@ -14,13 +14,13 @@ module Command
|
|
14
14
|
class << self
|
15
15
|
def execute(name, options, argv)
|
16
16
|
name = name.capitalize
|
17
|
-
# Avoid Ruby 1.8/1.9 String/Symbol/const_defined? differences
|
17
|
+
# Avoid Ruby 1.8/1.9 String/Symbol/const_defined? differences
|
18
18
|
unless constants.include?(name) || constants.include?(name.to_sym)
|
19
19
|
raise ArgumentError, "unknown command '#{name}'"
|
20
20
|
end
|
21
21
|
|
22
22
|
command = const_get(name).new(options)
|
23
|
-
command.execute(argv)
|
23
|
+
command.execute(argv)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -30,7 +30,7 @@ module Command
|
|
30
30
|
@options = options
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
class Providers < Base
|
35
35
|
def initialize(options)
|
36
36
|
# Let iTMSTransporter print the providers
|
@@ -51,7 +51,7 @@ module Command
|
|
51
51
|
puts "Metadata saved to #{filename}"
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
class Schema < Base
|
56
56
|
def execute(args = [])
|
57
57
|
filename = "#{@options[:version]}-#{@options[:type]}.rng"
|
@@ -60,22 +60,41 @@ module Command
|
|
60
60
|
puts "Schema saved to #{filename}"
|
61
61
|
end
|
62
62
|
end
|
63
|
-
|
63
|
+
|
64
64
|
class Status < Base
|
65
65
|
def initialize(options)
|
66
|
-
#
|
67
|
-
|
66
|
+
# It's preferable to let iTMSTransporter output the status but unlike other commands
|
67
|
+
# it summarizes errors on stdout. This results in redundant error messages since we summarize
|
68
|
+
# errors too. To avoid this, and keep our error messages consistant, we reprint the status below.
|
69
|
+
options[:print_stdout] = false unless options.include?(:print_stdout)
|
68
70
|
super
|
69
71
|
end
|
70
72
|
|
71
|
-
def execute(args = [])
|
72
|
-
@itms.status
|
73
|
+
def execute(args = [])
|
74
|
+
info = @itms.status
|
75
|
+
info.each do |k, v|
|
76
|
+
next if k == :status
|
77
|
+
say(k, v, 21)
|
78
|
+
end
|
79
|
+
|
80
|
+
info[:status].each_with_index do |status, i|
|
81
|
+
pos = info[:status].size - i
|
82
|
+
puts "\n#{'-' * 15} Upload ##{pos} #{'-' * 15}"
|
83
|
+
status.each do |k, v|
|
84
|
+
say(k, v, 18)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def say(k, v, width)
|
90
|
+
k = k.to_s.capitalize.gsub("_", " ")
|
91
|
+
printf "%-#{width}s %s\n", k, v
|
73
92
|
end
|
74
93
|
end
|
75
94
|
|
76
95
|
class Upload < Base
|
77
96
|
def initialize(options)
|
78
|
-
# These can take a while so we let the user know what's going on
|
97
|
+
# These can take a while so we let the user know what's going on
|
79
98
|
options[:print_stderr] = true unless options.include?(:print_stderr)
|
80
99
|
super
|
81
100
|
end
|
@@ -98,9 +117,9 @@ module Command
|
|
98
117
|
puts @itms.version
|
99
118
|
end
|
100
119
|
end
|
101
|
-
end
|
120
|
+
end
|
102
121
|
|
103
|
-
class Email
|
122
|
+
class Email
|
104
123
|
Binding = Class.new do
|
105
124
|
def initialize(options = {})
|
106
125
|
options.each do |k, v|
|
@@ -109,19 +128,19 @@ class Email
|
|
109
128
|
end
|
110
129
|
end
|
111
130
|
end
|
112
|
-
|
131
|
+
|
113
132
|
def initialize(config = {})
|
114
133
|
unless config["to"]
|
115
|
-
raise "No email recipeints provided, you must specify at least one"
|
134
|
+
raise "No email recipeints provided, you must specify at least one"
|
116
135
|
end
|
117
136
|
|
118
137
|
@config = config
|
119
138
|
end
|
120
|
-
|
121
|
-
def send(params = {})
|
139
|
+
|
140
|
+
def send(params = {})
|
122
141
|
to = @config["to"].to_s.split /,/
|
123
142
|
host = @config["host"] || "localhost"
|
124
|
-
from = @config["from"] || "#{
|
143
|
+
from = @config["from"] || "#{user}@#{host}"
|
125
144
|
params = params.merge(@config)
|
126
145
|
message = ::ERB.new(build_template).def_class(Binding).new(params).result
|
127
146
|
|
@@ -132,6 +151,10 @@ class Email
|
|
132
151
|
end
|
133
152
|
|
134
153
|
protected
|
154
|
+
def user
|
155
|
+
ENV["USER"] || ENV["USERNAME"]
|
156
|
+
end
|
157
|
+
|
135
158
|
def build_template
|
136
159
|
%w[to from subject cc bcc].inject("") do |t, key|
|
137
160
|
t << "#{key}: #{@config[key]}\n" if @config[key]
|
@@ -141,7 +164,7 @@ class Email
|
|
141
164
|
end
|
142
165
|
|
143
166
|
COMMANDS = ITunes::Store::Transporter.instance_methods(false).map(&:to_s)
|
144
|
-
|
167
|
+
CONFIG_FILE_NAME = ".itms"
|
145
168
|
|
146
169
|
def home
|
147
170
|
ENV["HOME"] || ENV["USERPROFILE"]
|
@@ -149,28 +172,28 @@ end
|
|
149
172
|
|
150
173
|
# Should probably create a class for the options
|
151
174
|
def load_config(command)
|
152
|
-
|
153
|
-
|
175
|
+
return {} unless home
|
176
|
+
|
177
|
+
path = File.join(home, CONFIG_FILE_NAME)
|
178
|
+
return {} unless File.file?(path)
|
154
179
|
|
155
|
-
|
156
|
-
return
|
180
|
+
config = YAML.load_file(path)
|
181
|
+
return {} unless config
|
157
182
|
|
158
|
-
config = YAML.load_file(path)
|
159
|
-
|
160
183
|
# Get the global defaults. select() returns an aray on Ruby < 1.9
|
161
|
-
defaults = config.select { |k,v| !v.is_a?(Hash) }
|
162
|
-
defaults = Hash[defaults] unless defaults.is_a?(Hash)
|
184
|
+
defaults = config.select { |k,v| !v.is_a?(Hash) }
|
185
|
+
defaults = Hash[defaults] unless defaults.is_a?(Hash)
|
163
186
|
|
164
187
|
config[command] = defaults.merge(config[command] || {})
|
165
|
-
|
188
|
+
|
166
189
|
# Normalize the email config
|
167
190
|
email = Hash.new { |h, k| h[k] = {} }
|
168
191
|
|
169
|
-
%w[success failure].each do |type|
|
170
|
-
email[type] = (config[command]["email"] ||= {})[type]
|
192
|
+
%w[success failure].each do |type|
|
193
|
+
email[type] = (config[command]["email"] ||= {})[type]
|
171
194
|
next unless email[type]
|
172
195
|
|
173
|
-
# Merge the global email options & the command's "global" options with the success/failure options
|
196
|
+
# Merge the global email options & the command's "global" options with the success/failure options
|
174
197
|
settings = (config["email"].to_a + config[command]["email"].to_a).reject { |k, v| k == "success" or k == "failure" }
|
175
198
|
settings.each do |k, v|
|
176
199
|
email[type][k] = email[type][k] ? "#{email[type][k]}, #{v}" : v
|
@@ -178,9 +201,9 @@ def load_config(command)
|
|
178
201
|
end
|
179
202
|
|
180
203
|
# ITunes::Store::Transporter uses Symbols for options
|
181
|
-
config[command] = config[command].inject({}) do |cfg, (k,v)|
|
204
|
+
config[command] = config[command].inject({}) do |cfg, (k,v)|
|
182
205
|
cfg[k.to_sym] = v unless k.empty? # Avoid intern empty string errors in 1.8
|
183
|
-
cfg
|
206
|
+
cfg
|
184
207
|
end
|
185
208
|
|
186
209
|
config[command][:email] = email
|
@@ -206,19 +229,24 @@ command = ARGV.shift
|
|
206
229
|
abort("usage: itms command [options]") unless command
|
207
230
|
abort("invalid command '#{command}', valid commands are: #{COMMANDS.sort.join(', ')}") unless COMMANDS.include?(command)
|
208
231
|
|
209
|
-
options = load_config(command)
|
232
|
+
options = ARGV.delete("--no-config") ? {} : load_config(command)
|
210
233
|
|
211
|
-
while ARGV.any?
|
234
|
+
while ARGV.any?
|
212
235
|
opt = ARGV.first.dup
|
213
236
|
break unless opt.sub!(/\A--(?=\w)/, "")
|
214
|
-
|
237
|
+
|
215
238
|
key, val = opt.split(/=/, 2)
|
216
239
|
key.gsub!(/-/, "_")
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
240
|
+
|
241
|
+
if val
|
242
|
+
val = val.to_i if val =~ /\A\d+\z/
|
243
|
+
else
|
244
|
+
# Boolean option
|
245
|
+
val = key.sub!(/\Ano_(?=\w)/, "") ? false : true
|
246
|
+
end
|
247
|
+
|
248
|
+
options[key.to_sym] = val
|
249
|
+
ARGV.shift
|
222
250
|
end
|
223
251
|
|
224
252
|
# Keys for this are strings
|
@@ -227,11 +255,11 @@ command_options = options.dup
|
|
227
255
|
options[:argv] = ARGV.dup
|
228
256
|
options[:command] = command
|
229
257
|
|
230
|
-
begin
|
231
|
-
puts "Running command '#{command}'"
|
258
|
+
begin
|
259
|
+
puts "Running command '#{command}'\n\n"
|
232
260
|
Command.execute(command, command_options, ARGV)
|
233
261
|
send_email(email_options["success"], options)
|
234
|
-
rescue ITunes::Store::Transporter::ExecutionError => e
|
262
|
+
rescue ITunes::Store::Transporter::ExecutionError => e
|
235
263
|
print_errors(e)
|
236
264
|
options[:error] = e
|
237
265
|
send_email(email_options["failure"], options)
|
@@ -240,4 +268,3 @@ rescue ITunes::Store::Transporter::TransporterError => e
|
|
240
268
|
$stderr.puts e
|
241
269
|
exit 2
|
242
270
|
end
|
243
|
-
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require "optout"
|
2
|
-
require "itunes/store/transporter"
|
3
2
|
require "itunes/store/transporter/shell"
|
4
3
|
require "itunes/store/transporter/errors"
|
5
4
|
require "itunes/store/transporter/output_parser"
|
@@ -9,7 +8,7 @@ module ITunes
|
|
9
8
|
module Store
|
10
9
|
class Transporter
|
11
10
|
module Command # :nodoc: all
|
12
|
-
|
11
|
+
|
13
12
|
class Base
|
14
13
|
include Option
|
15
14
|
|
@@ -18,13 +17,13 @@ module ITunes
|
|
18
17
|
@shell = Shell.new(@config[:path])
|
19
18
|
@default_options = default_options
|
20
19
|
end
|
21
|
-
|
20
|
+
|
22
21
|
def run(options = {})
|
23
22
|
options = default_options.merge(options)
|
24
23
|
argv = create_transporter_options(options)
|
25
24
|
stdout_lines = []
|
26
25
|
stderr_lines = []
|
27
|
-
|
26
|
+
|
28
27
|
# TODO: hooks
|
29
28
|
exitcode = @shell.exec(argv) do |line, name|
|
30
29
|
if name == :stdout
|
@@ -46,13 +45,13 @@ module ITunes
|
|
46
45
|
protected
|
47
46
|
attr :config
|
48
47
|
attr :default_options
|
49
|
-
|
48
|
+
|
50
49
|
def options
|
51
50
|
@options ||= Optout.options do
|
52
51
|
# On Windows we must include this else the Transporter batch script will call `pause` after the Transporter exits
|
53
|
-
on :windows, "-WONoPause"
|
52
|
+
on :windows, "-WONoPause"
|
54
53
|
# Optout can't do this: [a, b, c] => -X a -X b -X c
|
55
|
-
on :jvm, "-X" #, :multiple => true
|
54
|
+
on :jvm, "-X" #, :multiple => true
|
56
55
|
end
|
57
56
|
end
|
58
57
|
|
@@ -60,38 +59,38 @@ module ITunes
|
|
60
59
|
def handle_success(stdout_lines, stderr_lines, options)
|
61
60
|
stdout_lines.join
|
62
61
|
end
|
63
|
-
|
62
|
+
|
64
63
|
def handle_error(stdout_lines, stderr_lines, options, exitcode)
|
65
|
-
parser =
|
64
|
+
parser = OutputParser.new(stderr_lines)
|
66
65
|
errors = parser.errors.any? ? parser.errors : [ TransporterMessage.new(stderr_lines.join) ]
|
67
|
-
raise
|
66
|
+
raise ExecutionError.new(errors, exitcode)
|
68
67
|
end
|
69
|
-
|
68
|
+
|
70
69
|
def create_transporter_options(optz)
|
71
|
-
optz[:windows] = "true" if
|
70
|
+
optz[:windows] = "true" if Shell.windows?
|
72
71
|
options.argv(optz)
|
73
72
|
rescue Optout::OptionError => e
|
74
|
-
raise
|
73
|
+
raise OptionError, e.message
|
75
74
|
end
|
76
75
|
end
|
77
|
-
|
76
|
+
|
78
77
|
class Mode < Base
|
79
|
-
def initialize(*config)
|
80
|
-
super
|
78
|
+
def initialize(*config)
|
79
|
+
super
|
81
80
|
options.on :log, "-o", Optout::File
|
82
|
-
options.on :verbose, "-v", %w|informational critical detailed eXtreme| # Since log output is critical to determining what's going on we can't include "off"
|
81
|
+
options.on :verbose, "-v", %w|informational critical detailed eXtreme| # Since log output is critical to determining what's going on we can't include "off"
|
82
|
+
options.on :summary, "-summaryFile", Optout::File
|
83
|
+
options.on :mode, "-m", /\w+/, :required => true
|
83
84
|
options.on :username, "-u", :required => true
|
84
85
|
options.on :password, "-p", :required => true
|
85
|
-
options.on :summary, "-summaryFile", Optout::File
|
86
|
-
options.on :mode, "-m", /\w+/, :required => true
|
87
86
|
options.on *SHORTNAME
|
88
87
|
end
|
89
|
-
|
88
|
+
|
90
89
|
def create_transporter_options(optz)
|
91
90
|
optz[:mode] = mode
|
92
91
|
super
|
93
92
|
end
|
94
|
-
|
93
|
+
|
95
94
|
def mode
|
96
95
|
self.class.to_s.split("::")[-1].gsub(/([a-z])([A-Z])/, "\1_\2").downcase
|
97
96
|
end
|
@@ -100,4 +99,4 @@ module ITunes
|
|
100
99
|
end
|
101
100
|
end
|
102
101
|
end
|
103
|
-
end
|
102
|
+
end
|