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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d0a17813563822aee7e3ff59e3ed2fd2751ee933
4
- data.tar.gz: eea188ad94a3bbfa03fbe232f8b6e8200dcaa85f
3
+ metadata.gz: 28682823686b0e99c68d8e89e8dda278cedcbd7f
4
+ data.tar.gz: 35eaf912683fd1e7f183d21cf55f998652f04348
5
5
  SHA512:
6
- metadata.gz: 42e0d0a9be95baf3ff80bda2606708f8240d43726fff18dc6165841278e6b8e1aa6d1f675ccd813e5813be076c4aa3f5a8a9ee18a9141379f980002d4876b19b
7
- data.tar.gz: f48f91a79590f621aaaed9ddef6dccb6e0999f9462453345087cd64b64fb3e3ad4206e05934a0caf33761173230567e5d63ebe51de4c95d367de19a45d667da5
6
+ metadata.gz: 98811a3dfc69cda04149f26361b3b31d7b8525bb79b7151db8bba034c88e4468e93ebff266f9472026741c27b2ca53b72179c015822bee0c16461b5060aa4cc0
7
+ data.tar.gz: dec72fe9319649dfb226b22946b3bb2678443b0e83a8f39411e02b346d4d805c639d22d69b9fc678eb7aa931d78b89606deb906c12f6058e9f759e550ce88c00
data/.gitignore CHANGED
@@ -21,4 +21,4 @@ pkg
21
21
  Gemfile.lock
22
22
 
23
23
  ## PROJECT::SPECIFIC
24
- spec/shoes/.furoshiki
24
+ spec/.furoshiki
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- jruby-1.7.16
1
+ jruby-1.7.18
data/Gemfile CHANGED
@@ -2,4 +2,3 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'shoes'
data/Rakefile CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'bundler'
2
2
  require 'rake/clean'
3
+ require 'rspec/core/rake_task'
3
4
  Bundler::GemHelper.install_tasks
4
5
 
5
6
  @types = %w[rb]
@@ -19,3 +20,7 @@ namespace :whitespace do
19
20
  end
20
21
 
21
22
  CLEAN.include('spec/shoes/.furoshiki', 'spec/shoes/sample_app')
23
+
24
+ RSpec::Core::RakeTask.new(:spec)
25
+
26
+ task :default => :spec
data/furoshiki.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  s.add_dependency 'rubyzip', '>= 1.0.0'
22
22
 
23
23
  s.add_development_dependency 'rake'
24
- s.add_development_dependency 'rspec'
24
+ s.add_development_dependency 'rspec', '>= 3.0.0'
25
25
  s.add_development_dependency 'rspec-its'
26
26
  s.add_development_dependency 'pry'
27
27
  end
