appsendr 0.0.1

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.
@@ -0,0 +1,198 @@
1
+ require 'zip/zip'
2
+ require 'ftools'
3
+ module AppSendr::Command
4
+ class Build < Base
5
+ attr_accessor :app_path, :app_dir, :app_name, :ipa_path, :provisioning_name, :provisioning_path, :configuration, :project_info
6
+
7
+
8
+ def index
9
+ if require_project(1,"build","your active configuration name")
10
+ @configuration = load_configuration
11
+ display "=== Building Xcode project with configuration #{@configuration}"
12
+ compile(@configuration)
13
+
14
+ @app_dir = path_to_app_dir
15
+ @app_path = @app_dir+"/#{app_name}"
16
+ @ipa_path = build_ipa_with_app_at_path
17
+ info = app_info
18
+ bundle_identifier ||= info['CFBundleIdentifier']
19
+ icon_path = icon_path(info)
20
+ appsendr.upload(read_app_id,@ipa_path, profile_path,nil,bundle_identifier,icon_path)
21
+ FileUtils.rm_rf @ipa_path
22
+
23
+ end
24
+ end
25
+
26
+
27
+ def clean
28
+ require_project_dir("clean")
29
+ require_project_droppr
30
+ config = args.join(" ").strip
31
+ if !config.empty?
32
+ config = "-configuration #{config} "
33
+ end
34
+ %x[xcodebuild #{config} clean]
35
+ end
36
+
37
+ private
38
+
39
+ def compile(build_style, ossdk=nil, target=nil)
40
+ iossdk = "iphoneos4.1"
41
+ iossdk ||= ossdk
42
+ sdk_str = "-sdk "+iossdk
43
+
44
+ if target
45
+ target_str = "-target "+target
46
+ end
47
+ xcode = "xcodebuild #{sdk_str} #{target_str}-configuration #{build_style} > /dev/null 2>&1"
48
+ system(xcode)
49
+ end
50
+
51
+ def build_ipa_with_app_at_path
52
+ payload_path = @app_dir+"/Payload"
53
+ ipa_path = @app_dir+"/#{ipa_app_name(app_name)}"
54
+
55
+
56
+ FileUtils.rm_rf payload_path
57
+ Dir.mkdir(payload_path)
58
+ `cp -r #{@app_path.gsub(" ","\\ ")} #{payload_path.gsub(" ","\\ ")+"/"+app_name.gsub(" ","\\ ")}`
59
+ build_ipa(payload_path.gsub(" ","\\ "),ipa_path.gsub(" ","\\ "))
60
+
61
+ FileUtils.rm_rf payload_path
62
+
63
+ return ipa_path
64
+
65
+ end
66
+
67
+ def build_ipa(payload_path,ipa_path)
68
+ #ditto_exec = "ditto -c -k --keepParent -rsrc #{payload_path} #{ipa_path}"
69
+ ditto_exec = "ditto -c -k --keepParent --sequesterRsrc #{payload_path} #{ipa_path}"
70
+ #display ditto_exec
71
+ system(ditto_exec)
72
+ end
73
+
74
+
75
+
76
+ #povisioning
77
+
78
+ def profile_path
79
+ return @provisioning_path if @provisioning_path
80
+ @provisioning_path = provisioning_profiles_dir + "/#{profile_name}"
81
+ @provisioning_path
82
+ end
83
+
84
+ def provisioning_profiles_dir
85
+ File.expand_path("~/Library/MobileDevice/Provisioning\ Profiles/")
86
+ end
87
+
88
+
89
+ def profile_name
90
+ return @provisioning_name if @provisioning_name
91
+ error "Build is not valid for AdHoc distribution" unless valid_build?
92
+
93
+ mem_buf = File.open(@app_path+"/embedded.mobileprovision","rb") {|io| io.read}
94
+ name = mem_buf.scan(/UUID<\/key>[\s]+<string>(.*)<\/string>/).flatten.first
95
+ @provisioning_name = name + ".mobileprovision"
96
+ @provisioning_name
97
+ end
98
+
99
+
100
+ ## helpers
101
+ def load_configuration
102
+ @configuration = args.join(" ").strip
103
+ error "That configuration does not exist.\nAvailable configurations:\n#{project_info[:configurations].join("\n")}" unless valid_configuration?
104
+ @configuration
105
+ end
106
+
107
+ def valid_configuration?
108
+ return false unless @configuration
109
+ info = project_info
110
+ info[:configurations].include?(@configuration)
111
+ end
112
+
113
+ def project_info
114
+ return @project_info if @project_info
115
+ result = %x[xcodebuild -list]
116
+ project_info = result.split("\n")
117
+ name = project_info.shift.scan(/\"(.*)\"/).flatten.first
118
+ project_info.shift
119
+ project_info.pop #remove last line
120
+ project_info.pop #remove
121
+ on_targets = true
122
+ targets = []
123
+ configs = []
124
+ project_info.each{|ln|
125
+ if ln.strip == "Build Configurations:"
126
+ on_targets = false
127
+ end
128
+ if ln.strip != "Build Configurations:" and ln.strip != "Targets:" and ln.strip!=""
129
+ ln = ln.gsub("(Active)","").strip
130
+ on_targets ? targets.push(ln) : configs.push(ln)
131
+ end
132
+ }
133
+
134
+ @project_info = {:name => name, :targets=>targets, :configurations=>configs}
135
+ @project_info
136
+ end
137
+ def icon_path(info)
138
+ icon_files = info['CFBundleIconFiles']
139
+ icon_path = nil
140
+ icon_file = nil
141
+ if icon_files.nil? or icon_files.count == 0
142
+ icon_file = info['CFBundleIconFile']
143
+ else
144
+ large_icon = icon_files.grep(/@2x/i)
145
+ if large_icon.nil?
146
+ large_icon = icon_files.first
147
+ end
148
+ icon_file = large_icon
149
+ end
150
+ if icon_file and !icon_file.empty?
151
+ all_dir_contents = Dir["**/*"]
152
+ all_dir_contents = all_dir_contents - all_dir_contents.grep(/^build/)
153
+
154
+ icon = all_dir_contents.grep(%r{#{icon_file}}).flatten.first
155
+ if icon
156
+ icon_path = Dir.pwd+"/#{icon}"
157
+ end
158
+ end
159
+ return icon_path
160
+ end
161
+
162
+
163
+ def valid_build?
164
+ return unless @app_path
165
+ File.exist?(@app_path+"/embedded.mobileprovision")
166
+ end
167
+
168
+ def app_info
169
+ return unless @app_path
170
+ mem_buf = File.open(@app_path+"/Info.plist","rb") {|io| io.read}
171
+ plist = AppSendr::Plist::Binary.decode_binary_plist(mem_buf)
172
+ plist
173
+ end
174
+
175
+ def path_to_app_dir
176
+ Dir.pwd+"/build/#{@configuration}-iphoneos"
177
+ end
178
+
179
+ def app_name
180
+ if @app_dir.nil?
181
+ return unless @configuration
182
+ @app_dir = path_to_app_dir
183
+ end
184
+ return @app_name if @app_name
185
+ @app_name = Dir.entries(@app_dir).grep(/.+\.app$/).first
186
+ @app_name
187
+ end
188
+
189
+ def ipa_app_name(app_name)
190
+ app_name.gsub(/\.app$/,".ipa")
191
+ end
192
+
193
+ def project_name
194
+ return Dir.entries(Dir.pwd).grep(/.+\.xcodeproj/).first.sub("",".xcodeproj")
195
+ end
196
+
197
+ end
198
+ end
@@ -0,0 +1,41 @@
1
+ require 'optparse'
2
+
3
+ module AppSendr::Command
4
+ class Deploy < Build ## Inherhits from Deploy
5
+
6
+ def index
7
+ #
8
+ # opts = OptionParser.new
9
+ # opts.on('-m', '--message') { output_version ; exit 0 }
10
+ #
11
+ # opts.parse!(args) rescue return false
12
+ #
13
+ #
14
+ @configuration = load_configuration
15
+ if require_project(1,"deploy","your active configuration name",true)
16
+ @app_dir = path_to_app_dir
17
+ @app_path = @app_dir+"/#{app_name}"
18
+ if @app_name
19
+ @notes = ask_for_notes
20
+
21
+ @ipa_path = build_ipa_with_app_at_path
22
+ info = app_info
23
+ bundle_identifier ||= info['CFBundleIdentifier']
24
+ icon_path = icon_path(info)
25
+ appsendr.upload(read_app_id,@ipa_path, profile_path,@notes,bundle_identifier,icon_path)
26
+ FileUtils.rm_rf @ipa_path
27
+ else
28
+ error "No .app file was built for the configuration \"#{@configuration}\". Compile your app or use the \"build\" command."
29
+ end
30
+ end
31
+ end
32
+
33
+ protected
34
+ def ask_for_notes
35
+ print "Release Notes: "
36
+ notes = ask
37
+ return notes.strip
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,87 @@
1
+ module AppSendr::Command
2
+ class Help < Base
3
+ class HelpGroup < Array
4
+ attr_reader :title
5
+
6
+ def initialize(title)
7
+ @title = title
8
+ end
9
+
10
+ def command(name, description)
11
+ self << [name, description]
12
+ end
13
+
14
+ def space
15
+ self << ['', '']
16
+ end
17
+ end
18
+
19
+ def self.groups
20
+ @groups ||= []
21
+ end
22
+
23
+ def self.group(title, &block)
24
+ groups << begin
25
+ group = HelpGroup.new(title)
26
+ yield group
27
+ group
28
+ end
29
+ end
30
+
31
+ def self.create_default_groups!
32
+ group 'Commands' do |group|
33
+ group.command 'help', 'show this usage'
34
+ group.command 'version', 'show the gem version'
35
+ group.space
36
+ group.command 'list', 'list your apps'
37
+ group.command 'link', 'link your app with an exsiting one in appsendr'
38
+ group.command 'create <name>', 'create a new app'
39
+ group.space
40
+ group.command 'build <active configuration>', 'build your xcode project and deploy'
41
+ group.command 'build:clean', 'clean your xcode project'
42
+ group.space
43
+ group.command 'deploy <active configuration>', 'deploy the current build'
44
+ group.space
45
+ group.command 'testers', 'list testers'
46
+ group.command 'testers:add <email> <name>', 'add a tester'
47
+ group.command 'testers:remove <email> ', 'remove a tester'
48
+ group.command 'testers:clear', 'remove all testers'
49
+ group.command 'testers:notify', 'notify testers about the latest build'
50
+
51
+ end
52
+
53
+ end
54
+
55
+ def index
56
+ display usage
57
+ end
58
+
59
+ def version
60
+ display AppSendr::Client.version
61
+ end
62
+
63
+ def usage
64
+ longest_command_length = self.class.groups.map do |group|
65
+ group.map { |g| g.first.length }
66
+ end.flatten.max
67
+
68
+ self.class.groups.inject(StringIO.new) do |output, group|
69
+ output.puts "=== %s" % group.title
70
+ output.puts
71
+
72
+ group.each do |command, description|
73
+ if command.empty?
74
+ output.puts
75
+ else
76
+ output.puts "%-*s # %s" % [longest_command_length, command, description]
77
+ end
78
+ end
79
+
80
+ output.puts
81
+ output
82
+ end.string
83
+ end
84
+ end
85
+ end
86
+
87
+ AppSendr::Command::Help.create_default_groups!
@@ -0,0 +1,115 @@
1
+ module AppSendr::Command
2
+ class Testers < Base
3
+ def index #output testers
4
+ if in_project_dir?
5
+ unless has_project_droppr?
6
+ require_project_droppr
7
+ return
8
+ end
9
+
10
+ testers = appsendr.testers(read_app_id)
11
+ if testers["message"].size > 0
12
+ display "=== Your testers"
13
+ i = 0
14
+ display testers["message"].map {|app, id|
15
+ "#{i+=1}. #{app['tester']['name']} - #{app['tester']['email']}"
16
+ }.join("\n")
17
+ else
18
+ display "You have no testers."
19
+ end
20
+
21
+ # f = testers_file
22
+ # return unless f
23
+ # display "=== Testers"
24
+ # f.each do |line|
25
+ # tester = line.split(',')
26
+ # email = tester.first.strip
27
+ # display "#{email},#{name}"
28
+ # end
29
+ # f.close
30
+ end
31
+ end
32
+
33
+ def add
34
+
35
+ if require_project(2,"add testers","an email and name", true)
36
+ f = testers_file_append
37
+ return unless f
38
+ email = args.shift.strip
39
+ name = args.join(" ").strip
40
+ f.puts "#{email},#{name}"
41
+ f.close
42
+
43
+ appsendr.add_tester(read_app_id,email,name)
44
+
45
+ end
46
+ end
47
+
48
+ def remove
49
+ if require_project(1,"remove testers","an email")
50
+ entered_email = args.shift.strip
51
+
52
+ f = testers_file
53
+ return unless f
54
+ out = ""
55
+ found_email = nil
56
+ f.each do |line|
57
+ tester = line.split(',')
58
+ email = tester.first.strip
59
+ if email == entered_email
60
+ found_email = email
61
+ out << ""
62
+ else
63
+ out << line
64
+ end
65
+ end
66
+ f.pos = 0
67
+ f.print out
68
+ f.truncate(f.pos)
69
+
70
+ appsendr.remove_tester(read_app_id,entered_email)
71
+
72
+ end
73
+ end
74
+
75
+ def clear
76
+ if require_project(0,"clear testers",nil)
77
+ f = testers_file_truncate
78
+ return unless f
79
+ f.close
80
+
81
+ appsendr.add_tester(read_app_id)
82
+
83
+ end
84
+ end
85
+
86
+ def notify
87
+ require_project_dir("to notify testers")
88
+ require_project_droppr
89
+ appsendr.notify(read_app_id)
90
+
91
+ end
92
+
93
+ protected
94
+
95
+ def testers
96
+ AppSendr::PROJECT_DIR+"/testers"
97
+ end
98
+
99
+ def testers_file
100
+ return nil unless in_project_dir?
101
+ return File.open(testers, File::RDWR|File::CREAT, 0600)
102
+ end
103
+
104
+ def testers_file_append
105
+ return nil unless in_project_dir?
106
+ return File.open(testers, File::WRONLY|File::APPEND|File::CREAT, 0600)
107
+ end
108
+
109
+ def testers_file_truncate
110
+ return nil unless in_project_dir?
111
+ return File.open(path, File::WRONLY|File::TRUNC|File::CREAT, 0600)
112
+ end
113
+
114
+ end
115
+ end
@@ -0,0 +1,7 @@
1
+ module AppSendr::Command
2
+ class Version < Base
3
+ def index
4
+ display AppSendr::Client.gem_version_string
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module AppSendr
2
+ VERSION = "0.0.1"
3
+ PROJECT_DIR = ".appsendr"
4
+ APPDROPPR_DIR = ".appsendr"
5
+ end
@@ -0,0 +1,120 @@
1
+ module AppSendr
2
+ module Helpers
3
+ def home_directory
4
+ running_on_windows? ? ENV['USERPROFILE'] : ENV['HOME']
5
+ end
6
+
7
+ def running_on_windows?
8
+ RUBY_PLATFORM =~ /mswin32|mingw32/
9
+ end
10
+
11
+ def running_on_a_mac?
12
+ RUBY_PLATFORM =~ /-darwin\d/
13
+ end
14
+
15
+ def display(msg, newline=true)
16
+ if newline
17
+ puts(msg)
18
+ else
19
+ print(msg)
20
+ STDOUT.flush
21
+ end
22
+ end
23
+
24
+ def credentials_file
25
+ "#{home_directory}/#{AppSendr::APPDROPPR_DIR}/credentials"
26
+ end
27
+
28
+ def credentials_setup?
29
+ File.exist?(credentials_file)
30
+ end
31
+
32
+ def project_appsendr
33
+ Dir.pwd+"/#{AppSendr::PROJECT_DIR}"
34
+ end
35
+
36
+ def project_appsendr_app
37
+ project_appsendr+"/app"
38
+ end
39
+
40
+ def read_app_id
41
+ File.exists?(project_appsendr_app) and File.read(project_appsendr_app).split("\n").first
42
+ end
43
+ def read_app
44
+ File.exists?(project_appsendr_app) and File.read(project_appsendr_app).split("\n")
45
+ end
46
+
47
+ def has_project_droppr?
48
+ return false unless File.directory?(project_appsendr)
49
+ return File.exist?(project_appsendr_app)
50
+ end
51
+
52
+ def require_project_droppr
53
+ error "Project directory doesn't exist. Have you created an app droppr for this app?" unless has_project_droppr?
54
+ end
55
+
56
+ def require_project_dir(action)
57
+ error "You must be in a Xcode project directory to #{action}" unless in_project_dir?
58
+ end
59
+
60
+ def in_project_dir?
61
+ return Dir.entries(Dir.pwd).grep(/.+\.xcodeproj/).first
62
+ end
63
+
64
+ def require_project(num_args,action,wrong_args,greater_than=false)
65
+ if in_project_dir? and has_project_droppr?
66
+ if args.count >= num_args
67
+ if greater_than
68
+ return true
69
+ elsif !greater_than and (args.count == num_args)
70
+ return true
71
+ else
72
+ return false
73
+ end
74
+ elsif wrong_args
75
+ error "You must include #{wrong_args}"
76
+ end
77
+ else
78
+ require_project_dir(action)
79
+ require_project_droppr
80
+ end
81
+
82
+ return false
83
+ end
84
+
85
+ def require_in_project_and_no_droppr(num_args,action,wrong_args,greater_than=false)
86
+ if in_project_dir? and !has_project_droppr?
87
+ if args.count >= num_args
88
+ if greater_than
89
+ return true
90
+ elsif !greater_than and (args.count == num_args)
91
+ return true
92
+ else
93
+ return false
94
+ end
95
+ elsif wrong_args
96
+ error "You must include #{wrong_args}"
97
+ end
98
+ else
99
+ require_project_dir(action)
100
+ error "This app is already linked to appsendr." unless !has_project_droppr?
101
+
102
+ end
103
+
104
+ return false
105
+ end
106
+
107
+ def error(msg)
108
+ STDERR.puts(msg)
109
+ exit 1
110
+ end
111
+ end
112
+ end
113
+
114
+ unless String.method_defined?(:shellescape)
115
+ class String
116
+ def shellescape
117
+ empty? ? "''" : gsub(/([^A-Za-z0-9_\-.,:\/@\n])/n, '\\\\\\1').gsub(/\n/, "'\n'")
118
+ end
119
+ end
120
+ end
data/lib/appsendr.rb ADDED
@@ -0,0 +1,3 @@
1
+ module AppSendr; end
2
+
3
+ require 'appsendr/client'