furoshiki 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.ruby-version +1 -1
  4. data/Gemfile +0 -1
  5. data/Rakefile +5 -0
  6. data/furoshiki.gemspec +1 -1
  7. data/lib/furoshiki/configuration.rb +142 -0
  8. data/lib/furoshiki/jar.rb +41 -0
  9. data/lib/furoshiki/jar_app.rb +225 -0
  10. data/lib/furoshiki/util.rb +27 -0
  11. data/lib/furoshiki/validator.rb +42 -0
  12. data/lib/furoshiki/version.rb +1 -1
  13. data/lib/furoshiki/warbler_extensions.rb +12 -0
  14. data/lib/furoshiki.rb +4 -3
  15. data/lib/warbler/traits/furoshiki.rb +31 -0
  16. data/spec/{shoes/swt_app_spec.rb → app_spec.rb} +29 -22
  17. data/spec/configuration_spec.rb +12 -0
  18. data/spec/fixtures/config.yaml +1 -0
  19. data/spec/{shoes → fixtures}/test_app/app.yaml +0 -0
  20. data/spec/{shoes → fixtures}/test_app/bin/hello_world +0 -0
  21. data/spec/{shoes → fixtures}/test_app/dir_to_ignore/file_to_ignore.txt +0 -0
  22. data/spec/{shoes → fixtures}/test_app/img/boots.icns +0 -0
  23. data/spec/{shoes → fixtures}/test_app/img/boots.ico +0 -0
  24. data/spec/{shoes → fixtures}/test_app/img/boots_512x512x32.png +0 -0
  25. data/spec/{shoes → fixtures}/test_app/sibling.rb +0 -0
  26. data/spec/fixtures/test_project/bin/hello.rb +1 -0
  27. data/spec/fixtures/test_project/dir_to_ignore/file_to_ignore.txt +1 -0
  28. data/spec/{shoes/swt_jar_spec.rb → jar_spec.rb} +25 -12
  29. data/spec/spec_helper.rb +51 -0
  30. data/spec/support/shared_config.rb +19 -0
  31. data/spec/support/shared_zip.rb +1 -1
  32. data/spec/util_spec.rb +57 -0
  33. metadata +73 -45
  34. data/lib/furoshiki/shoes/configuration.rb +0 -184
  35. data/lib/furoshiki/shoes/swt_app.rb +0 -230
  36. data/lib/furoshiki/shoes/swt_jar.rb +0 -67
  37. data/lib/furoshiki/shoes.rb +0 -15
  38. data/lib/warbler/traits/shoes.rb +0 -51
  39. data/spec/shoes/configuration_spec.rb +0 -198
  40. data/spec/shoes/spec_helper.rb +0 -70
  41. data/spec/shoes/support/shared_config.rb +0 -8
