releasy 0.2.0rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|