wake-assets 0.2.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.
data/README.md ADDED
@@ -0,0 +1,126 @@
1
+ # Wake::Assets
2
+
3
+ This module helps you render links to assets managed using
4
+ [wake](http://github.com/jcoglan/wake). It's easy to set up and works with any
5
+ Ruby web framework.
6
+
7
+
8
+ ## Installation
9
+
10
+ ```
11
+ $ gem install wake-assets
12
+ ```
13
+
14
+
15
+ ## Usage
16
+
17
+ These examples are based on the [wake example build
18
+ config](https://github.com/jcoglan/wake#usage).
19
+
20
+ #### At boot time
21
+
22
+ When your app boots, create an instance of `Wake::Assets` and keep this object
23
+ around through the lifetime of the app process. For example, in Rails:
24
+
25
+ ```ruby
26
+ require 'wake/assets'
27
+
28
+ dev = Rails.env.development?
29
+
30
+ $wake = Wake::Assets.new(
31
+ :wake => File.expand_path('node_modules/.bin/wake', Rails.root),
32
+ :root => File.expand_path('public', Rails.root),
33
+ :mode => dev ? :sources : :targets,
34
+ :monitor => dev
35
+ )
36
+ ```
37
+
38
+ The options are:
39
+
40
+ * `:wake` - the path to your `wake` executable
41
+ * `:root` - the document root of your application
42
+ * `:mode` - `:sources` if you want to render links to source files, `:targets`
43
+ if you want optimised files
44
+ * `:monitor` - whether to monitor the filesystem for changes, recommended in
45
+ development but not in production
46
+
47
+ #### At request time
48
+
49
+ On each request, create a renderer from your `Assets` instance. In Rails, you
50
+ might do this with a helper:
51
+
52
+ ```ruby
53
+ module AssetsHelper
54
+ CONFIG_PATH = File.expand_path('package.json', Rails.root)
55
+ ASSET_HOSTS = JSON.parse(File.read(CONFIG_PATH))['wake']['css']['hosts']
56
+
57
+ def assets
58
+ @assets ||= $wake.renderer(
59
+ :builds => {
60
+ 'css' => request.ssl? ? 'ssl' : 'min',
61
+ 'javascript' => 'min',
62
+ 'binary' => 'min'
63
+ },
64
+ :hosts => ASSET_HOSTS[Rails.env][request.ssl? ? 'https' : 'http'],
65
+ :inline => false
66
+ )
67
+ end
68
+ end
69
+ ```
70
+
71
+ The options are:
72
+
73
+ * `:builds` - which build to use for each asset type, the default for each is
74
+ `min`
75
+ * `:hosts` - the set of asset hosts to use for rendering links, the default is
76
+ an empty list
77
+ * `:inline` - whether to render assets inline so the browser does not make
78
+ additional requests for them, default is `false`
79
+
80
+ #### In your templates
81
+
82
+ With this helper in place, you can render links to JavaScript, CSS and images:
83
+
84
+ ```ruby
85
+ assets.include_js 'scripts.js'
86
+ # => '<script type="text/javascript" src="/assets/scripts-bb210c6.js"></script>'
87
+
88
+ assets.include_css 'style.css'
89
+ # => '<link rel="stylesheet" type="text/css" href="/assets/styles-5a2ceb1.css">'
90
+
91
+ assets.include_image 'logo.png', :html => {:alt => 'Logo'}
92
+ # => '<img src="/assets/logo-2fa8d38.png" alt="Logo">'
93
+ ```
94
+
95
+ You can pass the `:inline` option to any of these to override the per-request
96
+ `:inline` setting:
97
+
98
+ ```ruby
99
+ assets.include_js 'scripts.js', :inline => true
100
+ # => '<script type="text/javascript">alert("Hello, world!")</script>'
101
+ ```
102
+
103
+
104
+ ## License
105
+
106
+ (The MIT License)
107
+
108
+ Copyright (c) 2013 James Coglan, Songkick
109
+
110
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
111
+ this software and associated documentation files (the 'Software'), to deal in
112
+ the Software without restriction, including without limitation the rights to
113
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
114
+ the Software, and to permit persons to whom the Software is furnished to do so,
115
+ subject to the following conditions:
116
+
117
+ The above copyright notice and this permission notice shall be included in all
118
+ copies or substantial portions of the Software.
119
+
120
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
121
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
122
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
123
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
124
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
125
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
126
+
data/lib/wake.rb ADDED
@@ -0,0 +1,6 @@
1
+ module Wake
2
+ root = File.expand_path('../wake', __FILE__)
3
+ autoload :Assets, root + '/assets'
4
+ autoload :Renderer, root + '/renderer'
5
+ end
6
+
@@ -0,0 +1,148 @@
1
+ require 'base64'
2
+ require 'erb'
3
+ require 'json'
4
+ require 'listen'
5
+ require 'mime/types'
6
+ require 'pathname'
7
+ require 'set'
8
+
9
+ module Wake
10
+ class Assets
11
+
12
+ DEFAULT_BUILD = 'min'
13
+ DEFAULT_MODE = :targets
14
+ DEFAULT_WAKE = './node_modules/wake/bin/wake'
15
+ CACHE_FILE = '.wake.json'
16
+ MANIFEST = '.manifest.json'
17
+ PACKAGE_FILE = 'package.json'
18
+ WAKE_FILE = 'wake.json'
19
+ CONFIG_FILES = Set.new([CACHE_FILE, MANIFEST, PACKAGE_FILE, WAKE_FILE])
20
+
21
+ CSS = 'css'
22
+ JS = 'javascript'
23
+ IMG = 'binary'
24
+
25
+ class InvalidReference < StandardError
26
+ end
27
+
28
+ autoload :Renderer, File.expand_path('../assets/renderer', __FILE__)
29
+
30
+ def initialize(options)
31
+ @pwd = File.expand_path(options.fetch(:pwd, Dir.pwd))
32
+ @wake = options.fetch(:wake, File.expand_path(DEFAULT_WAKE, @pwd))
33
+ @root = Pathname.new(File.expand_path(options.fetch(:root, @pwd)))
34
+ @mode = options.fetch(:mode, DEFAULT_MODE)
35
+ @manifest = new_manifest_cache
36
+ @paths = new_path_cache
37
+
38
+ system(@wake, '--cache')
39
+ read_config
40
+
41
+ return unless options[:monitor]
42
+
43
+ listener = Listen.to(@pwd).change do |modified, added, removed|
44
+ all = (modified + added + removed).map &File.method(:basename)
45
+ system(@wake, '--cache') if (added.any? or removed.any?) and not all.include?(CACHE_FILE)
46
+ update! if (CONFIG_FILES & all).any?
47
+ end
48
+ listener.force_polling(true)
49
+ listener.start
50
+ end
51
+
52
+ def deployment?
53
+ @mode == :targets
54
+ end
55
+
56
+ def paths_for(group, names, options = {})
57
+ build = options.fetch(:build, DEFAULT_BUILD)
58
+ unless @config[group].fetch('builds', {}).has_key?(build)
59
+ build = DEFAULT_BUILD
60
+ end
61
+ names.map { |name| @paths[group][name][build] }.flatten
62
+ end
63
+
64
+ def relative(path)
65
+ '/' + Pathname.new(path).relative_path_from(@root).to_s
66
+ end
67
+
68
+ def renderer(options = {})
69
+ Renderer.new(self, options)
70
+ end
71
+
72
+ private
73
+
74
+ def find_paths_for(group, name, build)
75
+ absolute_paths = begin
76
+ cache = @cache[group][name]
77
+ if @mode.to_s == 'sources'
78
+ cache.fetch('sources')
79
+ else
80
+ [cache.fetch('targets').fetch(build)]
81
+ end
82
+ rescue
83
+ nil
84
+ end
85
+
86
+ if absolute_paths.nil?
87
+ raise InvalidReference, "Could not find assets, group: #{group}, name: #{name}, build: #{build}"
88
+ end
89
+
90
+ absolute_paths.map do |path|
91
+ basename = File.basename(path)
92
+ dirname = File.dirname(path)
93
+ manifest = File.join(dirname, MANIFEST)
94
+
95
+ File.join(dirname, @manifest[manifest].fetch(basename, basename))
96
+ end
97
+ end
98
+
99
+ def new_manifest_cache
100
+ Hash.new do |hash, path|
101
+ hash[path] = File.file?(path) ? JSON.parse(File.read(path)) : {}
102
+ end
103
+ end
104
+
105
+ def new_path_cache
106
+ Hash.new do |h, group|
107
+ h[group] = Hash.new do |i, name|
108
+ i[name] = Hash.new do |j, build|
109
+ j[build] = find_paths_for(group, name, build)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def read_config
116
+ cache = File.join(@pwd, CACHE_FILE)
117
+ wake = File.join(@pwd, WAKE_FILE)
118
+ package = File.join(@pwd, PACKAGE_FILE)
119
+
120
+ @config = if File.file?(wake)
121
+ JSON.parse(File.read(wake))
122
+ elsif File.file?(package)
123
+ JSON.parse(File.read(package))['wake']
124
+ else
125
+ {}
126
+ end
127
+
128
+ @cache = JSON.parse(File.read(cache))
129
+ end
130
+
131
+ def update!
132
+ paths = new_path_cache
133
+ read_config
134
+
135
+ @paths.each do |group, a|
136
+ a.each do |name, b|
137
+ b.each do |build, files|
138
+ paths[group][name][build]
139
+ end
140
+ end
141
+ end
142
+ @manifest = new_manifest_cache
143
+ @paths = paths
144
+ end
145
+
146
+ end
147
+ end
148
+
@@ -0,0 +1,100 @@
1
+ module Wake
2
+ class Assets
3
+ class Renderer
4
+
5
+ def initialize(assets, options)
6
+ @assets = assets
7
+ @builds = options.fetch(:builds, {})
8
+ @hosts = options.fetch(:hosts, [])
9
+ @inline = options[:inline]
10
+ end
11
+
12
+ def include_css(*names)
13
+ names, options = extract_options(names)
14
+
15
+ tags = if options.fetch(:inline, @inline)
16
+ paths_for(CSS, names, options).map do |path|
17
+ %Q{#{tag :style, options, :type => 'text/css'}#{File.read path}</style>}
18
+ end
19
+ else
20
+ urls_for(CSS, names, options).map do |url|
21
+ tag :link, options, :rel => 'stylesheet', :type => 'text/css', :href => url
22
+ end
23
+ end
24
+
25
+ html_safe(tags * '')
26
+ end
27
+
28
+ def include_image(*names)
29
+ names, options = extract_options(names)
30
+
31
+ tags = if options.fetch(:inline, @inline)
32
+ paths_for(IMG, names, options).map do |path|
33
+ base64 = Base64.strict_encode64(File.read(path))
34
+ mime = MIME::Types.type_for(path).first
35
+ tag :img, options, :src => "data:#{mime};base64,#{base64}"
36
+ end
37
+ else
38
+ urls_for(IMG, names, options).map { |url| tag :img, options, :src => url }
39
+ end
40
+
41
+ html_safe(tags * '')
42
+ end
43
+
44
+ def include_js(*names)
45
+ names, options = extract_options(names)
46
+
47
+ tags = if options.fetch(:inline, @inline)
48
+ paths_for(JS, names, options).map do |path|
49
+ %Q{#{tag :script, options, :type => 'text/javascript'}#{File.read path}</script>}
50
+ end
51
+ else
52
+ urls_for(JS, names, options).map do |url|
53
+ %Q{#{tag :script, options, :type => 'text/javascript', :src => url}</script>}
54
+ end
55
+ end
56
+
57
+ html_safe(tags * '')
58
+ end
59
+
60
+ def paths_for(type, names, options = {})
61
+ @assets.paths_for(type, names, {:build => @builds[type]}.merge(options))
62
+ end
63
+
64
+ def urls_for(type, names, options = {})
65
+ paths = paths_for(type, names, options).map { |p| @assets.relative(p) }
66
+
67
+ return paths unless @hosts.any?
68
+
69
+ paths.map do |path|
70
+ @hosts[path.hash % @hosts.size].gsub(/\/*$/, '') + path
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def extract_options(names)
77
+ [names.grep(String), names.grep(Hash).first || {}]
78
+ end
79
+
80
+ def h(string)
81
+ ERB::Util.html_escape(string)
82
+ end
83
+
84
+ def html_safe(string)
85
+ if defined? ActiveSupport
86
+ ActiveSupport::SafeBuffer.new(string)
87
+ else
88
+ string
89
+ end
90
+ end
91
+
92
+ def tag(name, options, attrs)
93
+ attrs = attrs.merge(options.fetch(:html, {}))
94
+ "<#{name} #{ attrs.map { |k,v| %Q{#{k}="#{h v.to_s}"} }.join(' ') }>"
95
+ end
96
+
97
+ end
98
+ end
99
+ end
100
+
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wake-assets
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - James Coglan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: listen
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: mime-types
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description:
47
+ email: jcoglan@gmail.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - README.md
52
+ files:
53
+ - README.md
54
+ - lib/wake.rb
55
+ - lib/wake/assets.rb
56
+ - lib/wake/assets/renderer.rb
57
+ homepage: http://github.com/jcoglan/wake-assets
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --main
62
+ - README.md
63
+ - --markup
64
+ - markdown
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ! '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ! '>='
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 1.8.23
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Renders links to assets managed by wake
85
+ test_files: []