@@ -1,184 +0,0 @@
1
- require 'pathname'
2
- require 'yaml'
3
-
4
- module Furoshiki
5
- module Shoes
6
- # Configuration for Shoes packagers.
7
- #
8
- # @example
9
- # config_file = '/path/to/app.yaml'
10
- # config = Shoes::Package::Configuration.load(config_file)
11
- #
12
- # If your configuration uses hashes, the keys will always be
13
- # symbols, even if you have created it with string keys. It's just
14
- # easier that way.
15
- #
16
- # This is a value object. If you need to modify your configuration
17
- # after initialization, dump it with #to_hash, make your changes,
18
- # and instantiate a new object.
19
- class Configuration
20
- # Convenience method for loading config from a file. Note that you
21
- # can pass four kinds of paths to the loader. Given the following
22
- # file structure:
23
- #
24
- # ├── a
25
- # │   ├── app.yaml
26
- # │   └── shoes-app-a.rb
27
- # └── b
28
- # └── shoes-app-b.rb
29
- #
30
- # To package an app that has an `app.yaml`, like `shoes-app-a.rb`,
31
- # you can call the loader with any of:
32
- #
33
- # - a/app.yaml
34
- # - a
35
- # - a/shoes-app-a.rb
36
- #
37
- # These will all find and use your configuration in `a/app.yaml`.
38
- # To package an app that does not have an `app.yaml`, like
39
- # `b/shoes-app-b.rb`, you must call the loader with the path of
40
- # the script itself. Note that without an `app.yaml`, you will
41
- # only bundle a single file, and your app will simply use the
42
- # Shoes app icon.
43
- #
44
- # @overload load(path)
45
- # @param [String] path location of the app's 'app.yaml'
46
- # @overload load(path)
47
- # @param [String] path location of the directory that
48
- # contains the app's 'app.yaml'
49
- # @overload load(path)
50
- # @param [String] path location of the app
51
- def self.load(path = 'app.yaml')
52
- pathname = Pathname.new(path)
53
- app_yaml = Pathname.new('app.yaml')
54
-
55
- dummy_file = Struct.new(:read)
56
-
57
- if pathname.basename == app_yaml
58
- file, dir = pathname, pathname.dirname
59
- elsif pathname.directory?
60
- file, dir = pathname.join(app_yaml), pathname
61
- elsif pathname.file? && pathname.parent.children.include?(pathname.parent.join app_yaml)
62
- file, dir = pathname.parent.join(app_yaml), pathname.parent
63
- else
64
- # Can't find any 'app.yaml', so assume we just want to wrap
65
- # this file. If it exists, load default options. If not, let
66
- # the filesystem raise an error.
67
- default_options = {
68
- run: pathname.basename.to_s,
69
- name: pathname.basename(pathname.extname).to_s.gsub(/\W/, '-')
70
- }.to_yaml
71
- options = pathname.exist? ? default_options : pathname
72
- file = dummy_file.new(options)
73
- dir = pathname.parent
74
- end
75
- config = YAML.load(file.read)
76
- config[:working_dir] = dir
77
- new(config)
78
- end
79
-
80
- # @param [Hash] config user options
81
- # @param [String] working_dir directory in which do packaging work
82
- def initialize(config = {})
83
- defaults = {
84
- name: 'Shoes App',
85
- version: '0.0.0',
86
- release: 'Rookie',
87
- run: nil,
88
- ignore: 'pkg',
89
- icons: {
90
- #osx: 'path/to/default/App.icns',
91
- #gtk: 'path/to/default/app.png',
92
- #win32: 'path/to/default/App.ico',
93
- },
94
- dmg: {
95
- ds_store: 'path/to/default/.DS_Store',
96
- background: 'path/to/default/background.png'
97
- },
98
- working_dir: Dir.pwd
99
- }
100
-
101
- # Overwrite defaults with supplied config
102
- @config = config.inject(defaults) { |c, (k, v)| set_symbol_key c, k, v }
103
-
104
- # Ensure that we always have what we need
105
- [:ignore, :gems].each { |k| @config[k] = Array(@config[k]) }
106
- @config[:gems] << 'shoes'
107
- @config[:working_dir] = Pathname.new(@config[:working_dir])
108
-
109
- # Define reader for each key (#shortname defined below)
110
- metaclass = class << self; self; end
111
- @config.keys.reject {|k| k == :shortname}.each do |k|
112
- metaclass.send(:define_method, k) do
113
- @config[k]
114
- end
115
- end
116
-
117
- @errors = []
118
- end
119
-
120
- def shortname
121
- @config[:shortname] || @config[:name].downcase.gsub(/\W+/, '')
122
- end
123
-
124
- def to_hash
125
- @config
126
- end
127
-
128
- def validate
129
- unless @config[:run] && working_dir.join(@config[:run]).exist?
130
- add_missing_file_error(@config[:run], "Run file")
131
- end
132
-
133
- if @config[:icons][:osx] && !working_dir.join(@config[:icons][:osx]).exist?
134
- add_missing_file_error(@config[:icons][:osx], "OS X icon file")
135
- end
136
- end
137
-
138
- def valid?
139
- validate
140
- return errors.empty?
141
- end
142
-
143
- def errors
144
- @errors.dup
145
- end
146
-
147
- def error_message_list
148
- @errors.map {|m| " - #{m}"}.join("\n")
149
- end
150
-
151
- def ==(other)
152
- super unless other.class == self.class && other.respond_to?(:to_hash)
153
- @config == other.to_hash &&
154
- working_dir == other.working_dir &&
155
- errors == other.errors
156
- end
157
-
158
- private
159
- # Ensure symbol keys, even in nested hashes
160
- #
161
- # @param [Hash] config the hash to set (key: value) on
162
- # @param [#to_sym] k the key
163
- # @param [Object] v the value
164
- # @return [Hash] an updated hash
165
- def set_symbol_key(config, k, v)
166
- if v.kind_of? Hash
167
- config[k.to_sym] = v.inject({}) { |hash, (k, v)| set_symbol_key(hash, k, v) }
168
- else
169
- config[k.to_sym] = v
170
- end
171
- config
172
- end
173
-
174
- def add_error(message)
175
- @errors << message
176
- end
177
-
178
- def add_missing_file_error(value, description)
179
- message = "#{description} configured as '#{value}', but couldn't find file at #{working_dir.join(value.to_s)}"
180
- add_error(message)
181
- end
182
- end
183
- end
184
- end
@@ -1,230 +0,0 @@
1
- require 'furoshiki/exceptions'
2
- require 'furoshiki/shoes/configuration'
3
- require 'furoshiki/zip/directory'
4
- require 'furoshiki/shoes/swt_jar'
5
- require 'fileutils'
6
- require 'plist'
7
- require 'open-uri'
8
- require 'net/http'
9
-
10
- module Furoshiki
11
- module Shoes
12
- class SwtApp
13
- include FileUtils
14
-
15
- REMOTE_TEMPLATE_URL = 'https://s3.amazonaws.com/net.wasnotrice.shoes/wrappers/shoes-app-template-0.0.1.zip'
16
-
17
- # @param [Shoes::Package::Configuration] config user configuration
18
- def initialize(config)
19
- @config = config
20
-
21
- unless config.valid?
22
- raise Furoshiki::ConfigurationError, "Invalid configuration.\n#{config.error_message_list}"
23
- end
24
-
25
- home = ENV['FUROSHIKI_HOME'] || Dir.home
26
- @cache_dir = Pathname.new(home).join('.furoshiki', 'cache')
27
- @default_package_dir = working_dir.join('pkg')
28
- @package_dir = default_package_dir
29
- @default_template_path = cache_dir.join(template_filename)
30
- @template_path = default_template_path
31
- @tmp = @package_dir.join('tmp')
32
- end
33
-
34
- # @return [Pathname] default package directory: ./pkg
35
- attr_reader :default_package_dir
36
-
37
- # @return [Pathname] package directory
38
- attr_accessor :package_dir
39
-
40
- # @return [Pathname] default path to .app template
41
- attr_reader :default_template_path
42
-
43
- # @return [Pathname] path to .app template
44
- attr_accessor :template_path
45
-
46
- # @return [Pathname] cache directory
47
- attr_reader :cache_dir
48
-
49
- attr_reader :config
50
-
51
- attr_reader :tmp
52
-
53
- def package
54
- remove_tmp
55
- create_tmp
56
- cache_template
57
- extract_template
58
- inject_icon
59
- inject_config
60
- jar_path = ensure_jar_exists
61
- inject_jar jar_path
62
- move_to_package_dir tmp_app_path
63
- tweak_permissions
64
- rescue => e
65
- raise e
66
- ensure
67
- remove_tmp
68
- end
69
-
70
- def create_tmp
71
- tmp.mkpath
72
- end
73
-
74
- def remove_tmp
75
- tmp.rmtree if tmp.exist?
76
- end
77
-
78
- def cache_template
79
- cache_dir.mkpath unless cache_dir.exist?
80
- download_template unless template_path.size?
81
- end
82
-
83
- def template_basename
84
- 'shoes-app-template'
85
- end
86
-
87
- def template_extension
88
- '.zip'
89
- end
90
-
91
- def template_filename
92
- "#{template_basename}#{template_extension}"
93
- end
94
-
95
- def latest_template_version
96
- '0.0.1'
97
- end
98
-
99
- def download_template
100
- download remote_template_url, template_path
101
- end
102
-
103
- def download(remote_url, local_path)
104
- download_following_redirects remote_url, local_path
105
- end
106
-
107
- def download_following_redirects(remote_url, local_path, redirect_limit = 5)
108
- if redirect_limit == 0
109
- raise Furoshiki::DownloadError,
110
- "Too many redirects trying to reach #{remote_url}"
111
- end
112
-
113
- uri = URI(remote_url)
114
- Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
115
- request = Net::HTTP::Get.new(uri.request_uri)
116
- http.request request do |response|
117
- case response
118
- when Net::HTTPSuccess then
119
- warn "Downloading #{remote_url} to #{local_path}"
120
- open(local_path, 'wb') do |file|
121
- response.read_body do |chunk|
122
- file.write chunk
123
- end
124
- end
125
- when Net::HTTPRedirection then
126
- location = response['location']
127
- warn "Redirected to #{location}"
128
- download_following_redirects(location, local_path, redirect_limit - 1)
129
- else
130
- raise Furoshiki::DownloadError, "Couldn't download app template at #{remote_url}. #{response.value}"
131
- end
132
- end
133
- end
134
- end
135
-
136
- def downloads_url
137
- "http://shoesrb.com/downloads"
138
- end
139
-
140
- def remote_template_url
141
- #"#{downloads_url}/#{template_basename}-#{latest_template_version}#{template_extension}"
142
- REMOTE_TEMPLATE_URL
143
- end
144
-
145
- def move_to_package_dir(path)
146
- dest = package_dir.join(path.basename)
147
- dest.rmtree if dest.exist?
148
- mv path.to_s, dest
149
- end
150
-
151
- def ensure_jar_exists
152
- jar = SwtJar.new(@config)
153
- path = tmp.join(jar.filename)
154
- jar.package(tmp) unless File.exist?(path)
155
- path
156
- end
157
-
158
- # Injects JAR into APP. The JAR should be the only item in the
159
- # Contents/Java directory. If this directory contains more than one
160
- # JAR, the "first" one gets run, which may not be what we want.
161
- #
162
- # @param [Pathname, String] jar_path the location of the JAR to inject
163
- def inject_jar(jar_path)
164
- jar_dir = tmp_app_path.join('Contents/Java')
165
- jar_dir.rmtree
166
- jar_dir.mkdir
167
- cp Pathname.new(jar_path), jar_dir
168
- end
169
-
170
- def extract_template
171
- raise IOError, "Couldn't find app template at #{template_path}." unless template_path.size?
172
- extracted_app = nil
173
-
174
- ::Zip::File.open(template_path) do |zip_file|
175
- zip_file.each do |entry|
176
- extracted_app = template_path.join(entry.name) if Pathname.new(entry.name).extname == '.app'
177
- p = tmp.join(entry.name)
178
- p.dirname.mkpath
179
- entry.extract(p)
180
- end
181
- end
182
- mv tmp.join(extracted_app.basename.to_s), tmp_app_path
183
- end
184
-
185
- def inject_config
186
- plist = tmp_app_path.join 'Contents/Info.plist'
187
- template = Plist.parse_xml(plist)
188
- template['CFBundleIdentifier'] = "com.hackety.shoes.#{config.shortname}"
189
- template['CFBundleDisplayName'] = config.name
190
- template['CFBundleName'] = config.name
191
- template['CFBundleVersion'] = config.version
192
- template['CFBundleIconFile'] = Pathname.new(config.icons[:osx]).basename.to_s if config.icons[:osx]
193
- File.open(plist, 'w') { |f| f.write template.to_plist }
194
- end
195
-
196
- def inject_icon
197
- if config.icons[:osx]
198
- icon_path = working_dir.join(config.icons[:osx])
199
- raise IOError, "Couldn't find app icon at #{icon_path}" unless icon_path.exist?
200
- resources_dir = tmp_app_path.join('Contents/Resources')
201
- cp icon_path, resources_dir.join(icon_path.basename)
202
- end
203
- end
204
-
205
- def tweak_permissions
206
- executable_path.chmod 0755
207
- end
208
-
209
- def app_name
210
- "#{config.name}.app"
211
- end
212
-
213
- def tmp_app_path
214
- tmp.join app_name
215
- end
216
-
217
- def app_path
218
- package_dir.join app_name
219
- end
220
-
221
- def executable_path
222
- app_path.join('Contents/MacOS/JavaAppLauncher')
223
- end
224
-
225
- def working_dir
226
- config.working_dir
227
- end
228
- end
229
- end
230
- end
@@ -1,67 +0,0 @@
1
- require 'furoshiki/shoes/configuration'
2
- require 'warbler'
3
- require 'warbler/traits/shoes'
4
-
5
- module Furoshiki
6
- module Shoes
7
- class SwtJar
8
- # @param [Furoshiki::Shoes::Configuration] config user configuration
9
- def initialize(config = nil)
10
- @shoes_config = config || Furoshiki::Shoes::Configuration.load
11
-
12
- unless config.valid?
13
- raise Furoshiki::ConfigurationError, "Invalid configuration.\n#{config.error_message_list}"
14
- end
15
-
16
- Dir.chdir working_dir do
17
- @config = Warbler::Config.new do |config|
18
- config.jar_name = @shoes_config.shortname
19
- config.pathmaps.application = ['shoes-app/%p']
20
- specs = @shoes_config.gems.map { |g| Gem::Specification.find_by_name(g) }
21
- dependencies = specs.map { |s| s.runtime_dependencies }.flatten
22
- (specs + dependencies).uniq.each { |g| config.gems << g }
23
- ignore = @shoes_config.ignore.map do |f|
24
- path = f.to_s
25
- children = Dir.glob("#{path}/**/*") if File.directory?(path)
26
- [path, *children]
27
- end.flatten
28
- config.excludes.add FileList.new(ignore.flatten).pathmap(config.pathmaps.application.first)
29
- config.gem_excludes += [/^samples/, /^examples/, /^test/, /^spec/]
30
- end
31
- @config.extend ShoesWarblerConfig
32
- @config.run = @shoes_config.run.split(/\s/).first
33
- end
34
- end
35
-
36
- def package(dir = default_dir)
37
- Dir.chdir working_dir do
38
- jar = Warbler::Jar.new
39
- jar.apply @config
40
- package_dir = dir.relative_path_from(working_dir)
41
- package_dir.mkpath
42
- path = package_dir.join(filename).to_s
43
- jar.create path
44
- File.expand_path path
45
- end
46
- end
47
-
48
- def default_dir
49
- working_dir.join 'pkg'
50
- end
51
-
52
- def filename
53
- "#{@config.jar_name}.#{@config.jar_extension}"
54
- end
55
-
56
- def working_dir
57
- @shoes_config.working_dir
58
- end
59
-
60
- private
61
- # Adds Shoes-specific functionality to the Warbler Config
62
- module ShoesWarblerConfig
63
- attr_accessor :run
64
- end
65
- end
66
- end
67
- end
@@ -1,15 +0,0 @@
1
- require 'furoshiki/shoes/swt_jar'
2
- require 'furoshiki/shoes/swt_app'
3
-
4
- module Furoshiki
5
- module Shoes
6
- def self.new(backend, wrapper, config)
7
- class_name = class_name_for(backend, wrapper)
8
- self.const_get(class_name).new(config)
9
- end
10
-
11
- def self.class_name_for(backend, wrapper)
12
- [backend, wrapper].map { |name| name.to_s.capitalize }.join
13
- end
14
- end
15
- end
@@ -1,51 +0,0 @@
1
- require 'furoshiki/shoes/configuration'
2
-
3
- module Warbler
4
- module Traits
5
- # Hack to control the executable
6
- class NoGemspec
7
- def update_archive(jar); end
8
- end
9
-
10
- class Shoes
11
- include Trait
12
- include PathmapHelper
13
-
14
- def self.detect?
15
- #File.exist? "app.yaml"
16
- true
17
- end
18
-
19
- def self.requires?(trait)
20
- # Actually, it would be better to dump the NoGemspec trait, but since
21
- # we can't do that, we can at least make sure that this trait gets
22
- # processed later by declaring that it requires NoGemspec.
23
- [Traits::Jar, Traits::NoGemspec].include? trait
24
- end
25
-
26
- def after_configure
27
- config.init_contents << StringIO.new("require 'shoes'\nShoes.configuration.backend = :swt\n")
28
- end
29
-
30
- def update_archive(jar)
31
- # Not sure why Warbler doesn't do this automatically
32
- jar.files.delete_if { |k, v| @config.excludes.include? k }
33
- add_main_rb(jar, apply_pathmaps(config, default_executable, :application))
34
- end
35
-
36
- # Uses the `@config.run` if it exists. Otherwise, looks in the
37
- # application's `bin` directory for an executable with the same name as
38
- # the jar. If this also fails, defaults to the first executable (alphabetically) in the
39
- # applications `bin` directory.
40
- #
41
- # @return [String] filename of the executable to run
42
- def default_executable
43
- return @config.run if @config.run
44
- exes = Dir['bin/*'].sort
45
- exe = exes.grep(/#{config.jar_name}/).first || exes.first
46
- raise "No executable script found" unless exe
47
- exe
48
- end
49
- end
50
- end
51
- end