choctop 0.9.6 → 0.10.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.
- data/History.txt +10 -0
- data/Manifest.txt +16 -7
- data/README.rdoc +7 -3
- data/app_generators/install_choctop/templates/Rakefile.erb +2 -3
- data/features/development.feature +5 -5
- data/features/dmg.feature +34 -28
- data/features/fixtures/SampleApp/README.txt +1 -0
- data/features/fixtures/SampleApp/build/Release/README.txt +1 -0
- data/features/fixtures/SampleApp/build/Release/dmg/README.txt +1 -0
- data/features/initial_generator.feature +10 -10
- data/features/rake_tasks.feature +15 -15
- data/features/sparkle_feed.feature +25 -25
- data/features/step_definitions/common_steps.rb +177 -0
- data/features/{steps → step_definitions}/dmg_steps.rb +12 -4
- data/features/{steps → step_definitions}/file_attribute_steps.rb +2 -2
- data/features/{steps/generator.rb → step_definitions/generator_steps.rb} +2 -2
- data/features/{steps/remote.rb → step_definitions/remote_steps.rb} +2 -2
- data/features/{steps → step_definitions}/xcode_steps.rb +1 -1
- data/features/support/common.rb +44 -0
- data/features/{steps → support}/env.rb +0 -0
- data/features/support/matchers.rb +11 -0
- data/lib/choctop.rb +66 -28
- data/lib/choctop/appcast.rb +1 -1
- data/lib/choctop/dmg.rb +22 -3
- data/spec/choctop_spec.rb +85 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +5 -0
- data/tasks/rspec.rake +21 -0
- metadata +44 -13
- data/features/steps/common.rb +0 -248
@@ -8,7 +8,15 @@ Given /is configured for custom Applications icon$/ do
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
Given /^is configured for an asset file "([^\"]*)" to be included in dmg$/ do |file|
|
12
|
+
in_project_folder do
|
13
|
+
append_to_file "Rakefile", <<-RUBY.gsub(/^ /, '')
|
14
|
+
$sparkle.add_file "#{file}", :position=> [347, 65]
|
15
|
+
RUBY
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
When /^dmg "(.*)" is mounted as "(.*)"$/ do |dmg, name|
|
12
20
|
@stdout = File.expand_path(File.join(@tmp_root, "hdiutil.out"))
|
13
21
|
in_project_folder do
|
14
22
|
@mountpoint = ChocTop.new.mountpoint
|
@@ -22,19 +30,19 @@ def in_mounted_volume(&block)
|
|
22
30
|
FileUtils.chdir(@volume_path, &block)
|
23
31
|
end
|
24
32
|
|
25
|
-
Then %r{^folder
|
33
|
+
Then %r{^folder "(.*)" in mounted volume (is|is not) created} do |folder, is|
|
26
34
|
in_mounted_volume do
|
27
35
|
File.exists?(folder).should(is == 'is' ? be_true : be_false)
|
28
36
|
end
|
29
37
|
end
|
30
38
|
|
31
|
-
Then %r{^file
|
39
|
+
Then %r{^file "(.*)" in mounted volume (is|is not) created} do |file, is|
|
32
40
|
in_mounted_volume do
|
33
41
|
File.exists?(file).should(is == 'is' ? be_true : be_false)
|
34
42
|
end
|
35
43
|
end
|
36
44
|
|
37
|
-
Then /^file
|
45
|
+
Then /^file "(.*)" in mounted volume (is|is not) invisible$/ do |file, is|
|
38
46
|
in_mounted_volume do
|
39
47
|
`GetFileInfo -aV '#{@volume_path}/#{file}'`.to_i.should_not == (is == 'is' ? 0 : 1)
|
40
48
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Then /^file
|
1
|
+
Then /^file "(.*)" in mounted volume has GetFileInfo (.*) "(.*)"/ do |file, file_info_type, value|
|
2
2
|
flags = case file_info_type.to_sym
|
3
3
|
when :type; "-t"
|
4
4
|
when :alias; "-aa"
|
@@ -9,7 +9,7 @@ Then /^file '(.*)' in mounted volume has GetFileInfo (.*) '(.*)'/ do |file, file
|
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
Then /^file
|
12
|
+
Then /^file "(.*)" in mounted volume is aliased to "(.*)"/ do |file, target|
|
13
13
|
in_mounted_volume do
|
14
14
|
puts "TODO - how to get applescript to test this?"
|
15
15
|
end
|
@@ -24,7 +24,7 @@ end
|
|
24
24
|
Given /Rakefile constants rewired for local rsync/ do
|
25
25
|
end
|
26
26
|
|
27
|
-
Given /^a Cocoa app with choctop installed called
|
27
|
+
Given /^a Cocoa app with choctop installed called "(.*)"$/ do |name|
|
28
28
|
Given "a safe folder"
|
29
29
|
@remote_folder = File.expand_path(File.join(@tmp_root, 'website'))
|
30
30
|
FileUtils.rm_rf @remote_folder
|
@@ -33,7 +33,7 @@ Given /^a Cocoa app with choctop installed called '(.*)'$/ do |name|
|
|
33
33
|
`cp -r '#{app_path}' #{@tmp_root}/ 2> /dev/null`
|
34
34
|
`rm -rf '#{@tmp_root}/#{name}/build'`
|
35
35
|
setup_active_project_folder name
|
36
|
-
Given
|
36
|
+
Given %Q{I run local executable "install_choctop" with arguments "."}
|
37
37
|
Given "Rakefile wired to use development code instead of installed RubyGem"
|
38
38
|
Given "Rakefile constants rewired for local rsync"
|
39
39
|
ENV['NO_FINDER'] = 'YES' # disable Finder during tests
|
@@ -1,10 +1,10 @@
|
|
1
|
-
Then %r{^remote folder
|
1
|
+
Then %r{^remote folder "(.*)" is created} do |folder|
|
2
2
|
FileUtils.chdir @remote_folder do
|
3
3
|
File.exists?(folder).should be_true
|
4
4
|
end
|
5
5
|
end
|
6
6
|
|
7
|
-
Then %r{^remote file
|
7
|
+
Then %r{^remote file "(.*)" (is|is not) created} do |file, is|
|
8
8
|
FileUtils.chdir @remote_folder do
|
9
9
|
File.exists?(file).should(is == 'is' ? be_true : be_false)
|
10
10
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module CommonHelpers
|
2
|
+
def in_tmp_folder(&block)
|
3
|
+
FileUtils.chdir(@tmp_root, &block)
|
4
|
+
end
|
5
|
+
|
6
|
+
def in_project_folder(&block)
|
7
|
+
project_folder = @active_project_folder || @tmp_root
|
8
|
+
FileUtils.chdir(project_folder, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def in_home_folder(&block)
|
12
|
+
FileUtils.chdir(@home_path, &block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def force_local_lib_override(project_name = @project_name)
|
16
|
+
rakefile = File.read(File.join(project_name, 'Rakefile'))
|
17
|
+
File.open(File.join(project_name, 'Rakefile'), "w+") do |f|
|
18
|
+
f << "$:.unshift('#{@lib_path}')\n"
|
19
|
+
f << rakefile
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def setup_active_project_folder project_name
|
24
|
+
@active_project_folder = File.join(@tmp_root, project_name)
|
25
|
+
FileUtils.mkdir_p(@active_project_folder)
|
26
|
+
@project_name = project_name
|
27
|
+
end
|
28
|
+
|
29
|
+
def prepend_to_file(filename, text)
|
30
|
+
file = File.read(filename)
|
31
|
+
File.open(filename, "w+") do |f|
|
32
|
+
f << text + "\n"
|
33
|
+
f << file
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def append_to_file(filename, text)
|
38
|
+
File.open(filename, "a") do |f|
|
39
|
+
f << text + "\n"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
World(CommonHelpers)
|
File without changes
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Matchers
|
2
|
+
def contain(expected)
|
3
|
+
simple_matcher("contain #{expected.inspect}") do |given, matcher|
|
4
|
+
matcher.failure_message = "expected #{given.inspect} to contain #{expected.inspect}"
|
5
|
+
matcher.negative_failure_message = "expected #{given.inspect} not to contain #{expected.inspect}"
|
6
|
+
given.index expected
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
World(Matchers)
|
data/lib/choctop.rb
CHANGED
@@ -5,12 +5,17 @@ require "fileutils"
|
|
5
5
|
require "yaml"
|
6
6
|
require "builder"
|
7
7
|
require "erb"
|
8
|
+
require "uri"
|
8
9
|
require "osx/cocoa"
|
9
10
|
require "active_support"
|
10
11
|
require "RedCloth"
|
11
12
|
|
12
13
|
class ChocTop
|
13
|
-
VERSION = '0.
|
14
|
+
VERSION = '0.10.0'
|
15
|
+
|
16
|
+
# Path to the Info.plist
|
17
|
+
# Default: "Info.plist"
|
18
|
+
attr_accessor :info_plist_path
|
14
19
|
|
15
20
|
# The name of the Cocoa application
|
16
21
|
# Default: info_plist['CFBundleExecutable'] or project folder name if "${EXECUTABLE_NAME}"
|
@@ -23,20 +28,29 @@ class ChocTop
|
|
23
28
|
# The target name of the distributed DMG file
|
24
29
|
# Default: #{name}.app
|
25
30
|
attr_accessor :target
|
31
|
+
|
32
|
+
# The build type of the distributed DMG file
|
33
|
+
# Default: Release
|
34
|
+
attr_accessor :build_type
|
35
|
+
|
36
|
+
# The Sparkle feed URL
|
37
|
+
# Default: info_plist['SUFeedURL']
|
38
|
+
attr_accessor :su_feed_url
|
26
39
|
|
27
40
|
# The host name, e.g. some-domain.com
|
41
|
+
# Default: host from base_url
|
28
42
|
attr_accessor :host
|
29
43
|
|
30
44
|
# The url from where the xml + dmg files will be downloaded
|
31
|
-
# Default:
|
32
|
-
|
33
|
-
def base_url
|
34
|
-
@base_url ||= "http://#{host}"
|
35
|
-
end
|
45
|
+
# Default: dir path from appcast_filename
|
46
|
+
attr_accessor :base_url
|
36
47
|
|
37
48
|
# The file name for generated release notes for the latest release
|
38
49
|
# Default: release_notes.html
|
39
50
|
attr_accessor :release_notes
|
51
|
+
|
52
|
+
# List of files/bundles to be packaged into the DMG
|
53
|
+
attr_accessor :files
|
40
54
|
|
41
55
|
# The path for an HTML template into which the release_notes.txt are inserted
|
42
56
|
# after conversion to HTML
|
@@ -60,6 +74,13 @@ class ChocTop
|
|
60
74
|
# Default: -aCv
|
61
75
|
attr_accessor :rsync_args
|
62
76
|
|
77
|
+
# Folder from where all files will be copied into the DMG
|
78
|
+
# Files are copied here if specified with +add_file+ before DMG creation
|
79
|
+
attr_accessor :src_folder
|
80
|
+
def src_folder
|
81
|
+
@src_folder ||= "build/#{build_type}/dmg"
|
82
|
+
end
|
83
|
+
|
63
84
|
# Generated filename for a distribution, from name, version and .dmg
|
64
85
|
# e.g. MyApp-1.0.0.dmg
|
65
86
|
def pkg_name
|
@@ -136,41 +157,61 @@ class ChocTop
|
|
136
157
|
end
|
137
158
|
|
138
159
|
def info_plist
|
139
|
-
@info_plist ||= OSX::NSDictionary.dictionaryWithContentsOfFile(File.expand_path(
|
160
|
+
@info_plist ||= OSX::NSDictionary.dictionaryWithContentsOfFile(File.expand_path(info_plist_path)) || {}
|
161
|
+
end
|
162
|
+
|
163
|
+
# Add an explicit file/bundle/folder into the DMG
|
164
|
+
# Required option:
|
165
|
+
# +:position+ - two item array [x, y] window position
|
166
|
+
def add_file(path, options)
|
167
|
+
throw "add_files #{path}, :position => [x,y] option is missing" unless options[:position]
|
168
|
+
self.files ||= {}
|
169
|
+
files[path] = options
|
140
170
|
end
|
141
171
|
|
142
172
|
def initialize
|
143
173
|
$sparkle = self # define a global variable for this object
|
144
174
|
|
175
|
+
yield self if block_given?
|
176
|
+
|
145
177
|
# Defaults
|
146
|
-
@
|
147
|
-
@name
|
148
|
-
@
|
149
|
-
@
|
150
|
-
@
|
151
|
-
@
|
152
|
-
@release_notes_template = "release_notes_template.html.erb"
|
153
|
-
@rsync_args = '-aCv --progress'
|
178
|
+
@info_plist_path ||= 'Info.plist'
|
179
|
+
@name ||= info_plist['CFBundleExecutable']
|
180
|
+
@name = File.basename(File.expand_path(".")) if name.to_s == "${EXECUTABLE_NAME}"
|
181
|
+
@version ||= info_plist['CFBundleVersion']
|
182
|
+
@target ||= "#{name}.app"
|
183
|
+
@build_type = ENV['BUILD_TYPE'] || 'Release'
|
154
184
|
|
155
|
-
@
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
@
|
160
|
-
|
185
|
+
if @su_feed_url = info_plist['SUFeedURL']
|
186
|
+
@appcast_filename ||= File.basename(su_feed_url)
|
187
|
+
@base_url ||= File.dirname(su_feed_url)
|
188
|
+
end
|
189
|
+
if @base_url
|
190
|
+
@host ||= URI.parse(base_url).host
|
191
|
+
end
|
192
|
+
@release_notes ||= 'release_notes.html'
|
193
|
+
@release_notes_template ||= "release_notes_template.html.erb"
|
194
|
+
@rsync_args ||= '-aCv --progress'
|
161
195
|
|
162
|
-
|
196
|
+
@background_file ||= File.dirname(__FILE__) + "/../assets/sky_background.jpg"
|
197
|
+
@app_icon_position ||= [175, 65]
|
198
|
+
@applications_icon_position ||= [347, 270]
|
199
|
+
@volume_icon ||= File.dirname(__FILE__) + "/../assets/DefaultVolumeIcon.icns"
|
200
|
+
@icon_size ||= 104
|
201
|
+
@icon_text_size ||= 12
|
163
202
|
|
203
|
+
add_file "build/#{build_type}/#{target}", :position => app_icon_position
|
204
|
+
|
164
205
|
define_tasks
|
165
206
|
end
|
166
207
|
|
167
208
|
def define_tasks
|
168
209
|
return unless Object.const_defined?("Rake")
|
169
210
|
|
170
|
-
desc "Build Xcode
|
171
|
-
task :build => "build
|
211
|
+
desc "Build Xcode #{build_type}"
|
212
|
+
task :build => "build/#{build_type}/#{target}/Contents/Info.plist"
|
172
213
|
|
173
|
-
task "build
|
214
|
+
task "build/#{build_type}/#{target}/Contents/Info.plist" do
|
174
215
|
make_build
|
175
216
|
end
|
176
217
|
|
@@ -195,9 +236,6 @@ class ChocTop
|
|
195
236
|
upload_appcast
|
196
237
|
end
|
197
238
|
|
198
|
-
desc "Create dmg, update appcast file, and upload to host"
|
199
|
-
task :appcast => %w[force_build dmg force_feed upload]
|
200
|
-
|
201
239
|
task :detach_dmg do
|
202
240
|
detach_dmg
|
203
241
|
end
|
data/lib/choctop/appcast.rb
CHANGED
data/lib/choctop/dmg.rb
CHANGED
@@ -1,9 +1,18 @@
|
|
1
1
|
module ChocTop::Dmg
|
2
|
+
def prepare_files
|
3
|
+
FileUtils.mkdir_p(src_folder)
|
4
|
+
files.each do |file|
|
5
|
+
path, options = file
|
6
|
+
FileUtils.cp_r(path, src_folder)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
2
10
|
def make_dmg
|
11
|
+
prepare_files
|
3
12
|
FileUtils.rm_rf build_path
|
4
13
|
FileUtils.mkdir_p build_path
|
5
14
|
FileUtils.mkdir_p mountpoint # TODO can we remove random mountpoints?
|
6
|
-
sh "hdiutil create -format UDRW -quiet -volname '#{name}' -srcfolder '
|
15
|
+
sh "hdiutil create -format UDRW -quiet -volname '#{name}' -srcfolder '#{src_folder}' '#{pkg}'"
|
7
16
|
sh "hdiutil attach '#{pkg}' -mountpoint '#{volume_path}' -noautoopen -quiet"
|
8
17
|
sh "bless --folder '#{volume_path}' --openfolder '#{volume_path}'"
|
9
18
|
sh "sleep 1"
|
@@ -47,7 +56,7 @@ module ChocTop::Dmg
|
|
47
56
|
FileUtils.mkdir_p(File.dirname(target_background))
|
48
57
|
FileUtils.cp(background_file, target_background)
|
49
58
|
end
|
50
|
-
|
59
|
+
script = <<-SCRIPT.gsub(/^ /, '')
|
51
60
|
tell application "Finder"
|
52
61
|
set mountpoint to POSIX file ("#{volume_path}" as string) as alias
|
53
62
|
tell folder mountpoint
|
@@ -63,7 +72,7 @@ module ChocTop::Dmg
|
|
63
72
|
set icon size of the icon view options of container window to #{icon_size}
|
64
73
|
set text size of the icon view options of container window to #{icon_text_size}
|
65
74
|
set arrangement of the icon view options of container window to not arranged
|
66
|
-
|
75
|
+
#{set_position_of_files}
|
67
76
|
set position of item "Applications" to {#{applications_icon_position.join(", ")}}
|
68
77
|
set the bounds of the container window to {#{window_bounds.join(", ")}}
|
69
78
|
set background picture of the icon view options of container window to file "#{volume_background.gsub(/\//,':')}"
|
@@ -75,9 +84,19 @@ module ChocTop::Dmg
|
|
75
84
|
delay 5
|
76
85
|
end tell
|
77
86
|
SCRIPT
|
87
|
+
run_applescript(script)
|
78
88
|
sh "SetFile -a V '#{target_background}'" if background_file
|
79
89
|
end
|
80
90
|
|
91
|
+
def set_position_of_files
|
92
|
+
files.map do |file_options|
|
93
|
+
path, options = file_options
|
94
|
+
target = File.basename(path)
|
95
|
+
position = options[:position].join(", ")
|
96
|
+
%Q{set position of item "#{target}" to {#{position}}}
|
97
|
+
end.join("\n")
|
98
|
+
end
|
99
|
+
|
81
100
|
def configure_applications_icon
|
82
101
|
run_applescript <<-SCRIPT.gsub(/^ /, ''), "apps_icon_script"
|
83
102
|
tell application "Finder"
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper.rb'
|
2
|
+
|
3
|
+
# Time to add your specs!
|
4
|
+
# http://rspec.info/
|
5
|
+
describe ChocTop do
|
6
|
+
attr_reader :choctop
|
7
|
+
|
8
|
+
describe "default" do
|
9
|
+
before(:each) do
|
10
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
11
|
+
@choctop = ChocTop.new
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should get name from Info.plist['CFBundleExecutable']" do
|
16
|
+
choctop.name.should == 'SampleApp'
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should get version from Info.plist['CFBundleVersion']" do
|
20
|
+
choctop.version.should == '0.1.0'
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should derive host from Info.plist['SUFeedURL']" do
|
24
|
+
choctop.host.should == 'mocra.com'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should derive base_url from Info.plist['SUFeedURL']" do
|
28
|
+
choctop.base_url.should == 'http://mocra.com/sample_app'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should derive appcast_filename from Info.plist['SUFeedURL']" do
|
32
|
+
choctop.appcast_filename.should == 'my_feed.xml'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "add_files" do
|
37
|
+
before(:each) do
|
38
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
39
|
+
@choctop = ChocTop.new
|
40
|
+
@choctop.add_file "README.txt", :position => [50, 100]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have build/Release/SampleApp.app as a file/bundle" do
|
45
|
+
@choctop.files.keys.should be_include('build/Release/SampleApp.app')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should have README.txt as a file" do
|
49
|
+
@choctop.files.keys.include?('README.txt')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should have README.txt position" do
|
53
|
+
@choctop.files['README.txt'][:position].should == [50, 100]
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "+ prepare_files" do
|
57
|
+
before(:each) do
|
58
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
59
|
+
@choctop.prepare_files
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should have SampleApp.app in build/Release/dmg ready for inclusion in DMG" do
|
64
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
65
|
+
File.should be_exists('build/Release/dmg/SampleApp.app')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should position SampleApp.app at [175, 65]" do
|
70
|
+
@choctop.set_position_of_files.should =~ /set position of item "SampleApp.app" to \{175, 65\}/
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should have README.txt in build/Release ready for inclusion in DMG" do
|
74
|
+
FileUtils.chdir(File.dirname(__FILE__) + "/../features/fixtures/SampleApp") do
|
75
|
+
File.should be_exists('build/Release/dmg/README.txt')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should position README.txt at [50, 100]" do
|
80
|
+
@choctop.set_position_of_files.should =~ /set position of item "README.txt" to \{50, 100\}/
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|