@@ -0,0 +1,142 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+ require 'furoshiki/validator'
4
+ require 'furoshiki/warbler_extensions'
5
+ require 'furoshiki/util'
6
+
7
+ module Furoshiki
8
+ # Configuration for Furoshiki packagers.
9
+ #
10
+ # If your configuration uses hashes, the keys will always be
11
+ # symbols, even if you have created it with string keys. It's just
12
+ # easier that way.
13
+ #
14
+ # This is a value object. If you need to modify your configuration
15
+ # after initialization, dump it with #to_hash, make your changes,
16
+ # and instantiate a new object.
17
+ class Configuration
18
+ JAR_APP_TEMPLATE_URL = 'https://s3.amazonaws.com/net.wasnotrice.shoes/wrappers/shoes-app-template-0.0.1.zip'
19
+
20
+ include Util
21
+
22
+ # @param [Hash] config user options
23
+ # @param [String] working_dir directory in which to do packaging work
24
+ def initialize(config = {})
25
+ merge_config config
26
+ sanitize_config
27
+ define_readers
28
+ end
29
+
30
+ def shortname
31
+ @config[:shortname] || name.downcase.gsub(/\W+/, '')
32
+ end
33
+
34
+ def validator
35
+ @validator ||= validator_class.new(self)
36
+ end
37
+
38
+ def warbler_extensions
39
+ @warbler_extensions ||= warbler_extensions_class.new(self)
40
+ end
41
+
42
+ def to_hash
43
+ @config
44
+ end
45
+
46
+ def valid?
47
+ validator.valid?
48
+ end
49
+
50
+ def validate
51
+ return unless validator.respond_to? :reset_and_validate
52
+ validator.reset_and_validate
53
+ end
54
+
55
+ def error_message_list
56
+ validator.error_message_list
57
+ end
58
+
59
+ def ==(other)
60
+ super unless other.class == self.class && other.respond_to?(:to_hash)
61
+ @config == other.to_hash && working_dir == other.working_dir
62
+ end
63
+
64
+ def to_warbler_config
65
+ warbler_config = nil
66
+ Dir.chdir working_dir do
67
+ warbler_config = Warbler::Config.new do |config|
68
+ config.jar_name = self.shortname
69
+ specs = self.gems.map do |gem|
70
+ # This is rather a hack as Gem::Specification.find_by_name(gem)
71
+ # seems to break Travis.
72
+ # See: https://github.com/shoes/shoes4/pull/989#issuecomment-68170746
73
+ Gem::Specification.find_all_by_name(gem).first
74
+ end
75
+ dependencies = specs.map { |s| s.runtime_dependencies }.flatten
76
+ (specs + dependencies).uniq.each { |g| config.gems << g }
77
+ ignore = self.ignore.map do |f|
78
+ path = f.to_s
79
+ children = Dir.glob("#{path}/**/*") if File.directory?(path)
80
+ [path, *children]
81
+ end.flatten
82
+ config.excludes.add FileList.new(ignore.flatten).pathmap(config.pathmaps.application.first)
83
+ config.gem_excludes += [/^samples/, /^examples/, /^test/, /^spec/]
84
+
85
+ warbler_extensions.customize(config) if warbler_extensions.respond_to? :customize
86
+ end
87
+ end
88
+ warbler_config
89
+ end
90
+
91
+ private
92
+ def warbler_extensions_class
93
+ @warbler_extensions_class ||= @config.fetch(:warbler_extensions)
94
+ end
95
+
96
+ def validator_class
97
+ @validator_class ||= @config.fetch(:validator)
98
+ end
99
+
100
+ # Overwrite defaults with supplied config
101
+ def merge_config(config)
102
+ defaults = {
103
+ name: 'Ruby App',
104
+ version: '0.0.0',
105
+ release: 'Rookie',
106
+ ignore: 'pkg',
107
+ # TODO: Establish these default icons and paths. These would be
108
+ # default icons for generic Ruby apps.
109
+ icons: {
110
+ #osx: 'path/to/default/App.icns',
111
+ #gtk: 'path/to/default/app.png',
112
+ #win32: 'path/to/default/App.ico',
113
+ },
114
+ template_urls: {
115
+ jar_app: JAR_APP_TEMPLATE_URL,
116
+ },
117
+ validator: Furoshiki::Validator,
118
+ warbler_extensions: Furoshiki::WarblerExtensions,
119
+ working_dir: Dir.pwd,
120
+ }
121
+
122
+ @config = merge_with_symbolized_keys(defaults, config)
123
+ end
124
+
125
+ # Ensure these keys have workable values
126
+ def sanitize_config
127
+ [:ignore, :gems].each { |k| @config[k] = Array(@config[k]) }
128
+ @config[:working_dir] = Pathname.new(@config[:working_dir])
129
+ end
130
+
131
+ # Define reader for each top-level config key (except those already defined
132
+ # explicitly)
133
+ def define_readers
134
+ metaclass = class << self; self; end
135
+ @config.keys.reject {|k| self.respond_to?(k) }.each do |k|
136
+ metaclass.send(:define_method, k) do
137
+ @config[k]
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,41 @@
1
+ require 'warbler'
2
+ require 'warbler/traits/furoshiki'
3
+
4
+ module Furoshiki
5
+ class Jar
6
+ # @param [Furoshiki::Shoes::Configuration] config user configuration
7
+ def initialize(config)
8
+ @furoshiki_config = config
9
+
10
+ unless config.valid?
11
+ raise Furoshiki::ConfigurationError, "Invalid configuration.\n#{config.error_message_list}"
12
+ end
13
+
14
+ @config = @furoshiki_config.to_warbler_config
15
+ end
16
+
17
+ def package(dir = default_dir)
18
+ Dir.chdir working_dir do
19
+ jar = Warbler::Jar.new
20
+ jar.apply @config
21
+ package_dir = dir.relative_path_from(working_dir)
22
+ package_dir.mkpath
23
+ path = package_dir.join(filename).to_s
24
+ jar.create path
25
+ File.expand_path path
26
+ end
27
+ end
28
+
29
+ def default_dir
30
+ working_dir.join 'pkg'
31
+ end
32
+
33
+ def filename
34
+ "#{@config.jar_name}.#{@config.jar_extension}"
35
+ end
36
+
37
+ def working_dir
38
+ @furoshiki_config.working_dir
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,225 @@
1
+ require 'furoshiki/exceptions'
2
+ require 'furoshiki/zip/directory'
3
+ require 'furoshiki/jar'
4
+ require 'fileutils'
5
+ require 'plist'
6
+ require 'open-uri'
7
+ require 'net/http'
8
+
9
+ module Furoshiki
10
+ class JarApp
11
+ include FileUtils
12
+
13
+ # @param [Furoshiki::Shoes::Configuration] config user configuration
14
+ def initialize(config)
15
+ @config = config
16
+
17
+ unless config.valid?
18
+ raise Furoshiki::ConfigurationError, "Invalid configuration.\n#{config.error_message_list}"
19
+ end
20
+
21
+ home = ENV['FUROSHIKI_HOME'] || Dir.home
22
+ @cache_dir = Pathname.new(home).join('.furoshiki', 'cache')
23
+ @default_package_dir = working_dir.join('pkg')
24
+ @package_dir = default_package_dir
25
+ @default_template_path = cache_dir.join(template_filename)
26
+ @template_path = default_template_path
27
+ @tmp = @package_dir.join('tmp')
28
+ end
29
+
30
+ # @return [Pathname] default package directory: ./pkg
31
+ attr_reader :default_package_dir
32
+
33
+ # @return [Pathname] package directory
34
+ attr_accessor :package_dir
35
+
36
+ # @return [Pathname] default path to .app template
37
+ attr_reader :default_template_path
38
+
39
+ # @return [Pathname] path to .app template
40
+ attr_accessor :template_path
41
+
42
+ # @return [Pathname] cache directory
43
+ attr_reader :cache_dir
44
+
45
+ attr_reader :config
46
+
47
+ attr_reader :tmp
48
+
49
+ def package
50
+ remove_tmp
51
+ create_tmp
52
+ cache_template
53
+ extract_template
54
+ inject_icon
55
+ inject_config
56
+ jar_path = ensure_jar_exists
57
+ inject_jar jar_path
58
+ move_to_package_dir tmp_app_path
59
+ tweak_permissions
60
+ rescue => e
61
+ raise e
62
+ ensure
63
+ remove_tmp
64
+ end
65
+
66
+ private
67
+ def create_tmp
68
+ tmp.mkpath
69
+ end
70
+
71
+ def remove_tmp
72
+ tmp.rmtree if tmp.exist?
73
+ end
74
+
75
+ def cache_template
76
+ cache_dir.mkpath unless cache_dir.exist?
77
+ download_template unless template_path.size?
78
+ end
79
+
80
+ def template_basename
81
+ 'shoes-app-template'
82
+ end
83
+
84
+ def template_extension
85
+ '.zip'
86
+ end
87
+
88
+ def template_filename
89
+ "#{template_basename}#{template_extension}"
90
+ end
91
+
92
+ def latest_template_version
93
+ '0.0.1'
94
+ end
95
+
96
+ def download_template
97
+ download remote_template_url, template_path
98
+ end
99
+
100
+ def download(remote_url, local_path)
101
+ download_following_redirects remote_url, local_path
102
+ end
103
+
104
+ def download_following_redirects(remote_url, local_path, redirect_limit = 5)
105
+ if redirect_limit == 0
106
+ raise Furoshiki::DownloadError,
107
+ "Too many redirects trying to reach #{remote_url}"
108
+ end
109
+
110
+ uri = URI(remote_url)
111
+ Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
112
+ request = Net::HTTP::Get.new(uri.request_uri)
113
+ http.request request do |response|
114
+ case response
115
+ when Net::HTTPSuccess then
116
+ warn "Downloading #{remote_url} to #{local_path}"
117
+ open(local_path, 'wb') do |file|
118
+ response.read_body do |chunk|
119
+ file.write chunk
120
+ end
121
+ end
122
+ when Net::HTTPRedirection then
123
+ location = response['location']
124
+ warn "Redirected to #{location}"
125
+ download_following_redirects(location, local_path, redirect_limit - 1)
126
+ else
127
+ raise Furoshiki::DownloadError, "Couldn't download app template at #{remote_url}. #{response.value}"
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ def downloads_url
134
+ "http://shoesrb.com/downloads"
135
+ end
136
+
137
+ def remote_template_url
138
+ config.template_urls.fetch(:jar_app)
139
+ end
140
+
141
+ def move_to_package_dir(path)
142
+ dest = package_dir.join(path.basename)
143
+ dest.rmtree if dest.exist?
144
+ mv path.to_s, dest
145
+ end
146
+
147
+ def ensure_jar_exists
148
+ jar = Jar.new(@config)
149
+ path = tmp.join(jar.filename)
150
+ jar.package(tmp) unless File.exist?(path)
151
+ path
152
+ end
153
+
154
+ # Injects JAR into APP. The JAR should be the only item in the
155
+ # Contents/Java directory. If this directory contains more than one
156
+ # JAR, the "first" one gets run, which may not be what we want.
157
+ #
158
+ # @param [Pathname, String] jar_path the location of the JAR to inject
159
+ def inject_jar(jar_path)
160
+ jar_dir = tmp_app_path.join('Contents/Java')
161
+ jar_dir.rmtree
162
+ jar_dir.mkdir
163
+ cp Pathname.new(jar_path), jar_dir
164
+ end
165
+
166
+ def extract_template
167
+ raise IOError, "Couldn't find app template at #{template_path}." unless template_path.size?
168
+ extracted_app = nil
169
+
170
+ ::Zip::File.open(template_path) do |zip_file|
171
+ zip_file.each do |entry|
172
+ extracted_app = template_path.join(entry.name) if Pathname.new(entry.name).extname == '.app'
173
+ p = tmp.join(entry.name)
174
+ p.dirname.mkpath
175
+ entry.extract(p)
176
+ end
177
+ end
178
+ mv tmp.join(extracted_app.basename.to_s), tmp_app_path
179
+ end
180
+
181
+ def inject_config
182
+ plist = tmp_app_path.join 'Contents/Info.plist'
183
+ template = Plist.parse_xml(plist)
184
+ template['CFBundleIdentifier'] = "com.hackety.shoes.#{config.shortname}"
185
+ template['CFBundleDisplayName'] = config.name
186
+ template['CFBundleName'] = config.name
187
+ template['CFBundleVersion'] = config.version
188
+ template['CFBundleIconFile'] = Pathname.new(config.icons[:osx]).basename.to_s if config.icons[:osx]
189
+ File.open(plist, 'w') { |f| f.write template.to_plist }
190
+ end
191
+
192
+ def inject_icon
193
+ if config.icons[:osx]
194
+ icon_path = working_dir.join(config.icons[:osx])
195
+ raise IOError, "Couldn't find app icon at #{icon_path}" unless icon_path.exist?
196
+ resources_dir = tmp_app_path.join('Contents/Resources')
197
+ cp icon_path, resources_dir.join(icon_path.basename)
198
+ end
199
+ end
200
+
201
+ def tweak_permissions
202
+ executable_path.chmod 0755
203
+ end
204
+
205
+ def app_name
206
+ "#{config.name}.app"
207
+ end
208
+
209
+ def tmp_app_path
210
+ tmp.join app_name
211
+ end
212
+
213
+ def app_path
214
+ package_dir.join app_name
215
+ end
216
+
217
+ def executable_path
218
+ app_path.join('Contents/MacOS/JavaAppLauncher')
219
+ end
220
+
221
+ def working_dir
222
+ config.working_dir
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,27 @@
1
+ module Furoshiki
2
+ module Util
3
+ # Ensure symbol keys, even in nested hashes
4
+ #
5
+ # @param [Hash] config the hash to set (key: value) on
6
+ # @param [#to_sym] k the key
7
+ # @param [Object] v the value
8
+ # @return [Hash] an updated hash
9
+ def deep_set_symbol_key(hash, key, value)
10
+ if value.kind_of? Hash
11
+ hash[key.to_sym] = value.inject({}) { |inner_hash, (inner_key, inner_value)| deep_set_symbol_key(inner_hash, inner_key, inner_value) }
12
+ else
13
+ hash[key.to_sym] = value
14
+ end
15
+ hash
16
+ end
17
+
18
+ def deep_symbolize_keys(hash)
19
+ merge_with_symbolized_keys({}, hash)
20
+ end
21
+
22
+ # Assumes that defaults already has symbolized keys
23
+ def merge_with_symbolized_keys(defaults, hash)
24
+ hash.inject(defaults) { |symbolized, (k, v)| deep_set_symbol_key(symbolized, k, v) }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,42 @@
1
+ module Furoshiki
2
+ class Validator
3
+ def initialize(config)
4
+ @config = config
5
+ @errors = []
6
+ end
7
+
8
+ attr_reader :config
9
+
10
+ def valid?
11
+ reset_and_validate
12
+ return errors.empty?
13
+ end
14
+
15
+ def reset_and_validate
16
+ @errors.clear
17
+ validate if respond_to? :validate
18
+ end
19
+
20
+ def errors
21
+ @errors.dup
22
+ end
23
+
24
+ def error_message_list
25
+ @errors.map {|m| " - #{m}"}.join("\n")
26
+ end
27
+
28
+ def working_dir
29
+ @config.working_dir
30
+ end
31
+
32
+ private
33
+ def add_error(message)
34
+ @errors << message
35
+ end
36
+
37
+ def add_missing_file_error(value, description)
38
+ message = "#{description} configured as '#{value}', but couldn't find file at #{working_dir.join(value.to_s)}"
39
+ add_error(message)
40
+ end
41
+ end
42
+ end
@@ -1,4 +1,4 @@
1
1
  module Furoshiki
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
4
4
 
