asset_manifest 1.0.0.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 60c26b354f46bb76f50c8d4a1cbec0b81b34584a
4
+ data.tar.gz: 19c9d5a20eded7714f188233edcd5d920d7a7b5d
5
+ SHA512:
6
+ metadata.gz: 9b2f14e2cdae4e98842eb232089c9c38be813e24819b151a7e0f2a5e37c4e599334f106f58077d70ac2471b5620ea61c1d2a16f3b9e22aa03f7201064915ea05
7
+ data.tar.gz: 81fba360287012e033e387c41a3ccabef024c8b20a17c8e3c6029cf0c6970c5ca2f38d16f2cc0db4c518683697cf8c66329bff8eb3bba9e27d1071e909694e1e
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2016 Nicolas Sanguinetti <foca@13floor.org>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # Asset Manifest
2
+
3
+ Tiny set of utilities to compute subresource integrity and cache busting
4
+ checksums for static assets.
5
+
6
+ ## How it works
7
+
8
+ Asset Manifest provides two main utilities:
9
+
10
+ * An executable that you should run when compiling your static assets. This
11
+ generates a `manifest` file that includes the SRI hash and a checksum (for
12
+ cache-busting purposes) of each of your assets.
13
+
14
+ * A ruby library to generate the proper links to the assets taking into account
15
+ the data from your manifest.
16
+
17
+ ## Usage
18
+
19
+ Somewhere in your makefile, you'll want to generate the `public/manifest.json`
20
+ file like so:
21
+
22
+ ``` Makefile
23
+ # Assuming you keep an ASSETS list with all the assets you're compiling...
24
+ ASSETS += public/css/app.css
25
+ ASSETS += public/css/app.min.css
26
+ ASSETS += public/js/app.js
27
+ ASSETS += public/js/app.min.js
28
+
29
+ # ...you'd want a rule like this one:
30
+ public/manifest.json: $(ASSETS)
31
+ asset-manifest -d public $^ > $@
32
+ ```
33
+
34
+ Then, in your app, you'll want to initialize `AssetManifest::Helpers` passing
35
+ the contents of this JSON file:
36
+
37
+ ``` ruby
38
+ assets = AssetManifest::Helpers.new(
39
+ JSON.parse(File.read("./public/manifest.json")),
40
+ { minify: ENV["RACK_ENV"] == "production" }
41
+ )
42
+ ```
43
+
44
+ Finally, you'll want to pass this object to your views, so that you can do the
45
+ following:
46
+
47
+ ``` erb
48
+ <%= assets.stylesheet_tag("/css/app.css") %>
49
+ <%= assets.script_tag("/js/app.js") %>
50
+ ```
51
+
52
+ This would produce output similar to:
53
+
54
+ ``` html
55
+ <link rel="stylesheet"
56
+ href="/css/app-07915293e2bb992a67c618e1aa335d978efc3734.css"
57
+ integrity="sha256-yqqr5VJwz1IM5iTlSram51zrBvuE21FbiYgNnD2fwgE=">
58
+ <script src="/js/app-1a4aefaba81b61c7ea763d42fcb39584e5784c32.js"
59
+ integrity="sha256-ZmTdnjlqI4ppv9cW8Y5i6PixtV4CQlVUvxB2iySwU94="></script>
60
+ ```
61
+
62
+ (Whitespace added for clarity)
63
+
64
+ ### With Rake instead of Make
65
+
66
+ Assuming you have an `ASSETS` constant with the list of assets, you can use the
67
+ following example in your `Rakefile` to re-generate your manifest when your
68
+ assets change.
69
+
70
+ ``` rake
71
+ rule "public/manifest.json" => ASSETS do |t|
72
+ sh "asset-manifest -d public #{t.prerequisites.join(" ")} > #{t.name}"
73
+ end
74
+
75
+ task assets: ASSETS
76
+ task assets: "public/manifest.json"
77
+ ```
78
+
79
+ You can add this file to your `assets` so that the file is automatically
80
+ generated when your source assets change.
81
+
82
+ ## Cuba plugin
83
+
84
+ If you use [Cuba](http://cuba.is), then you can just do this:
85
+
86
+ ``` ruby
87
+ require "asset_manifest/cuba"
88
+ Cuba.plugin AssetManifest::Cuba
89
+ ```
90
+
91
+ This will set up an `assets` helper available in all your apps that you can use
92
+ / pass to the templates.
93
+
94
+ ## License
95
+
96
+ This project is shared under the MIT license. See the attached [LICENSE][] file
97
+ for details.
98
+
99
+ [LICENSE]: ./LICENSE
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "optparse"
4
+ require "json"
5
+ require "digest/sha1"
6
+ require "digest/sha2"
7
+ require "fileutils"
8
+ require "asset_manifest"
9
+
10
+ options = {}
11
+ OptionParser.new do |o|
12
+ o.banner = "Usage: #{File.basename($0)} [options] asset..."
13
+
14
+ o.on("-d", "--directory DIR", "Public directory") do |dir|
15
+ options[:dir] = dir
16
+ end
17
+ end.parse!(ARGV)
18
+
19
+ manifest = Hash.new { |h,k| h[k] = {} }
20
+
21
+ ARGV.each do |path|
22
+ key = path.sub(/^#{Regexp.escape(options[:dir])}/, "")
23
+
24
+ body = File.read(path)
25
+ manifest[key][:integrity] = "sha256-" + Array(Digest::SHA256.digest(body)).pack("m0")
26
+ manifest[key][:checksum] = Digest::SHA1.hexdigest(body)
27
+
28
+ asset = AssetManifest::Asset.new(path.sub(".min.", "."), {
29
+ manifest: manifest.dup,
30
+ minify: path.include?(".min."),
31
+ integrity: manifest[key][:integrity],
32
+ checksum: manifest[key][:checksum],
33
+ })
34
+
35
+ FileUtils.cp(File.join(Dir.pwd, path), asset.path)
36
+ end
37
+
38
+ puts JSON.dump(manifest)
@@ -0,0 +1,59 @@
1
+ require_relative "../asset_manifest"
2
+
3
+ # Cuba plugin to simplify using AssetManifest. Gives you an entrypoint into the set of
4
+ # helpers to render different HTML tags.
5
+ #
6
+ # All you need to do is add this:
7
+ #
8
+ # Cuba.plugin AssetManifest::Cuba
9
+ #
10
+ # For example, in your template you could do something like this:
11
+ #
12
+ # <%= assets.stylesheet_tag("/css/main.css") %>
13
+ # <%= assets.script_tag("/js/main.js") %>
14
+ #
15
+ # Configuration
16
+ # -------------
17
+ #
18
+ # You can configure the following things:
19
+ #
20
+ # * `Cuba.settings[:asset_manifest][:public]`:
21
+ # The public directory where assets go. Defaults to `./public`.
22
+ #
23
+ # * `Cuba.settings[:asset_manifest][:manifest]`:
24
+ # Path to the asset manifest. Defaults to `{{ public }}/manifest.json`.
25
+ #
26
+ # * `Cuba.settings[:asset_manifest][:minified]`:
27
+ # Whether to append a `min` to the filename. If this is trueish, then a link
28
+ # to `foo.xyz` would turn to a link to `foo.min.xyz`. Defaults to `nil`.
29
+ #
30
+ # * `Cuba.settings[:asset_manifest][:asset_host]`:
31
+ # Host to serve assets from. Defaults to an empty String (same root as the
32
+ # Cuba app).
33
+ #
34
+ module AssetManifest::Cuba
35
+ def self.setup(app)
36
+ settings = app.settings[:asset_manifest] ||= {}
37
+
38
+ settings[:public] ||= "./public"
39
+ settings[:manifest] ||= File.join(settings[:public], "manifest.json")
40
+ settings[:minified] ||= nil
41
+ settings[:asset_host] ||= ""
42
+ end
43
+
44
+ # Public: Helper proxy for your apps.
45
+ #
46
+ # Example:
47
+ #
48
+ # <%= assets.stylesheet_tag("/css/main.css") %>
49
+ #
50
+ def assets
51
+ @_assets ||= AssetManifest::Helpers.new(
52
+ JSON.parse(File.read(settings[:asset_manifest][:manifest])),
53
+ {
54
+ minify: settings[:asset_manifest][:minified],
55
+ host: settings[:asset_manifest][:asset_host],
56
+ }
57
+ )
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module AssetManifest
2
+ VERSION = "1.0.0.pre.rc1".freeze
3
+ end
@@ -0,0 +1,165 @@
1
+ require_relative "asset_manifest/version"
2
+
3
+ module AssetManifest
4
+ class Helpers
5
+ def initialize(manifest, asset_options = {})
6
+ @manifest = manifest
7
+ @asset_options = asset_options
8
+ end
9
+
10
+ # Public: Generate a <link> tag.
11
+ #
12
+ # path - The absolute path (for the browser) to the stylesheet.
13
+ # html - A Hash with HTML attribute mappings for this tag.
14
+ # **opts - Any keyword arguments will be forwarded to the initializer of
15
+ # AssetManifest::Asset to calculate the tag's attributes.
16
+ #
17
+ # Returns a String.
18
+ def link_tag(path, html: {}, **opts)
19
+ link = asset(path, opts)
20
+ attrs = attributes(html)
21
+ attrs.concat(sri(link))
22
+ %Q(<link href="#{link.url}" #{attrs.join(" ")}>)
23
+ end
24
+
25
+ # Public: Generate a <link> tag to a stylesheet.
26
+ #
27
+ # path - The absolute path (for the browser) to the stylesheet.
28
+ # html - A Hash with HTML attribute mappings for this tag.
29
+ # **opts - Any keyword arguments will be forwarded to the initializer of
30
+ # AssetManifest::Asset to calculate the tag's attributes.
31
+ #
32
+ # Returns a String.
33
+ def stylesheet_tag(path, html: {}, **opts)
34
+ html.update(rel: "stylesheet")
35
+ link_tag(path, html: html, **opts)
36
+ end
37
+
38
+ # Public: Generate a <script> tag to a JS file.
39
+ #
40
+ # path - The absolute path (for the browser) to the script.
41
+ # html - A Hash with HTML attribute mappings for this tag.
42
+ # **opts - Any keyword arguments will be forwarded to the initializer of
43
+ # AssetManifest::Asset to calculate the tag's attributes.
44
+ #
45
+ # Returns a String.
46
+ def script_tag(path, html: {}, **opts)
47
+ script = asset(path, opts)
48
+ attrs = attributes(html)
49
+ attrs.concat(sri(script))
50
+ %Q(<script src="#{script.url}" #{attrs.join(" ")}></script>)
51
+ end
52
+
53
+ # Public: Generate an <img> tag.
54
+ #
55
+ # path - The absolute path (for the browser) to the image.
56
+ # html - A Hash with HTML attribute mappings for this tag.
57
+ # **opts - Any keyword arguments will be forwarded to the initializer of
58
+ # AssetManifest::Asset to calculate the tag's attributes.
59
+ #
60
+ # Returns a String.
61
+ def image_tag(path, html: {}, **opts)
62
+ image = asset(path, opts)
63
+ attrs = attributes(html)
64
+ %Q(<img src="#{image.url}" #{attrs.join(" ")}>)
65
+ end
66
+
67
+ # Internal: Generate an attribute list from a Hash. If there's a `:data` key
68
+ # and it's itself a Hash, its keys will get a `data-` prefix.
69
+ #
70
+ # opts - A Hash of attribute => value mappings. If a value is the literal
71
+ # `true`, then we'll consider the attribute to be a Boolean
72
+ # attribute and output it without value (ie. `{ hidden: true }`
73
+ # would transform into `["hidden"]`, not `['hidden="true"']`.
74
+ # prefix - A prefix to add to attribute names. Defaults to an empty String.
75
+ #
76
+ # Returns an Array of 'attribute="value"' Strings.
77
+ def attributes(opts, prefix="")
78
+ attrs = []
79
+
80
+ if Hash === opts[:data]
81
+ attrs.concat(attributes(opts.delete(:data), "data-"))
82
+ end
83
+
84
+ attrs.concat(
85
+ opts.map { |attr, val| val == true ? attr : %Q(#{attr}="#{val}") }
86
+ )
87
+
88
+ attrs
89
+ end
90
+
91
+ # Internal: Returns an AssetManifest::Asset for the given path, using the default
92
+ # asset options and any other options passed in the `opts` Hash.
93
+ def asset(path, opts)
94
+ Asset.new(path, manifest: @manifest, **@asset_options.merge(opts))
95
+ end
96
+
97
+ # Internal: Generate the Sub-Resource Integrity attributes for the tags that
98
+ # require it.
99
+ #
100
+ # tag - An AssetManifest::Asset object.
101
+ #
102
+ # Returns an Array of attribute=value Strings.
103
+ def sri(asset)
104
+ attrs = [%Q(integrity="#{asset.integrity}")]
105
+ attrs << %Q(crossorigin="#{asset.cors}") if asset.cors?
106
+ attrs
107
+ end
108
+ end
109
+
110
+ class Asset
111
+ # Public: Returns a String with the SRI hash for this asset.
112
+ attr_reader :integrity
113
+
114
+ # Public: Returns a String with the value of the crossorigin attribute, or
115
+ # `nil` if the asset isn't served from a special asset host.
116
+ attr_reader :cors
117
+
118
+ def initialize(path, manifest:,
119
+ host: "",
120
+ minify: nil,
121
+ integrity: nil,
122
+ checksum: nil,
123
+ cors: "anonymous")
124
+ minify = (minify if minify) # Ensure `false` gets converted to `nil`
125
+ @path = base_path(path, minify)
126
+ @manifest = manifest[@path]
127
+ @integrity = integrity || @manifest.fetch("integrity")
128
+ @checksum = checksum || @manifest.fetch("checksum")
129
+ @minify = minify
130
+ @host = host
131
+ @cors = cors if cors?
132
+ end
133
+
134
+ # Public: Returns a String with the path to the asset from the host's root
135
+ # (i.e. without the asset host).
136
+ def path
137
+ @_path ||= begin
138
+ sep = @minify ? ".min." : "."
139
+ *asset, ext = @path.split(sep)
140
+ asset.last << "-#{@checksum}"
141
+ [*asset, ext].join(sep)
142
+ end
143
+ end
144
+
145
+ # Public: Returns a String with the full URL of the asset (including the
146
+ # asset host, if any).
147
+ def url
148
+ @url ||= File.join(@host, path)
149
+ end
150
+
151
+ # Public: Returns a Boolean representing whether we should set up a
152
+ # `crossorigin` attribute for this asset.
153
+ def cors?
154
+ @host != ""
155
+ end
156
+
157
+ private
158
+
159
+ def base_path(path, minify)
160
+ minify = "min" if minify
161
+ *asset, ext = path.split(".")
162
+ [*asset, *minify, ext].join(".")
163
+ end
164
+ end
165
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asset_manifest
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.pre.rc1
5
+ platform: ruby
6
+ authors:
7
+ - Nicolas Sanguinetti
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-03-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: cutest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: nokogiri
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ description: AssetManifest provides utilities for generating SRI and cache-busting
56
+ hashes to your static assets during compilation.
57
+ email:
58
+ - contacto@nicolassanguinetti.info
59
+ executables:
60
+ - asset-manifest
61
+ extensions: []
62
+ extra_rdoc_files: []
63
+ files:
64
+ - LICENSE
65
+ - README.md
66
+ - bin/asset-manifest
67
+ - lib/asset_manifest.rb
68
+ - lib/asset_manifest/cuba.rb
69
+ - lib/asset_manifest/version.rb
70
+ homepage: http://github.com/13floor/asset_manifest
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">"
86
+ - !ruby/object:Gem::Version
87
+ version: 1.3.1
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.4.5.1
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Utilities for serving your static assets.
94
+ test_files: []