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 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