furoshiki 0.2.0 → 0.3.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.
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