releasy 0.2.0rc1
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/.gitignore +17 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +20 -0
- data/README.md +165 -0
- data/Rakefile +31 -0
- data/bin/7z.sfx +0 -0
- data/bin/releasy +7 -0
- data/lib/releasy.rb +18 -0
- data/lib/releasy/archivers.rb +12 -0
- data/lib/releasy/archivers/archiver.rb +74 -0
- data/lib/releasy/archivers/dmg.rb +18 -0
- data/lib/releasy/archivers/exe.rb +23 -0
- data/lib/releasy/archivers/seven_zip.rb +12 -0
- data/lib/releasy/archivers/tar_archiver.rb +14 -0
- data/lib/releasy/archivers/tar_bzip2.rb +13 -0
- data/lib/releasy/archivers/tar_gzip.rb +13 -0
- data/lib/releasy/archivers/zip.rb +12 -0
- data/lib/releasy/builders.rb +12 -0
- data/lib/releasy/builders/builder.rb +52 -0
- data/lib/releasy/builders/ocra_builder.rb +43 -0
- data/lib/releasy/builders/osx_app.rb +158 -0
- data/lib/releasy/builders/source.rb +27 -0
- data/lib/releasy/builders/windows_builder.rb +65 -0
- data/lib/releasy/builders/windows_folder.rb +47 -0
- data/lib/releasy/builders/windows_folder_from_ruby_dist.rb +150 -0
- data/lib/releasy/builders/windows_installer.rb +117 -0
- data/lib/releasy/builders/windows_standalone.rb +37 -0
- data/lib/releasy/cli.rb +12 -0
- data/lib/releasy/cli/install_sfx.rb +66 -0
- data/lib/releasy/dsl_wrapper.rb +71 -0
- data/lib/releasy/mixins/exec.rb +14 -0
- data/lib/releasy/mixins/has_archivers.rb +37 -0
- data/lib/releasy/mixins/has_gemspecs.rb +42 -0
- data/lib/releasy/mixins/register.rb +47 -0
- data/lib/releasy/project.rb +300 -0
- data/lib/releasy/version.rb +3 -0
- data/lib/releasy/windows_wrapper_maker.rb +90 -0
- data/lib/releasy/windows_wrapper_maker/Gemfile +8 -0
- data/media/releasy.png +0 -0
- data/releasy.gemspec +37 -0
- data/test/releasy/archivers_test.rb +65 -0
- data/test/releasy/builders/data/Info.plist +51 -0
- data/test/releasy/builders/data/Main.rb +12 -0
- data/test/releasy/builders/data/relapse_runner.rb +1 -0
- data/test/releasy/builders/data/set_app_executable.sh +3 -0
- data/test/releasy/builders/helpers/helper.rb +47 -0
- data/test/releasy/builders/ocra_builder_test.rb +37 -0
- data/test/releasy/builders/osx_app_test.rb +107 -0
- data/test/releasy/builders/source_test.rb +43 -0
- data/test/releasy/builders/windows_builder_test.rb +26 -0
- data/test/releasy/builders/windows_folder_from_ruby_dist_test.rb +105 -0
- data/test/releasy/builders/windows_folder_test.rb +56 -0
- data/test/releasy/builders/windows_installer_test.rb +62 -0
- data/test/releasy/builders/windows_standalone_test.rb +58 -0
- data/test/releasy/cli/install_sfx_test.rb +90 -0
- data/test/releasy/dsl_wrapper_test.rb +79 -0
- data/test/releasy/integration/source_test.rb +122 -0
- data/test/releasy/mixins/register_test.rb +52 -0
- data/test/releasy/project_test.rb +198 -0
- data/test/releasy/windows_wrapper_maker_test.rb +61 -0
- data/test/teststrap.rb +52 -0
- data/test/yard_test.rb +61 -0
- data/test_project/Gemfile +9 -0
- data/test_project/LICENSE.txt +1 -0
- data/test_project/README.txt +2 -0
- data/test_project/bin/test_app +3 -0
- data/test_project/lib/test_app.rb +6 -0
- data/test_project/lib/test_app/stuff.rb +1 -0
- data/test_project/test_app.icns +0 -0
- data/test_project/test_app.ico +0 -0
- data/wrappers/put_wrappers_here_for_testing.txt +17 -0
- metadata +236 -0
@@ -0,0 +1,150 @@
|
|
1
|
+
require "releasy/builders/windows_builder"
|
2
|
+
require "releasy/mixins/has_gemspecs"
|
3
|
+
|
4
|
+
module Releasy
|
5
|
+
module Builders
|
6
|
+
# Wraps up an application for Windows when not on Windows, based on a RubyInstaller distribution and installing Windows binary gems.
|
7
|
+
#
|
8
|
+
# The resulting package will be much larger than a Windows package created on Windows, since it will include
|
9
|
+
# the whole Ruby distribution, not just files that are needed.
|
10
|
+
#
|
11
|
+
# Limitations:
|
12
|
+
# * Does not DLLs loaded from the system, which will have to be included manually if any are required by the application and no universally available in Windows installations.
|
13
|
+
# * Unless a gem is in pure Ruby or available as a pre-compiled binary gem, it won't work!
|
14
|
+
class WindowsFolderFromRubyDist < WindowsBuilder
|
15
|
+
include Mixins::HasGemspecs
|
16
|
+
|
17
|
+
TYPE = :windows_folder_from_ruby_dist
|
18
|
+
DEFAULT_FOLDER_SUFFIX = "WIN32"
|
19
|
+
|
20
|
+
# Files that are required for Tcl/Tk, but which are unlikely to be used in many applications.
|
21
|
+
TCL_TK_FILES = %w[bin/tcl*-ri.dll bin/tk*-ri.dll
|
22
|
+
lib/tcltk
|
23
|
+
lib/ruby/tk*.rb
|
24
|
+
lib/ruby/1.*/tk* lib/ruby/1.*/tcl*
|
25
|
+
lib/ruby/1.*/i386-mingw32/tk* lib/ruby/1.*/i386-mingw32/tcl*
|
26
|
+
]
|
27
|
+
|
28
|
+
# Encoding files that are required, even if we don't need most of them if we select to {#exclude_encoding}.
|
29
|
+
REQUIRED_ENCODING_FILES = %w[encdb.so iso_8859_1.so utf_16le.so trans/single_byte.so trans/transdb.so trans/utf_16_32.so]
|
30
|
+
|
31
|
+
Builders.register self
|
32
|
+
|
33
|
+
# @return [String] Path to windows distribution archive that has been manually downloaded from http://rubyinstaller.org/downloads/ (e.g. "rubies/ruby-1.9.2-p290-i386-mingw32.7z").
|
34
|
+
attr_accessor :ruby_dist
|
35
|
+
|
36
|
+
# Remove TCL/TK from package, which can save a significant amount of space if the application does not require them.
|
37
|
+
# This is over 6MB uncompressed, which is a saving of 1.6MB when compressed with 7z format (LZMA).
|
38
|
+
# @return [Project] self
|
39
|
+
def exclude_tcl_tk; @exclude_tcl_tk = true; self; end
|
40
|
+
|
41
|
+
def valid_for_platform?; not Releasy.win_platform?; end
|
42
|
+
|
43
|
+
protected
|
44
|
+
# FOLDER containing EXE, Ruby + source.
|
45
|
+
def generate_tasks
|
46
|
+
raise ConfigError, "#ruby_dist not set" unless ruby_dist
|
47
|
+
raise ConfigError, "#ruby_dist not valid: #{ruby_dist}" unless File.exist?(ruby_dist) and File.extname(ruby_dist) == ".7z"
|
48
|
+
|
49
|
+
directory project.output_path
|
50
|
+
|
51
|
+
file folder => project.files + [ruby_dist] do
|
52
|
+
build
|
53
|
+
end
|
54
|
+
|
55
|
+
desc "Build source/exe folder #{project.version} from wrapper"
|
56
|
+
task "build:windows:folder_from_ruby_dist" => folder
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
def build
|
61
|
+
Rake::FileUtilsExt.verbose project.verbose?
|
62
|
+
|
63
|
+
copy_ruby_distribution
|
64
|
+
delete_excluded_files
|
65
|
+
|
66
|
+
copy_files_relative project.files, File.join(folder, 'src')
|
67
|
+
|
68
|
+
create_link_files folder
|
69
|
+
project.exposed_files.each {|file| cp file, folder }
|
70
|
+
|
71
|
+
create_executable
|
72
|
+
|
73
|
+
# Copy gems.
|
74
|
+
destination = File.join(folder, 'vendor')
|
75
|
+
downloaded_binary_gems = install_binary_gems destination
|
76
|
+
copy_gems vendored_gem_names(downloaded_binary_gems), destination
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
def delete_excluded_files
|
81
|
+
# Remove TCL/TK dlls, lib folder and source.
|
82
|
+
rm_r Dir[*(TCL_TK_FILES.map {|f| File.join(folder, f) })].uniq.sort if @exclude_tcl_tk
|
83
|
+
|
84
|
+
# Remove Encoding files on Ruby 1.9
|
85
|
+
if encoding_excluded? and ruby_dist =~ /1\.9\.\d/
|
86
|
+
encoding_files = Dir[File.join folder, "lib/ruby/1.9.1/i386-mingw32/enc/**/*.so"]
|
87
|
+
required_encoding_files = REQUIRED_ENCODING_FILES.map {|f| File.join folder, "lib/ruby/1.9.1/i386-mingw32/enc", f }
|
88
|
+
rm_r encoding_files - required_encoding_files
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
protected
|
93
|
+
def setup
|
94
|
+
@exclude_tcl_tk = false
|
95
|
+
@ruby_dist = nil
|
96
|
+
super
|
97
|
+
end
|
98
|
+
|
99
|
+
protected
|
100
|
+
def executable_name; "#{project.underscored_name}.exe"; end
|
101
|
+
|
102
|
+
protected
|
103
|
+
def copy_ruby_distribution
|
104
|
+
archive_name = File.basename(ruby_dist).chomp(File.extname(ruby_dist))
|
105
|
+
exec %[7z x "#{ruby_dist}" -o"#{File.dirname folder}"]
|
106
|
+
mv File.join(File.dirname(folder), archive_name), folder
|
107
|
+
rm_r File.join(folder, "share")
|
108
|
+
rm_r File.join(folder, "include") if File.exists? File.join(folder, "include")
|
109
|
+
unused_exe = effective_executable_type == :windows ? "ruby.exe" : "rubyw.exe"
|
110
|
+
rm File.join(folder, "bin", unused_exe)
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def create_executable
|
115
|
+
maker = Releasy::WindowsWrapperMaker.new
|
116
|
+
maker.build_executable("#{folder}/#{executable_name}", "src/#{project.executable}",
|
117
|
+
:windows => (effective_executable_type == :windows))
|
118
|
+
end
|
119
|
+
|
120
|
+
protected
|
121
|
+
def install_binary_gems(destination)
|
122
|
+
puts "Checking gems to see if any are binary" if project.verbose?
|
123
|
+
binary_gems = []
|
124
|
+
gemspecs.reject {|s| false }.each do |spec|
|
125
|
+
puts "Checking gem #{spec.name} #{spec.version} to see if there is a Windows binary version" if project.verbose?
|
126
|
+
# Find out what versions are available and if the required version is available as a windows binary, download and install that.
|
127
|
+
versions = %x[gem list "#{spec.name}" --remote --all --prerelease]
|
128
|
+
if versions =~ /#{spec.name} \(([^\)]*)\)/m
|
129
|
+
version_string = $1
|
130
|
+
platforms = version_string.split(/,\s*/).find {|s| s =~ /^#{spec.version}/ }.split(/\s+/)
|
131
|
+
windows_platform = platforms.find {|p| p =~ /mingw|mswin/ }
|
132
|
+
raise "Gem #{spec.name} is binary, but #{spec.version} does not have a published binary" if version_string =~ /mingw|mswin/ and not windows_platform
|
133
|
+
|
134
|
+
if windows_platform
|
135
|
+
puts "Installing Windows version of binary gem #{spec.name} #{spec.version}"
|
136
|
+
# If we have a bundle file specified, then gem will _only_ install the version specified by it and not the one we request.
|
137
|
+
bundle_gemfile = ENV['BUNDLE_GEMFILE']
|
138
|
+
ENV['BUNDLE_GEMFILE'] = ''
|
139
|
+
exec %[gem install "#{spec.name}" --remote --no-rdoc --no-ri --force --ignore-dependencies --platform "#{windows_platform}" --version "#{spec.version}" --install-dir "#{destination}"]
|
140
|
+
ENV['BUNDLE_GEMFILE'] = bundle_gemfile
|
141
|
+
binary_gems << spec.name
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
binary_gems
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require "releasy/builders/ocra_builder"
|
2
|
+
|
3
|
+
module Releasy
|
4
|
+
module Builders
|
5
|
+
# Builds a win32 installer for the application.
|
6
|
+
class WindowsInstaller < OcraBuilder
|
7
|
+
TYPE = :windows_installer
|
8
|
+
INSTALLER_SCRIPT = "windows_installer.iss"
|
9
|
+
DEFAULT_FOLDER_SUFFIX = "WIN32_INSTALLER"
|
10
|
+
|
11
|
+
Builders.register self
|
12
|
+
|
13
|
+
# @return [String] Optional start-menu grouping of the application when installed (if name == "app" and installer_group == "frog", then it will get put into 'frog/app' in the start menu).
|
14
|
+
attr_accessor :start_menu_group
|
15
|
+
|
16
|
+
# @return [String] File name of readme file - End user will have the option to view this after the Windows installer has installed, recommended to be .txt, .rtf or .html.
|
17
|
+
attr_accessor :readme
|
18
|
+
# @return [String] Filename of license file - Must be .txt or .rtf file, which will be shown to user who will be requested to accept it (Windows installer only).
|
19
|
+
attr_accessor :license
|
20
|
+
|
21
|
+
protected
|
22
|
+
# Regular windows installer, but some users consider them evil.
|
23
|
+
def generate_tasks
|
24
|
+
directory folder
|
25
|
+
|
26
|
+
file folder => project.files do
|
27
|
+
Rake::FileUtilsExt.verbose project.verbose?
|
28
|
+
|
29
|
+
create_link_files folder
|
30
|
+
project.exposed_files.each {|file| cp file, folder }
|
31
|
+
|
32
|
+
create_installer installer_name, :links => true
|
33
|
+
|
34
|
+
rm temp_installer_script
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Build installer #{project.version} [Innosetup]"
|
38
|
+
task "build:windows:installer" => folder
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
def setup
|
43
|
+
super
|
44
|
+
@start_menu_group = nil
|
45
|
+
@readme = nil
|
46
|
+
@license = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
def temp_installer_script; "#{project.output_path}/#{INSTALLER_SCRIPT}"; end
|
51
|
+
def installer_name; "#{folder}/#{project.underscored_name}_setup.exe"; end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def create_installer(file, options = {})
|
55
|
+
generate_installer_script file, options
|
56
|
+
exec %[#{ocra_command} --chdir-first --no-lzma --innosetup "#{temp_installer_script}"]
|
57
|
+
end
|
58
|
+
|
59
|
+
# Generate innosetup script to build installer.
|
60
|
+
protected
|
61
|
+
def generate_installer_script(output_file, options = {})
|
62
|
+
installer_links = options[:links]
|
63
|
+
|
64
|
+
File.open(temp_installer_script, "w") do |file|
|
65
|
+
file.write <<END
|
66
|
+
[Setup]
|
67
|
+
AppName=#{project.name}
|
68
|
+
AppVersion=#{project.version}
|
69
|
+
DefaultDirName={pf}\\#{project.name.gsub(/[^\w\s]/, '')}
|
70
|
+
OutputDir=#{File.dirname output_file}
|
71
|
+
OutputBaseFilename=#{File.basename(output_file).chomp(File.extname(output_file))}
|
72
|
+
UninstallDisplayIcon={app}\\#{project.underscored_name}.exe
|
73
|
+
END
|
74
|
+
|
75
|
+
if installer_links
|
76
|
+
if start_menu_group
|
77
|
+
file.puts "DefaultGroupName=#{start_menu_group}\\#{project.name}"
|
78
|
+
else
|
79
|
+
file.puts "DefaultGroupName=#{project.name}"
|
80
|
+
end
|
81
|
+
|
82
|
+
file.puts "LicenseFile=#{license}" if license # User must accept license.
|
83
|
+
file.puts "SetupIconFile=#{icon}" if icon
|
84
|
+
file.puts
|
85
|
+
|
86
|
+
file.puts "[Files]"
|
87
|
+
|
88
|
+
file.puts %[Source: "#{license}"; DestDir: "{app}"] if license
|
89
|
+
file.puts %[Source: "#{readme}"; DestDir: "{app}"; Flags: isreadme] if readme
|
90
|
+
|
91
|
+
dir = File.dirname(output_file).tr("/", "\\")
|
92
|
+
project.send(:links).each_value do |title|
|
93
|
+
file.puts %[Source: "#{dir}\\#{title}.url"; DestDir: "{app}"]
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
file.write <<END
|
98
|
+
|
99
|
+
[Run]
|
100
|
+
Filename: "{app}\\#{project.underscored_name}.exe"; Description: "Launch"; Flags: postinstall nowait skipifsilent unchecked
|
101
|
+
|
102
|
+
[Icons]
|
103
|
+
Name: "{group}\\#{project.name}"; Filename: "{app}\\#{project.underscored_name}.exe"
|
104
|
+
Name: "{group}\\Uninstall #{project.name}"; Filename: "{uninstallexe}"
|
105
|
+
Name: "{commondesktop}\\#{project.name}"; Filename: "{app}\\#{project.underscored_name}.exe"; Tasks: desktopicon
|
106
|
+
|
107
|
+
[Tasks]
|
108
|
+
Name: desktopicon; Description: "Create a &desktop icon"; GroupDescription: "Additional icons:";
|
109
|
+
Name: desktopicon\\common; Description: "For all users"; GroupDescription: "Additional icons:"; Flags: exclusive
|
110
|
+
Name: desktopicon\\user; Description: "For the current user only"; GroupDescription: "Additional icons:"; Flags: exclusive unchecked
|
111
|
+
Name: quicklaunchicon; Description: "Create a &Quick Launch icon"; GroupDescription: "Additional icons:"; Flags: unchecked
|
112
|
+
END
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "releasy/builders/ocra_builder"
|
2
|
+
|
3
|
+
module Releasy
|
4
|
+
module Builders
|
5
|
+
# Creates a completely standalone Windows executable.
|
6
|
+
#
|
7
|
+
# @note Startup of the executable created by this build takes a couple of seconds longer than running from the other windows builds, as the files are extracted into a temporary directory each time it is run. It is recommended to build with _:windows_folder_ or _:windows_installer_ instead of this, unless you really need to distribute the application as a single file.
|
8
|
+
class WindowsStandalone < OcraBuilder
|
9
|
+
TYPE = :windows_standalone
|
10
|
+
DEFAULT_FOLDER_SUFFIX = "WIN32_EXE"
|
11
|
+
|
12
|
+
Builders.register self
|
13
|
+
|
14
|
+
protected
|
15
|
+
# Self-extracting standalone executable.
|
16
|
+
def generate_tasks
|
17
|
+
directory folder
|
18
|
+
|
19
|
+
file folder => project.files do
|
20
|
+
Rake::FileUtilsExt.verbose project.verbose?
|
21
|
+
|
22
|
+
project.exposed_files.each {|file| cp file, folder }
|
23
|
+
|
24
|
+
create_link_files folder
|
25
|
+
|
26
|
+
exec %[#{ocra_command} --output "#{folder}/#{executable_name}"]
|
27
|
+
end
|
28
|
+
|
29
|
+
desc "Build standalone exe #{project.version} [Ocra]"
|
30
|
+
task "build:windows:standalone" => folder
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
def executable_name; "#{project.underscored_name}.exe"; end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/releasy/cli.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'cri'
|
2
|
+
|
3
|
+
Releasy::Cli = Cri::Command.new_basic_root.modify do
|
4
|
+
name 'releasy'
|
5
|
+
usage 'releasy [options] [command] [options]'
|
6
|
+
summary 'helper for using the Releasy gem'
|
7
|
+
description 'Helper for using the Releasy gem to release packages'
|
8
|
+
end
|
9
|
+
|
10
|
+
Dir[File.expand_path("../cli/*.rb", __FILE__)].each do |file|
|
11
|
+
require file.chomp(File.extname(file))
|
12
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'cri'
|
2
|
+
require "releasy/archivers/exe"
|
3
|
+
|
4
|
+
command = 'install-sfx'
|
5
|
+
sfx_file = Releasy::Archivers::Exe::SFX_NAME
|
6
|
+
sfx_path = Releasy::Archivers::Exe::SFX_FILE
|
7
|
+
|
8
|
+
Releasy::Cli.define_command do
|
9
|
+
name command
|
10
|
+
usage "#{command} [options]"
|
11
|
+
aliases
|
12
|
+
summary "copy #{sfx_file} to 7z assets folder"
|
13
|
+
description "copy #{sfx_file} to 7z assets folder, after 7z has been installed (required only when not on a Windows system). Warning: This command will likely require 'sudo' to be able to write the file"
|
14
|
+
|
15
|
+
flag :h, :help, 'show help for this command' do |value, cmd|
|
16
|
+
puts cmd.help
|
17
|
+
exit 0
|
18
|
+
end
|
19
|
+
|
20
|
+
option :t, :output, "specify directory to copy to (default is to try to find it automatically)", :argument => :required
|
21
|
+
|
22
|
+
run do |options, args, cmd|
|
23
|
+
if Gem.win_platform?
|
24
|
+
puts "#{command}: only required when not on a Windows platform, since #{sfx_file} is included in the Windows version of 7z"
|
25
|
+
exit 0
|
26
|
+
end
|
27
|
+
|
28
|
+
exe_location = `which 7z`.strip
|
29
|
+
|
30
|
+
if exe_location.empty?
|
31
|
+
puts "#{command}: 7z (p7zip) not installed; install it before trying to use this command"
|
32
|
+
exit 0
|
33
|
+
end
|
34
|
+
|
35
|
+
assets_location = if options[:output]
|
36
|
+
options[:output]
|
37
|
+
elsif exe_location =~ %r[bin/7z$]
|
38
|
+
exe_location.sub %r[bin/7z$], "lib/p7zip"
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
|
43
|
+
destination_file = File.join(assets_location, sfx_file)
|
44
|
+
if File.exists? destination_file
|
45
|
+
puts "#{command}: #{destination_file} already exists; no need to install it again"
|
46
|
+
exit 0
|
47
|
+
else
|
48
|
+
begin
|
49
|
+
FileUtils.cp sfx_path, assets_location, :verbose => true
|
50
|
+
puts "#{sfx_file} copied to #{assets_location}"
|
51
|
+
rescue Errno::ENOENT, Errno::EACCES
|
52
|
+
|
53
|
+
if ENV["USER"] != "root"
|
54
|
+
command = %[sudo cp "#{sfx_path}" "#{assets_location}"]
|
55
|
+
puts "Copy failed, trying again as super-user:\n#{command}"
|
56
|
+
exec command
|
57
|
+
if File.exists? destination_file
|
58
|
+
puts "#{sfx_file} copied to #{assets_location}"
|
59
|
+
else
|
60
|
+
puts "Failed copy as super-user :("
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Releasy
|
2
|
+
# Wraps an object and redirects public methods to it, to allow for a terse, block-based API.
|
3
|
+
#
|
4
|
+
# * Safer alternative to running Object#instance_eval directly, since protected/private methods and instance variables are not exposed.
|
5
|
+
# * Less wordy than a system which operates like Object#tap (`object.tap {|o| o.fish = 5; o.run }`)
|
6
|
+
#
|
7
|
+
# A method call, #meth called on the wrapper will try to call #meth or #meth= on the owner, as appropriate.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # To create a DSL block for a given object.
|
11
|
+
# class Cheese
|
12
|
+
# attr_accessor :value
|
13
|
+
# attr_accessor :list
|
14
|
+
# def initialize; @value = 0; @list = []; end
|
15
|
+
# def invert; @list.reverse!; end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# object = Cheese.new
|
19
|
+
# Releasy::DSLWrapper.wrap object do
|
20
|
+
# list [1, 2, 3] # Calls object.list = [1, 2, 3]
|
21
|
+
# list << 4 # Calls object.list << 4
|
22
|
+
# value 5 # Calls object.value = 5
|
23
|
+
# value list.size # Calls object.value = object.list.size
|
24
|
+
# invert # Calls object.invert
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class DSLWrapper
|
28
|
+
# @return [Object] Object that the DSLWrapper object is redirecting to.
|
29
|
+
attr_reader :owner
|
30
|
+
|
31
|
+
class << self
|
32
|
+
# Synonym for .new.
|
33
|
+
alias_method :wrap, :new
|
34
|
+
end
|
35
|
+
|
36
|
+
# If passed a block, the DSLWrapper will #instance_eval it automatically.
|
37
|
+
#
|
38
|
+
# @param owner [Object] Object to redirect the public methods of.
|
39
|
+
def initialize(owner, &block)
|
40
|
+
@owner = owner
|
41
|
+
|
42
|
+
metaclass = class << self; self; end
|
43
|
+
|
44
|
+
(@owner.public_methods - Object.public_instance_methods).each do |target_method|
|
45
|
+
redirection_method = target_method.to_s.chomp('=').to_sym
|
46
|
+
|
47
|
+
metaclass.class_eval do
|
48
|
+
define_method redirection_method do |*args, &inner_block|
|
49
|
+
if @owner.respond_to? "#{redirection_method}=" and (args.any? or not @owner.respond_to? redirection_method)
|
50
|
+
# Has a setter and we are passing argument(s) or if we haven't got a corresponding getter.
|
51
|
+
@owner.send "#{redirection_method}=", *args, &inner_block
|
52
|
+
elsif @owner.respond_to? redirection_method
|
53
|
+
# We have a getter or general method
|
54
|
+
@owner.send redirection_method, *args, &inner_block
|
55
|
+
else
|
56
|
+
# Should never reach here, but let's be paranoid.
|
57
|
+
raise NoMethodError, "#{owner} does not have a public method, ##{redirection_method}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
instance_eval &block if block_given?
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
def method_missing(meth, *args, &block)
|
68
|
+
raise NoMethodError, "#{owner} does not have either public method, ##{meth} or ##{meth}="
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|