itunes_store_transporter 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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, except
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 equivalent 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>.
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: 750_000
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
- <% @username > uploaded it using <%= @transport %>
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
- * Source Code: http://github.com/sshaw/itunes_store_transporter
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
- # Let iTMSTransporter print the status
67
- options[:print_stdout] = true
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(args.shift)
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"] || "#{ENV["USER"]}@#{host}"
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
- RC_FILE_NAME = ".itms"
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
- config = {}
153
- return config unless home
175
+ return {} unless home
176
+
177
+ path = File.join(home, CONFIG_FILE_NAME)
178
+ return {} unless File.file?(path)
154
179
 
155
- path = File.join(home, RC_FILE_NAME)
156
- return config unless File.file?(path)
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
- # TODO: false, --no-xxxx
218
- val = true unless val
219
- val = val.to_i if val =~ /\A\d+\z/
220
- options[key.to_sym] = val
221
- ARGV.shift
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,4 +1,3 @@
1
- require "itunes/store/transporter/command"
2
1
  require "itunes/store/transporter/command/lookup"
3
2
  require "itunes/store/transporter/command/providers"
4
3
  require "itunes/store/transporter/command/schema"
@@ -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 = Transporter::OutputParser.new(stderr_lines)
64
+ parser = OutputParser.new(stderr_lines)
66
65
  errors = parser.errors.any? ? parser.errors : [ TransporterMessage.new(stderr_lines.join) ]
67
- raise ITunes::Store::Transporter::ExecutionError.new(errors, exitcode)
66
+ raise ExecutionError.new(errors, exitcode)
68
67
  end
69
-
68
+
70
69
  def create_transporter_options(optz)
71
- optz[:windows] = "true" if Transporter::Shell.windows?
70
+ optz[:windows] = "true" if Shell.windows?
72
71
  options.argv(optz)
73
72
  rescue Optout::OptionError => e
74
- raise ITunes::Store::Transporter::OptionError, e.message
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