asset_manifest 1.0.0.pre.rc1

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.
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: []