@@ -0,0 +1,12 @@
1
+ module Furoshiki
2
+ class WarblerExtensions
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ # Override to customize config
8
+ def customize(warbler_config)
9
+ warbler_config
10
+ end
11
+ end
12
+ end
data/lib/furoshiki.rb CHANGED
@@ -1,3 +1,4 @@
1
- puts "OMG, nothing here!"
2
- puts ""
3
- puts "You probably want to `require 'furoshiki/shoes'`"
1
+ require 'furoshiki/configuration'
2
+ require 'furoshiki/exceptions'
3
+ require 'furoshiki/jar'
4
+ require 'furoshiki/jar_app'
@@ -0,0 +1,31 @@
1
+ require 'warbler'
2
+ require 'warbler/traits'
3
+
4
+ module Warbler
5
+ module Traits
6
+ # Hack to stop bundler injecting itself
7
+ class Bundler
8
+ def self.detect?
9
+ false
10
+ end
11
+ end
12
+
13
+ class Furoshiki
14
+ include ::Warbler::Trait
15
+ include PathmapHelper
16
+
17
+ def self.detect?
18
+ true
19
+ end
20
+
21
+ def self.requires?(trait)
22
+ [Traits::Jar].include? trait
23
+ end
24
+
25
+ def update_archive(jar)
26
+ # Not sure why Warbler doesn't do this automatically
27
+ jar.files.delete_if { |k, v| @config.excludes.include? k }
28
+ end
29
+ end
30
+ end
31
+ end