pax_global_header 0000666 0000000 0000000 00000000064 13312220220 0014475 g ustar 00root root 0000000 0000000 52 comment=2f7b7e5e67f47c32a2d637b7e90dfa5ecf922eb3
sprockets-3.7.2/ 0000775 0000000 0000000 00000000000 13312220220 0013523 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/.gitattributes 0000664 0000000 0000000 00000000070 13312220220 0016413 0 ustar 00root root 0000000 0000000 test/fixtures/**/*.png binary
test/fixtures/**/* eol=lf
sprockets-3.7.2/.gitignore 0000664 0000000 0000000 00000000102 13312220220 0015504 0 ustar 00root root 0000000 0000000 *.rbc
.bundle
Gemfile.lock
README.html
doc
docs
pkg
tmp
.DS_Store
sprockets-3.7.2/.travis.yml 0000664 0000000 0000000 00000001236 13312220220 0015636 0 ustar 00root root 0000000 0000000 language: ruby
cache: bundler
sudo: false
rvm:
- 2.0.0
- 2.1
- 2.2
- 2.3.1
- 2.4.0-rc1
matrix:
include:
test_isolated
matrix:
include:
- rvm: 1.9.3
gemfile: gemfiles/Gemfile-1.9
- rvm: 2.2
script: bundle exec rake test_isolated
notifications:
email: false
irc:
on_success: change
on_failure: always
channels:
- "irc.freenode.org#rails-contrib"
campfire:
on_success: change
on_failure: always
rooms:
- secure: "bJyiK4EXGfm0EhAId/QqIEdhPCx8BjxUKvx1h0+wgrsUm8BDoEN7tg2wqoGWU2KfzqLdx77wZVQXbmksOgmNcMGKJed5uNNMpAG4B+AQYTEX0odFRgOZKdkMtypga9CNIkKVgeSKhd6BY+g2AV6wvJ0Jq056uXpGkqK5OEFOpQc="
sprockets-3.7.2/CHANGELOG.md 0000664 0000000 0000000 00000021124 13312220220 0015334 0 ustar 00root root 0000000 0000000 **3.7.2** (June 19, 2018)
* Security release for [CVE-2018-3760](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-3760).
**3.7.1** (December 19, 2016)
* Ruby 2.4 support for Sprockets 3.
**3.7.0** (July 21, 2016)
* Deprecated interfaces now emit deprecation warnings #345
**3.6.3** (July 1, 2016)
* Faster asset lookup in large directories #336
* Faster PathUtils.match_path_extname https://github.com/rails/sprockets/commit/697269cf81e5261fdd7072e32bd489403027fd7e
* Fixed uglifier comment stripping #326
* Error messages now show load path info #313
**3.6.2** (June 21, 2016)
* More performance improvements.
**3.6.1** (June 17, 2016)
* Some performance improvements.
**3.6.0** (April 6, 2016)
* Add `Manifest#find_sources` to return the source of the compiled assets.
* Fix the list of compressable mime types.
* Improve performance of the `FileStore` cache.
**3.5.2** (December 8, 2015)
* Fix JRuby bug with concurrent-ruby.
* Fix disabling gzip generation in cached environments.
**3.5.1** (December 5, 2015)
* Fix gzip asset generation for assets already on disk.
**3.5.0** (December 3, 2015)
* Reintroduce Gzip file generation for non-binary assets.
**3.4.1** (November 25, 2015)
* PathUtils::Entries will no longer error on an empty directory.
**3.4.0** (October 5, 2015)
* Expose method to override the sass cache in the SassProcessor.
**3.3.5** (September 25, 2015)
* Fix bug related to absolute path being reintroduced into history cache #141.
**3.3.4** (September 1, 2015)
* Relative cache contents now work with windows.
**3.3.3** (August 21, 2015)
* Remove more absolute paths from cache contents.
**3.3.2** (August 19, 2015)
* Fix cache contents to use relative paths instead of absolute paths.
**3.3.1** (August 15, 2015)
* Fix legacy Tilt integration when locals is required argument.
**3.3.0** (August 12, 2015)
* Change internal cache key to use relative asset paths instead of absolute paths.
**3.2.0** (June 2, 2015)
* Updated SRI integrity to align with spec changes
* Deprecated Manifest integrity attribute
* Cleanup concatenating JS sources with newlines
**3.1.0** (May 10, 2015)
* Removed "index" logical path normalization. Asset#logical_path is always the
full logical path to the index file.
* Fixed static asset mtimes
* Fix manifest cleanup by age
* Removed redundant minifier level cache
* Updated SRI format according to spec changes
**3.0.3** (April 27, 2015)
* Fix static asset mtime fallback
* Only warn when specified asset version can not be loaded.
**3.0.2** (April 22, 2015)
* Ensure legacy Tilt handlers return String class data. Fixes issues with Haml
Tilt handler.
* Type check and improve error messages raised on bad processor returned results.
* Improve error message for relative paths not under load path.
* Changed HTML encoding fallback from ISO-8859-1 to default external.
* Avoid falling back to 0 mtimes which may cause warnings with tar
**3.0.1** (April 14, 2015)
* Fixed `Context#depend_on` with paths outside the load path
**3.0.0** (April 12, 2015)
[Guide to upgrading from Sprockets 2.x to 3.x](https://github.com/rails/sprockets/blob/3.x/UPGRADING.md)
* New processor API. Tilt interface is deprecated.
* Improved file store caching backend.
* MIME Types now accept charset custom charset detecters. Improves support for UTF-16/32 files.
* Environment#version no longer affects asset digests. Only used for busting the asset cache.
* Removed builtin support for LESS.
* Removed `//= include` directive support.
* Deprecated `BundledAsset#to_a`. Use `BundledAsset#included` to access debugging subcomponents.
* Support circular dependencies. For parity with ES6 modules.
* Manifest compilation will no longer generate .gz files by default. [Mixing
Content-Encoding and ETags is just a bad
idea](https://issues.apache.org/bugzilla/show_bug.cgi?id=39727)
* Added linked or referenced assets. When an asset is compiled, any of its links will be compiled as well.
* Introduce some limitations around enumerating all logical paths. 4.x will deprecate it and favor linked manifests for compliation.
* Add Asset integrity attribute for Subresource Integrity
* Default digest changed to SHA256. Configuring `digest_class` is deprecated.
* Rename `Asset#digest` to `Asset#hexdigest`. `Asset#digest` is deprecated and will
return a raw byte String in 4.x.
* Added transitional compatibility flag to `Environment#resolve(path, compat: true)`. 2.x mode operates with `compat: true` and 4.x with `compat: false`
* `manifest-abc123.json` renamed to `.sprockets-abc123.json`
**2.12.3** (October 28, 2014)
* Security: Fix directory traversal bug in development mode server.
**2.12.2** (September 5, 2014)
* Ensure internal asset lookups calls are still restricted to load paths within
asset compiles. Though, you should not depend on internal asset resolves to be
completely restricted for security reasons. Assets themselves should be
considered full scripting environments with filesystem access.
**2.12.1** (April 17, 2014)
* Fix making manifest target directory when its different than the output directory.
**2.12.0** (March 13, 2014)
* Avoid context reference in SassImporter hack so its Marshallable. Fixes
issues with Sass 3.3.x.
**2.11.0** (February 19, 2014)
* Cache store must now be an LRU implementation.
* Default digest changed to SHA1. To continue using MD5.
`env.digest_class = Digest::MD5`.
**2.10.0** (May 24, 2013)
* Support for `bower.json`
**2.9.3** (April 20, 2013)
* Fixed sass caching bug
**2.9.2** (April 8, 2013)
* Improve file freshness check performance
* Directive processor encoding fixes
**2.9.1** (April 6, 2013)
* Support for Uglifier 2.x
**2.9.0** (February 25, 2013)
* Write out gzipped variants of bundled assets.
**2.8.2** (December 10, 2012)
* Fixed top level Sass constant references
* Fixed manifest logger when environment is disabled
**2.8.1** (October 31, 2012)
* Fixed Sass importer bug
**2.8.0** (October 16, 2012)
* Allow manifest location to be separated from output directory
* Pass logical path and absolute path to each_logical_path iterator
**2.7.0** (October 10, 2012)
* Added --css-compressor and --js-compressor command line flags
* Added css/js compressor shorthand
* Change default manifest.json filename to be a randomized manifest-16HEXBYTES.json
* Allow nil environment to be passed to manifest
* Allow manifest instance to be set on rake task
**2.6.0** (September 19, 2012)
* Added bower component.json require support
**2.5.0** (September 4, 2012)
* Fixed Ruby 2.0 RegExp warning
* Provide stubbed implementation of context *_path helpers
* Add SassCompressor
**2.4.5** (July 10, 2012)
* Tweaked some logger levels
**2.4.4** (July 2, 2012)
* Canonicalize logical path extensions
* Check absolute paths passed to depend_on
**2.4.3** (May 16, 2012)
* Exposed :sprockets in sass options
* Include dependency paths in asset mtime
**2.4.2** (May 7, 2012)
* Fixed MultiJson feature detect
**2.4.1** (April 26, 2012)
* Fixed MultiJson API change
* Fixed gzip mtime
**2.4.0** (March 27, 2012)
* Added global path registry
* Added global processor registry
**2.3.2** (March 26, 2012)
* Fix Context#logical_path with dots
**2.3.1** (February 11, 2012)
* Added bytesize to manifest
* Added Asset#bytesize alias
* Security: Check path for forbidden access after unescaping
**2.3.0** (January 16, 2012)
* Added special Sass importer that automatically tracks any `@import`ed files.
**2.2.0** (January 10, 2012)
* Added `sprockets` command line utility.
* Added rake/sprocketstask.
* Added json manifest log of compiled assets.
* Added `stub` directive that allows you to exclude files from the bundle.
* Added per environment external encoding (Environment#default_external_encoding). Defaults to UTF-8. Fixes issues where LANG is not set correctly and Rubys default external is set to ASCII.
**2.1.2** (November 20, 2011)
* Disabled If-Modified-Since server checks. Fixes some browser caching issues when serving the asset body only. If-None-Match caching is sufficient.
**2.1.1** (November 18, 2011)
* Fix windows absolute path check bug.
**2.1.0** (November 11, 2011)
* Directive comment lines are now turned into empty lines instead of removed. This way line numbers in
CoffeeScript syntax errors are correct.
* Performance and caching bug fixes.
**2.0.3** (October 17, 2011)
* Detect format extensions from right to left.
* Make JST namespace configurable.
**2.0.2** (October 4, 2011)
* Fixed loading stale cache from bundler gems.
**2.0.1** (September 30, 2011)
* Fixed bug with fingerprinting file names with multiple dots.
* Decode URIs as default internal.
* Fix symlinked asset directories.
**2.0.0** (August 29, 2011)
* Initial public release.
sprockets-3.7.2/Gemfile 0000664 0000000 0000000 00000000326 13312220220 0015017 0 ustar 00root root 0000000 0000000 source "https://rubygems.org"
gemspec
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.2.2")
gem 'rack', '< 2.0'
end
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.0")
gem 'json', '< 2.0'
end
sprockets-3.7.2/LICENSE 0000664 0000000 0000000 00000002101 13312220220 0014522 0 ustar 00root root 0000000 0000000 Copyright (c) 2014 Sam Stephenson
Copyright (c) 2014 Joshua Peek
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
sprockets-3.7.2/README.md 0000664 0000000 0000000 00000044022 13312220220 0015004 0 ustar 00root root 0000000 0000000 # Sprockets: Rack-based asset packaging
Sprockets is a Ruby library for compiling and serving web assets.
It features declarative dependency management for JavaScript and CSS
assets, as well as a powerful preprocessor pipeline that allows you to
write assets in languages like CoffeeScript, Sass and SCSS.
## Installation
Install Sprockets from RubyGems:
``` sh
$ gem install sprockets
```
Or include it in your project's `Gemfile` with Bundler:
``` ruby
gem 'sprockets', '~> 3.0'
```
## Using sprockets
For most people interested in using sprockets you will want to see [End User Asset Generation](guides/end_user_asset_generation.md) guide. This contains information about sprocket's directive syntax, and default processing behavior.
If you are a framework developer that is using sprockets, see [Building an Asset Processing Framework](guides/building_an_asset_processing_framework.md).
If you are a library developer who is extending the functionality of sprockets, see [Extending Sprockets](guides/extending_sprockets.md).
Below is a disjointed mix of documentation for all three of these roles. Eventually they will be moved to an appropriate guide, for now the recommended way to consume documentation is to view the appropriate guide first and then supplement with docs from the README.
## Behavior
### Index files are proxies for folders
In sprockets index files such as `index.js` or `index.css` files inside of a folder will generate a file with the folder's name. So if you have a `foo/index.js` file it will compile down to `foo.js`. This is similar to NPM's behavior of using [folders as modules](https://nodejs.org/api/modules.html#modules_folders_as_modules). It is also somewhat similar to the way that a file in `public/my_folder/index.html` can be reached by a request to `/my_folder`. This means that you cannot directly use an index file. For example this would not work:
```
<%= asset_path("foo/index.js") %>
```
Instead you would need to use:
```
<%= asset_path("foo.js") %>
```
Why would you want to use this behavior? It is common behavior where you might want to include an entire directory of files in a top level javascript. You can do this in sprockets using `require_tree .`
```
//= require_tree .
```
This has the problem that files are required alphabetically. If your directory has `jquery-ui.js` and `jquery.min.js` then sprockets will require `jquery-ui.js` before `jquery` is required which won't work (because jquery-ui depends on jquery). Previously the only way to get the correct ordering would be to rename your files, something like `0-jquery-ui.js`. Instead of doing that you can use an index file.
For example, if you have an `application.js` and want all the files in the `foo/` folder you could do this:
```
//= require foo.js
```
Then create a file `foo/index.js` that requires all the files in that folder in any order you want:
```
//= require foo.min.js
//= require foo-ui.js
```
Now in your `application.js` will correctly load the `foo.min.js` before `foo-ui.js`. If you used `require_tree` it would not work correctly.
## Understanding the Sprockets Environment
You'll need an instance of the `Sprockets::Environment` class to
access and serve assets from your application. Under Rails 4.0 and
later, `YourApp::Application.assets` is a preconfigured
`Sprockets::Environment` instance. For Rack-based applications, create
an instance in `config.ru`.
The Sprockets `Environment` has methods for retrieving and serving
assets, manipulating the load path, and registering processors. It is
also a Rack application that can be mounted at a URL to serve assets
over HTTP.
### The Load Path
The *load path* is an ordered list of directories that Sprockets uses
to search for assets.
In the simplest case, a Sprockets environment's load path will consist
of a single directory containing your application's asset source
files. When mounted, the environment will serve assets from this
directory as if they were static files in your public root.
The power of the load path is that it lets you organize your source
files into multiple directories -- even directories that live outside
your application -- and combine those directories into a single
virtual filesystem. That means you can easily bundle JavaScript, CSS
and images into a Ruby library or [Bower](http://bower.io) package and import them into your application.
#### Manipulating the Load Path
To add a directory to your environment's load path, use the
`append_path` and `prepend_path` methods. Directories at the beginning
of the load path have precedence over subsequent directories.
``` ruby
environment = Sprockets::Environment.new
environment.append_path 'app/assets/javascripts'
environment.append_path 'lib/assets/javascripts'
environment.append_path 'vendor/assets/bower_components'
```
In general, you should append to the path by default and reserve
prepending for cases where you need to override existing assets.
### Accessing Assets
Once you've set up your environment's load path, you can mount the
environment as a Rack server and request assets via HTTP. You can also
access assets programmatically from within your application.
#### Logical Paths
Assets in Sprockets are always referenced by their *logical path*.
The logical path is the path of the asset source file relative to its
containing directory in the load path. For example, if your load path
contains the directory `app/assets/javascripts`:
Asset source file |
Logical path |
app/assets/javascripts/application.js |
application.js |
app/assets/javascripts/models/project.js |
models/project.js |
In this way, all directories in the load path are merged to create a
virtual filesystem whose entries are logical paths.
#### Serving Assets Over HTTP
When you mount an environment, all of its assets are accessible as
logical paths underneath the *mount point*. For example, if you mount
your environment at `/assets` and request the URL
`/assets/application.js`, Sprockets will search your load path for the
file named `application.js` and serve it.
Under Rails 4.0 and later, your Sprockets environment is automatically
mounted at `/assets`. If you are using Sprockets with a Rack
application, you will need to mount the environment yourself. A good
way to do this is with the `map` method in `config.ru`:
``` ruby
require 'sprockets'
map '/assets' do
environment = Sprockets::Environment.new
environment.append_path 'app/assets/javascripts'
environment.append_path 'app/assets/stylesheets'
run environment
end
map '/' do
run YourRackApp
end
```
#### Accessing Assets Programmatically
You can use the `find_asset` method (aliased as `[]`) to retrieve an
asset from a Sprockets environment. Pass it a logical path and you'll
get a `Sprockets::Asset` instance back:
``` ruby
environment['application.js']
# => #
```
Call `to_s` on the resulting asset to access its contents, `length` to
get its length in bytes, `mtime` to query its last-modified time, and
`filename` to get its full path on the filesystem.
## Using Processors
Asset source files can be written in another format, like SCSS or
CoffeeScript, and automatically compiled to CSS or JavaScript by
Sprockets. Processors that convert a file from one format to another are called *transformers*.
### Minifying Assets
Several JavaScript and CSS minifiers are available through shorthand.
``` ruby
environment.js_compressor = :uglify
environment.css_compressor = :scss
```
### Styling with Sass and SCSS
[Sass](http://sass-lang.com/) is a language that compiles to CSS and
adds features like nested rules, variables, mixins and selector
inheritance.
If the `sass` gem is available to your application, you can use Sass
to write CSS assets in Sprockets.
Sprockets supports both Sass syntaxes. For the original
whitespace-sensitive syntax, use the extension `.sass`. For the
new SCSS syntax, use the extension `.scss`.
### Scripting with CoffeeScript
[CoffeeScript](http://jashkenas.github.com/coffee-script/) is a
language that compiles to the "good parts" of JavaScript, featuring a
cleaner syntax with array comprehensions, classes, and function
binding.
If the `coffee-script` gem is available to your application, you can
use CoffeeScript to write JavaScript assets in Sprockets. Note that
the CoffeeScript compiler is written in JavaScript, and you will need
an [ExecJS](https://github.com/rails/execjs)-supported runtime
on your system to invoke it.
To write JavaScript assets with CoffeeScript, use the extension
`.coffee`.
### JavaScript Templating with EJS and Eco
Sprockets supports *JavaScript templates* for client-side rendering of
strings or markup. JavaScript templates have the special format
extension `.jst` and are compiled to JavaScript functions.
When loaded, a JavaScript template function can be accessed by its
logical path as a property on the global `JST` object. Invoke a
template function to render the template as a string. The resulting
string can then be inserted into the DOM.
```
Hello, <%= name %>!
// application.js
//= require templates/hello
$("#hello").html(JST["templates/hello"]({ name: "Sam" }));
```
Sprockets supports two JavaScript template languages:
[EJS](https://github.com/sstephenson/ruby-ejs), for embedded
JavaScript, and [Eco](https://github.com/sstephenson/ruby-eco), for
embedded CoffeeScript. Both languages use the familiar `<% … %>`
syntax for embedding logic in templates.
If the `ejs` gem is available to your application, you can use EJS
templates in Sprockets. EJS templates have the extension `.jst.ejs`.
If the `eco` gem is available to your application, you can use [Eco
templates](https://github.com/sstephenson/eco) in Sprockets. Eco
templates have the extension `.jst.eco`. Note that the `eco` gem
depends on the CoffeeScript compiler, so the same caveats apply as
outlined above for the CoffeeScript engine.
### Invoking Ruby with ERB
Sprockets provides an ERB engine for preprocessing assets using
embedded Ruby code. Append `.erb` to a CSS or JavaScript asset's
filename to enable the ERB engine.
Ruby code embedded in an asset is evaluated in the context of a
`Sprockets::Context` instance for the given asset. Common uses for ERB
include:
- embedding another asset as a Base64-encoded `data:` URI with the
`asset_data_uri` helper
- inserting the URL to another asset, such as with the `asset_path`
helper provided by the Sprockets Rails plugin
- embedding other application resources, such as a localized string
database, in a JavaScript asset via JSON
- embedding version constants loaded from another file
See the [Helper Methods](lib/sprockets/context.rb) section for more information about
interacting with `Sprockets::Context` instances via ERB.
## Managing and Bundling Dependencies
You can create *asset bundles* -- ordered concatenations of asset
source files -- by specifying dependencies in a special comment syntax
at the top of each source file.
Sprockets reads these comments, called *directives*, and processes
them to recursively build a dependency graph. When you request an
asset with dependencies, the dependencies will be included in order at
the top of the file.
### The Directive Processor
Sprockets runs the *directive processor* on each CSS and JavaScript
source file. The directive processor scans for comment lines beginning
with `=` in comment blocks at the top of the file.
``` js
//= require jquery
//= require jquery-ui
//= require backbone
//= require_tree .
```
The first word immediately following `=` specifies the directive
name. Any words following the directive name are treated as
arguments. Arguments may be placed in single or double quotes if they
contain spaces, similar to commands in the Unix shell.
**Note**: Non-directive comment lines will be preserved in the final
asset, but directive comments are stripped after
processing. Sprockets will not look for directives in comment blocks
that occur after the first line of code.
#### Supported Comment Types
The directive processor understands comment blocks in three formats:
``` css
/* Multi-line comment blocks (CSS, SCSS, JavaScript)
*= require foo
*/
```
``` js
// Single-line comment blocks (SCSS, JavaScript)
//= require foo
```
``` coffee
# Single-line comment blocks (CoffeeScript)
#= require foo
```
### Sprockets Directives
You can use the following directives to declare dependencies in asset
source files.
For directives that take a *path* argument, you may specify either a
logical path or a relative path. Relative paths begin with `./` and
reference files relative to the location of the current file.
#### The `require` Directive
`require` *path* inserts the contents of the asset source file
specified by *path*. If the file is required multiple times, it will
appear in the bundle only once.
### The `require_directory` Directive ###
`require_directory` *path* requires all source files of the same
format in the directory specified by *path*. Files are required in
alphabetical order.
#### The `require_tree` Directive
`require_tree` *path* works like `require_directory`, but operates
recursively to require all files in all subdirectories of the
directory specified by *path*.
#### The `require_self` Directive
`require_self` tells Sprockets to insert the body of the current
source file before any subsequent `require` directives.
#### The `link` Directive
`link` *path* declares a dependency on the target *path* and adds it to a list
of subdependencies to automatically be compiled when the asset is written out to
disk.
For an example, in a CSS file you might reference an external image that always
needs to be compiled along with the css file.
``` css
/*= link "logo.png" */
.logo {
background-image: url(logo.png)
}
```
However, if you use a `asset-path` or `asset-url` SCSS helper, these links will
automatically be defined for you.
``` css
.logo {
background-image: asset-url("logo.png")
}
```
#### The `depend_on` Directive
`depend_on` *path* declares a dependency on the given *path* without
including it in the bundle. This is useful when you need to expire an
asset's cache in response to a change in another file.
#### The `depend_on_asset` Directive
`depend_on_asset` *path* works like `depend_on`, but operates
recursively reading the file and following the directives found. This is automatically implied if you use `link`, so consider if it just makes sense using `link` instead of `depend_on_asset`.
#### The `stub` Directive
`stub` *path* allows dependency to be excluded from the asset bundle.
The *path* must be a valid asset and may or may not already be part
of the bundle. `stub` should only be used at the top level bundle, not
within any subdependencies.
## Processor Interface
Sprockets 2.x was originally design around [Tilt](https://github.com/rtomayko/tilt)'s engine interface. However, starting with 3.x, a new interface has been introduced deprecating Tilt.
Similar to Rack, a processor is a any "callable" (an object that responds to `call`). This maybe a simple Proc or a full class that defines a `def self.call(input)` method. The `call` method accepts an `input` Hash and returns a Hash of metadata.
Also see [`Sprockets::ProcessorUtils`](https://github.com/rails/sprockets/blob/master/lib/sprockets/processor_utils.rb) for public helper methods.
### input Hash
The `input` Hash defines the following public fields.
* `:data` - String asset contents
* `:environment` - Current `Sprockets::Environment` instance.
* `:cache` - A `Sprockets::Cache` instance. See [`Sprockets::Cache#fetch`](https://github.com/rails/sprockets/blob/master/lib/sprockets/cache.rb).
* `:uri` - String Asset URI.
* `:filename` - String full path to original file.
* `:load_path` - String current load path for filename.
* `:name` - String logical path for filename.
* `:content_type` - String content type of the output asset.
* `:metadata` - Hash of processor metadata.
``` ruby
def self.call(input)
input[:cache].fetch("my:cache:key:v1") do
# Remove all semicolons from source
input[:data].gsub(";", "")
end
end
```
### return Hash
The processor should return metadata `Hash`. With the exception of the `:data` key, the processor can store arbitrary JSON valid values in this Hash. The data will be stored and exposed on `Asset#metadata`.
The returned `:data` replaces the assets `input[:data]` to the next processor in the chain. Returning a `String` is shorthand for returning `{ data: str }`. And returning `nil` is shorthand for a no-op where the input data is not transformed, `{ data: input[:data] }`.
### metadata
The metadata Hash provides an open format for processors to extend the pipeline processor. Internally, built-in processors use it for passing data to each other.
* `:required` - A `Set` of String Asset URIs that the Bundle processor should concatenate together.
* `:stubbed` - A `Set` of String Asset URIs that will be omitted from the `:required` set.
* `:links` - A `Set` of String Asset URIs that should be compiled along with this asset.
* `:dependencies` - A `Set` of String Cache URIs that should be monitored for caching.
``` ruby
def self.call(input)
# Any metadata may start off as nil, so initialize it the value
required = Set.new(input[:metadata][:required])
# Manually add "foo.js" asset uri to our bundle
required << input[:environment].resolve("foo.js")
{ required: required }
end
```
## Development
### Contributing
The Sprockets source code is [hosted on
GitHub](https://github.com/rails/sprockets). You can check out a
copy of the latest code using Git:
$ git clone https://github.com/rails/sprockets
If you've found a bug or have a question, please open an issue on the
[Sprockets issue
tracker](https://github.com/rails/sprockets/issues). Or, clone
the Sprockets repository, write a failing test case, fix the bug and
submit a pull request.
### Version History
Please see the [CHANGELOG](https://github.com/rails/sprockets/tree/master/CHANGELOG.md)
## License
Copyright © 2014 Sam Stephenson <>
Copyright © 2014 Joshua Peek <>
Sprockets is distributed under an MIT-style license. See LICENSE for
details.
sprockets-3.7.2/Rakefile 0000664 0000000 0000000 00000000424 13312220220 0015170 0 ustar 00root root 0000000 0000000 require "rake/testtask"
require "bundler/gem_tasks"
task :default => :test
Rake::TestTask.new do |t|
t.libs << "test"
t.warning = true
end
task :test_isolated do
Dir["test/test*.rb"].each do |fn|
ruby "-Ilib:test", "-w", fn
abort unless $?.success?
end
end
sprockets-3.7.2/UPGRADING.md 0000664 0000000 0000000 00000024151 13312220220 0015370 0 ustar 00root root 0000000 0000000 # Guide to upgrading from Sprockets 2.x to 3.x
Even though its a major release, Sprockets 3.x should be fairly API compatible
with 2.x unless explicitly noted in the CHANGELOG. For the most part, the
application facing APIs have remained the same with the majority of the changes
at the extension layer. So you shouldn't have to change much application code,
but you should verify that all the sprockets extension gems you are using are
compatible with 3.x.
## Application Changes
### public/assets/manifest-abc123.json location
JSON manifests are now written out to `.sprockets-manifest-abc123.json` to
prevent collisions with assets actually called `manifest`. If any old manifests
exists they will automatically be renamed. Just note if you were depending on
the `manifests-abc123.json` name in a deployment related task, you'll see this
new file showing up.
### Preference for asset manifest and links
Previously, Sprockets had no idea what files you wanted to statically compile
and how they related to each other. Now assets have the concept of referencing
each with "links". This provides a composable way for assets subresources to be
declared.
``` css
/* homepage.css */
.logo {
background: url("logo.png")
}
```
Typically you have these subresource relations in css files to other images.
You'd have to tell Sprockets to compile both the css and image.
But now when you use any of the asset helpers in ERB or SCSS, a link
relationship to created between the two assets.
``` css
.logo {
background: url(<%= asset_url("logo.png") %>)
}
```
Its understood that whenever `homepage.css` is compiled, you'll need `logo.png`
too.
Most of the time you won't have to think up declaring links, helpers will do
that for you. But there are programmatic APIs for setting up links if you're
doing something custom.
``` css
/* A comment directive or erb call can be used to declare a link relationship */
/*= link logo.png */
<%= link_asset "logo.png" %>
.logo {}
```
Since links are composable, you can use them to define a single "manifest" file
that links to ever asset you need in production.
``` js
// app/assets/manifest.js
//
// JS bundles
//= link ./javascripts/standalone-jquery.js
//= link ./javascripts/application.js
//= link ./javascripts/settings.js
//
// CSS bundles
//= link ./stylesheets/application.css
//= link ./stylesheets/settings.css
//
// Pull in all app/assets/images/ since app/views may link to them
//= link_tree ./images
```
**Caution** Unlike `require` directives, links should have an explicit content
type or file extension. When using `link_directory` or `link_tree` prefer
setting a format as well.
``` js
// A mime type or file extension can be given as a second parameter to
// link_directory or link_tree
//
//= link_directory ./scripts .js
//= link_tree ./styles text/css
```
Then compiling `manifest` will ensure all the subresources are compiled as well.
``` ruby
config.assets.precompile = ["manifest.js"]
```
### Prefer just `foo.coffee` and `foo.scss`
Instead of the longer `foo.js.coffee` and `foo.css.scss`. This shorthand works in
2.x but is preferred going forward.
### Rev `version` less often
Many load path changing caching bugs have been fixed and processors can now
partipate in asset cache invalidation. So when you upgrade CoffeeScript, it will
automatically bust old changes. You'll need to be sure you're running the latest
versions of any Sprockets extensions so they opt into these new cache APIs.
### Removed `//= include` directive
You can replace this with ERB usage `<%= environment.find_asset("foo") %>`. This
will also allow you to put the contents anywhere you want in the file.
## Extension Changes
### Processor Interface
If you're a Sprockets plugin author you should definitely take some time to
migrate to the new processor API. You can relax for now since 3.x will still
support the old Tilt interface. 4.x will be the hard break away. Hopefully our
existing extensions still work on both 2.x and 3.x (unless you're using private
apis or monkey patching things).
So whats wrong with Tilt, why bother?
It was probably a good decision at the time, but we've out grown the constraints
of the Tilt template interface. After all, it was primarily designed for dynamic
HTML template engines, not assets like JS and CSS or binary assets like images.
Sprockets would like to have other metadata passed between processors besides
simple Strings. Passing source maps was one of the primary motivators.
Instead of a Tilt template interface, we now have a uniform Processor interface
across every part of the pipeline.
Similar to Rack, a processor is a any "callable" (an object that responds to
`call`). This maybe a simple Proc or a full class that defines a `def
self.call(input)` method. The `call` method accepts an `input` Hash and returns
a Hash of metadata.
If you just care about modifying the input data, the simplest processor looks
like
``` ruby
proc do |input|
# Take input data, remove all semicolons and return a string
input[:data].gsub(";", "")
end
```
A `proc` works well for quick user defined processors, but you might want to use
a full class for your extension.
``` ruby
class MyProcessor
def initialize(options = {})
@options = options
end
def call(input)
end
end
# A initializer pattern can allow users to configure application specific
# options for your processor
MyProcessor.new(style: :minimal)
```
`call(input)` is the only required method to implement, you can also provide a
`cache_key` method. This allows the processor to bust asset caches after a
library upgrade or configuration changes.
``` ruby
class MyProcessor
def initialize(options = {})
@options = options
end
def cache_key
['3', @options]
end
def call(input)
end
end
```
`cache_key` may return any simple JSON serializable value to use to
differentiate caches. This may just be a static version identifier you change
every gem release or configuration options declared on setup.
Heres a pretty standard processor boilerplate thats used internally for
Sprockets.
``` ruby
class MyProcessor
VERSION = '3'
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
def initialize(options = {})
@cache_key = [self.class.name, VERSION, options].freeze
end
def call(input)
# process input
end
end
```
### Transformers
Sprockets 2.x always had a one to one mapping between source file on disk
(app/assets/javascripts/application.coffee.rb) to compiled artifact
(public/assets/application.js). This has prevented the ability to compile assets
to multiple targets such as image conversations from logo.svg to logo.png,
logo.jpg, logo.gif, etc.
Supporting variants will definitely make the processor chain more powerful, but
it means moving away from some previous patterns. For an example, we can only
simply map over all assets under a directory like app/assets since a single file
may have multiple representations depending on the requested content type.
To support transformers, two APIs have been added.
First, the ability to request a variant of an asset for a content type.
``` ruby
# Find any source asset named "logo" that can be transformed into "image/png"
env.find_asset("logo", accept: "image/png")
# or using the more common extension format now means the same
env.find_asset("logo.png")
```
Second, a processor API for describing transformation types.
``` ruby
Sprockets.register_transformer 'image/svg+xml', 'image/png', SVG2PNGProcessor
Sprockets.register_transformer 'image/svg+xml', 'image/gif', SVG2GIFProcessor
```
Even if you don't need to support multiple content types, transformers can
replace traditional engines.
``` ruby
# Register a content type for file extension, its okay if its made up
register_mime_type 'text/coffeescript', extensions: ['.coffee']
register_transformer 'text/coffeescript', 'application/javascript', CoffeeScriptProcessor
```
Some important differences from previous engines.
**We can request the file in its original content type.**
``` ruby
# Return the file as is
env.find_asset("foo.coffee").source
```
Its important we can serve the original source file to the browser if source
maps are being used.
**Preprocesors run for the source content type, not the destination**
``` ruby
register_preprocessor 'text/coffeescript', LintCoffeeScript
register_postprocessor 'application/javascript', FormatJavaScript
```
Before converting a coffeescript file to JS, we first run any coffeescript
preprocessors, convert it to JS, then run the postprocessor. Because we have a
before and after content type distinction, theres not much use for pre vs post
processors. Once the transition to transformers is complete, prefer just using
`register_preprocessor` with the correct content type.
**Transformers may bind to multi-extnames**
``` ruby
register_mime_type 'application/javascript+module', extensions: ['.module.js']
register_mime_type 'text/html+ruby', extensions: ['.html.erb']
register_mime_type 'text/yaml+manifest', extensions: ['.manifest.yml']
```
Whatever special extname you pick, it doesn't necessarily have to be at the end
of the file. Prefer having an extname at the end that plays nice if your
editor's syntax highlighting.
However, this requires you whitelist all the compatible extension combinations.
Theres no free form chaining. This turned out to be a less useful feature. It
meant `foo.js.coffee.erb.haml.jst.eco.sass` was a legal processor chain, but
pretty useless.
### Pipeline overview
* Run preprocessors for source content type (`text/coffeescript`)
* Run legacy engines defined by file extensions (.erb)
* Run postprocessors for source content type (`text/coffeescript`)
* Run transformer from source to destination content type (coffee->js)
* Run preprocessors for destination content type (`application/javascript`)
* Run postprocessors for destination content type (`application/javascript`)
* Concatenate "required" files
* Run bundle processors
With engines being phased out, we can collapse the pre and post processor
chains. But that still leaves the "bundle" step as a special thing. I haven't
quite figured out how to it more unified. Let me know if you have any ideas.
sprockets-3.7.2/bin/ 0000775 0000000 0000000 00000000000 13312220220 0014273 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/bin/sprockets 0000775 0000000 0000000 00000004131 13312220220 0016235 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
$VERBOSE = nil
require 'sprockets'
require 'optparse'
require 'shellwords'
unless ARGV.delete("--noenv")
if File.exist?(path = "./.sprocketsrc")
rcflags = Shellwords.split(File.read(path))
ARGV.unshift(*rcflags)
end
end
filenames = []
environment = Sprockets::Environment.new(Dir.pwd)
manifest = nil
(ENV['SPROCKETS_PATH'] || "").split(File::PATH_SEPARATOR).each do |path|
environment.append_path path
end
OptionParser.new do |opts|
opts.summary_width = 28
opts.banner = "Usage: sprockets [options] filename [filename ...]"
def opts.show_usage
puts self
exit 1
end
opts.on("-r", "--require LIBRARY", "Require the LIBRARY before doing anything") do |lib|
require lib
end
opts.on("-I DIRECTORY", "--include=DIRECTORY", "Adds the directory to the Sprockets load path") do |directory|
environment.append_path directory
end
opts.on("-o DIRECTORY", "--output=DIRECTORY", "Copy provided assets into DIRECTORY") do |directory|
manifest = Sprockets::Manifest.new(environment, directory)
end
opts.on("--css-compressor=COMPRESSOR", "Use CSS compressor") do |compressor|
environment.css_compressor = compressor.to_sym
end
opts.on("--js-compressor=COMPRESSOR", "Use JavaScript compressor") do |compressor|
environment.js_compressor = compressor.to_sym
end
opts.on("--noenv", "Disables .sprocketsrc file") do
end
opts.on_tail("-h", "--help", "Shows this help message") do
opts.show_usage
end
opts.on_tail("-v", "--version", "Shows version") do
puts Sprockets::VERSION
exit
end
opts.show_usage if ARGV.empty?
begin
opts.order(ARGV) do |filename|
filenames << File.expand_path(filename)
end
rescue OptionParser::ParseError => e
opts.warn e.message
opts.show_usage
end
end
if environment.paths.empty?
warn "No load paths given"
warn "Usage: sprockets -Ijavascripts/ filename"
exit 1
end
if manifest
manifest.compile(filenames)
elsif filenames.length == 1
puts environment.find_asset(filenames.first).to_s
else
warn "Only one file can be compiled to stdout at a time"
exit 1
end
sprockets-3.7.2/gemfiles/ 0000775 0000000 0000000 00000000000 13312220220 0015316 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/gemfiles/Gemfile-1.9 0000664 0000000 0000000 00000000155 13312220220 0017117 0 ustar 00root root 0000000 0000000 source "https://rubygems.org"
gemspec path: '..'
gem 'sass', '< 3.4'
gem 'rack', '< 2.0'
gem 'json', '< 2.0'
sprockets-3.7.2/guides/ 0000775 0000000 0000000 00000000000 13312220220 0015003 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/guides/building_an_asset_processing_framework.md 0000664 0000000 0000000 00000003067 13312220220 0025316 0 ustar 00root root 0000000 0000000 # Building an Asset Processing Framework
This guide is for using a Sprockets::Environment to process assets. You would use this class directly if you were building a feature similar to Rail's asset pipeline. If you aren't building an asset processing frameworks, you will want to refer to the [End User Asset Generation](end_user_asset_generation.md) guide instead. For a reference use of `Sprockets::Environemnt` see [sprockets-rails](github.com/rails/Sprockets-rails).
## Gzip
By default when Sprockets generates a compiled asset file it will also produce a gzipped copy of that file. Sprockets only gzips non-binary files such as CSS, JavaScript, and SVG files.
For example if Sprockets is generating
```
application-12345.css
```
Then it will also generate a compressed copy in
```
application-12345.css.gz
```
You can disable this behavior `Sprockets::Environemnt#gzip=` to something falsey for example:
```ruby
env = Sprockets::Environment.new(".")
env.gzip = false
```
## WIP
This guide is a work in progress. There are many different groups of people who interact with Sprockets. Some only need to know directive syntax to put in their asset files, some are building features like the Rails asset pipeline, and some are plugging into Sprockets and writing things like preprocessors. The goal of these guides are to provide task specific guidance to make the expected behavior explicit. If you are using Sprockets and you find missing information in these guides, please consider submitting a pull request with updated information.
These guides live in [guides](/guides).
sprockets-3.7.2/guides/end_user_asset_generation.md 0000664 0000000 0000000 00000016710 13312220220 0022550 0 ustar 00root root 0000000 0000000 # End User Asset Generation
This guide is for those using Sprockets through an interface similar to the Rails asset pipeline. It will talk about end user interfaces. If you are not the end user, but instead a tools developer who is building an asset processing framework, see [building an asset processing framework](building_an_asset_processing_framework.md).
## What is Sprockets?
Sprockets is a Ruby library for compiling and serving web assets.
It features declarative dependency management for JavaScript and CSS
assets, as well as a preprocessor pipeline that allows you to
write assets in languages like CoffeeScript, Sass and SCSS.
## Behavior Overview
You can interact through Sprockets primarily through directives and file extensions. This section covers how to use each of these things, and the defaults that ship with Sprockets.
Since you are likely using Sprockets through another framework (like the Rails asset pipeline), there will configuration options you can toggle that will change behavior such as what directories or files get compiled. For that documentation you should see your framework's documentation.
### Directives
Directives are special comments in your asset file and the main way of interacting with processors. What kind of interactions? You can use these directives to tell Sprockets to load other files, or specify dependencies on other assets.
For example, let's say you have custom JavaScript that you've written. You put this javascript in a file called `beta.js`. The javascript makes heavy use of jQuery, so you need to load that before your code executes. You could add this directive to the top of `beta.js`:
```js
//= require jquery
$().ready({
// my custom code here
})
```
The directive processor understands comment blocks in three formats:
``` css
/* Multi-line comment blocks (CSS, SCSS, JavaScript)
*= require foo
*/
```
``` js
// Single-line comment blocks (SCSS, JavaScript)
//= require foo
```
``` coffee
# Single-line comment blocks (CoffeeScript)
#= require foo
```
Here is a list of the available directives
- require
- require_self
- require_tree
- require_directory
- depend
- depend_on_asset
- stub
- link
- link_directory
- link_tree
You can see what each of these does below
### Specifying Processors through File Extensions
Sprockets uses the filename extensions to determine what processors to run on your file and in what order. For example if you have a file:
```
application.scss
```
Then Sprockets will by default run the sass processor (which implements scss). The output file will be converted to css.
You can specify multiple processors by specifying multiple file extensions. For example you can use Ruby's [ERB template language](http://ruby-doc.org/stdlib-2.2.3/libdoc/erb/rdoc/ERB.html) to embed content in your doc before running the sass processor. To accomplish this you would need to name your file
```
application.scss.erb
```
Processors are run from right to left, so in the above example the processor associated with `erb` will be run before the processor associated with `scss` extension.
For a description of the processors that Sprockets has by default see the "default processors" section below. Other libraries may register additional processors.
## File Order Processing
By default files are processed in alphabetical order. This behavior can impact your asset compilation when one asset needs to be loaded before another.
For example if you have an `application.js` and it loads another directory
```js
//= require_directory my_javascript
```
The files in that directory will be loaded in alphabetical order. If the directory looks like this:
```sh
$ ls -1 my_javascript/
alpha.js
beta.js
jquery.js
```
Then `alpha.js` will be loaded before either of the other two. This can be a problem if `alpha.js` uses jquery. For this reason it is not recommend to use `require_directory` with files that are ordering dependent. You can either require individual files manually:
```
//= require jquery
//= require alpha
//= require beta
```
Or you can use index files to proxy your folders
### Index files are proxies for folders
In Sprockets index files such as `index.js` or `index.css` files inside of a folder will generate a file with the folder's name. So if you have a `foo/index.js` file it will compile down to `foo.js`. This is similar to NPM's behavior of using [folders as modules](https://nodejs.org/api/modules.html#modules_folders_as_modules). It is also somewhat similar to the way that a file in `public/my_folder/index.html` can be reached by a request to `/my_folder`. This means that you cannot directly use an index file. For example this would not work:
```
<%= asset_path("foo/index.js") %>
```
Instead you would need to use:
```
<%= asset_path("foo.js") %>
```
Why would you want to use this behavior? It is common behavior where you might want to include an entire directory of files in a top level JavaScript. You can do this in Sprockets using `require_tree .`
```
//= require_tree .
```
This has the problem that files are required alphabetically. If your directory has `jquery-ui.js` and `jquery.min.js` then Sprockets will require `jquery-ui.js` before `jquery` is required which won't work (because jquery-ui depends on jquery). Previously the only way to get the correct ordering would be to rename your files, something like `0-jquery-ui.js`. Instead of doing that you can use an index file.
For example, if you have an `application.js` and want all the files in the `foo/` folder you could do this:
```
//= require foo.js
```
Then create a file `foo/index.js` that requires all the files in that folder in any order you want:
```
//= require foo.min.js
//= require foo-ui.js
```
Now in your `application.js` will correctly load the `foo.min.js` before `foo-ui.js`. If you used `require_tree` it would not work correctly.
## Default Directives
TODO: add description of each of the directives
## Default Processors
TODO: add description of each of the processors
## Default Compressors
TODO:
## Output
This section details the default output of Sprockets. This may have been modified by the frameworks you're using, so you will want to verify behavior with their docs.
Processors and compressors will affect individual file output contents. Refer to the default processors and compressor section how processors for your asset may have modified your file.
### Manifest File
TODO: Explain contents, location, and name of a manifest file.
### Fingerprinting
TODO: Explain default fingerprinting/digest behavior
### Gzip
By default when Sprockets generates a compiled asset file it will also produce a gzipped copy of that file. Sprockets only gzips non-binary files such as CSS, javascript, and SVG files.
For example if Sprockets is generating
```
application-12345.css
```
Then it will also generate a compressed copy in
```
application-12345.css.gz
```
This behavior can be disabled, refer to your framework specific documentation.
## WIP
This guide is a work in progress. There are many different groups of people who interact with Sprockets. Some only need to know directive syntax to put in their asset files, some are building features like the Rails asset pipeline, and some are plugging into Sprockets and writing things like preprocessors. The goal of these guides are to provide task specific guidance to make the expected behavior explicit. If you are using Sprockets and you find missing information in these guides, please consider submitting a pull request with updated information.
These guides live in [guides](/guides).
sprockets-3.7.2/guides/extending_sprockets.md 0000664 0000000 0000000 00000001421 13312220220 0021405 0 ustar 00root root 0000000 0000000 # Extending Sprockets
Sprockets can use custom processors, compressors, and directives. This document is intended for library authors who want to extend sprockets functionality.
## WIP
This guide is a work in progress. There are many different groups of people who interact with sprockets. Some only need to know directive syntax to put in their asset files, some are building features like the Rails asset pipeline, and some are plugging into sprockets and writing things like preprocessors. The goal of these guides are to provide task specific guidance to make the expected behavior explicit. If you are using sprockets and you find missing information in these guides, please consider submitting a pull request with updated information.
These guides live in [guides](/guides).
sprockets-3.7.2/lib/ 0000775 0000000 0000000 00000000000 13312220220 0014271 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/rake/ 0000775 0000000 0000000 00000000000 13312220220 0015213 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/rake/sprocketstask.rb 0000664 0000000 0000000 00000007311 13312220220 0020442 0 ustar 00root root 0000000 0000000 require 'rake'
require 'rake/tasklib'
require 'sprockets'
require 'logger'
module Rake
# Simple Sprockets compilation Rake task macro.
#
# Rake::SprocketsTask.new do |t|
# t.environment = Sprockets::Environment.new
# t.output = "./public/assets"
# t.assets = %w( application.js application.css )
# end
#
class SprocketsTask < Rake::TaskLib
# Name of the task. Defaults to "assets".
#
# The name will also be used to suffix the clean and clobber
# tasks, "clean_assets" and "clobber_assets".
attr_accessor :name
# `Environment` instance used for finding assets.
#
# You'll most likely want to reassign `environment` to your own.
#
# Rake::SprocketsTask.new do |t|
# t.environment = Foo::Assets
# end
#
def environment
if !@environment.is_a?(Sprockets::Base) && @environment.respond_to?(:call)
@environment = @environment.call
else
@environment
end
end
attr_writer :environment
# Returns cached cached environment
def cached
@cached ||= environment.cached if environment
end
alias_method :index, :cached
# `Manifest` instance used for already compiled assets.
#
# Will be created by default if an environment and output
# directory are given
def manifest
if !@manifest.is_a?(Sprockets::Manifest) && @manifest.respond_to?(:call)
@manifest = @manifest.call
else
@manifest
end
end
attr_writer :manifest
# Directory to write compiled assets too. As well as the manifest file.
#
# t.output = "./public/assets"
#
attr_accessor :output
# Array of asset logical paths to compile.
#
# t.assets = %w( application.js jquery.js application.css )
#
attr_accessor :assets
# Number of old assets to keep.
attr_accessor :keep
# Logger to use during rake tasks. Defaults to using stderr.
#
# t.logger = Logger.new($stdout)
#
attr_accessor :logger
# Returns logger level Integer.
def log_level
@logger.level
end
# Set logger level with constant or symbol.
#
# t.log_level = Logger::INFO
# t.log_level = :debug
#
def log_level=(level)
if level.is_a?(Integer)
@logger.level = level
else
@logger.level = Logger.const_get(level.to_s.upcase)
end
end
def initialize(name = :assets)
@name = name
@environment = lambda { Sprockets::Environment.new(Dir.pwd) }
@manifest = lambda { Sprockets::Manifest.new(cached, output) }
@logger = Logger.new($stderr)
@logger.level = Logger::INFO
@keep = 2
yield self if block_given?
define
end
# Define tasks
def define
desc name == :assets ? "Compile assets" : "Compile #{name} assets"
task name do
with_logger do
manifest.compile(assets)
end
end
desc name == :assets ? "Remove all assets" : "Remove all #{name} assets"
task "clobber_#{name}" do
with_logger do
manifest.clobber
end
end
task :clobber => ["clobber_#{name}"]
desc name == :assets ? "Clean old assets" : "Clean old #{name} assets"
task "clean_#{name}" do
with_logger do
manifest.clean(keep)
end
end
task :clean => ["clean_#{name}"]
end
private
# Sub out environment logger with our rake task logger that
# writes to stderr.
def with_logger
if env = manifest.environment
old_logger = env.logger
env.logger = @logger
end
yield
ensure
env.logger = old_logger if env
end
end
end
sprockets-3.7.2/lib/sprockets.rb 0000664 0000000 0000000 00000015004 13312220220 0016633 0 ustar 00root root 0000000 0000000 # encoding: utf-8
require 'sprockets/version'
require 'sprockets/cache'
require 'sprockets/environment'
require 'sprockets/errors'
require 'sprockets/manifest'
require 'sprockets/deprecation'
module Sprockets
require 'sprockets/processor_utils'
extend ProcessorUtils
# Extend Sprockets module to provide global registry
require 'sprockets/configuration'
require 'sprockets/context'
require 'digest/sha2'
extend Configuration
self.config = {
bundle_processors: Hash.new { |h, k| [].freeze }.freeze,
bundle_reducers: Hash.new { |h, k| {}.freeze }.freeze,
compressors: Hash.new { |h, k| {}.freeze }.freeze,
dependencies: Set.new.freeze,
dependency_resolvers: {}.freeze,
digest_class: Digest::SHA256,
engine_mime_types: {}.freeze,
engines: {}.freeze,
mime_exts: {}.freeze,
mime_types: {}.freeze,
paths: [].freeze,
pipelines: {}.freeze,
postprocessors: Hash.new { |h, k| [].freeze }.freeze,
preprocessors: Hash.new { |h, k| [].freeze }.freeze,
registered_transformers: Hash.new { |h, k| {}.freeze }.freeze,
root: File.expand_path('..', __FILE__).freeze,
transformers: Hash.new { |h, k| {}.freeze }.freeze,
version: "",
gzip_enabled: true
}.freeze
self.computed_config = {}
@context_class = Context
require 'logger'
@logger = Logger.new($stderr)
@logger.level = Logger::FATAL
# Common asset text types
register_mime_type 'application/javascript', extensions: ['.js'], charset: :unicode
register_mime_type 'application/json', extensions: ['.json'], charset: :unicode
register_mime_type 'application/xml', extensions: ['.xml']
register_mime_type 'text/css', extensions: ['.css'], charset: :css
register_mime_type 'text/html', extensions: ['.html', '.htm'], charset: :html
register_mime_type 'text/plain', extensions: ['.txt', '.text']
register_mime_type 'text/yaml', extensions: ['.yml', '.yaml'], charset: :unicode
# Common image types
register_mime_type 'image/x-icon', extensions: ['.ico']
register_mime_type 'image/bmp', extensions: ['.bmp']
register_mime_type 'image/gif', extensions: ['.gif']
register_mime_type 'image/webp', extensions: ['.webp']
register_mime_type 'image/png', extensions: ['.png']
register_mime_type 'image/jpeg', extensions: ['.jpg', '.jpeg']
register_mime_type 'image/tiff', extensions: ['.tiff', '.tif']
register_mime_type 'image/svg+xml', extensions: ['.svg']
# Common audio/video types
register_mime_type 'video/webm', extensions: ['.webm']
register_mime_type 'audio/basic', extensions: ['.snd', '.au']
register_mime_type 'audio/aiff', extensions: ['.aiff']
register_mime_type 'audio/mpeg', extensions: ['.mp3', '.mp2', '.m2a', '.m3a']
register_mime_type 'application/ogg', extensions: ['.ogx']
register_mime_type 'audio/midi', extensions: ['.midi', '.mid']
register_mime_type 'video/avi', extensions: ['.avi']
register_mime_type 'audio/wave', extensions: ['.wav', '.wave']
register_mime_type 'video/mp4', extensions: ['.mp4', '.m4v']
# Common font types
register_mime_type 'application/vnd.ms-fontobject', extensions: ['.eot']
register_mime_type 'application/x-font-opentype', extensions: ['.otf']
register_mime_type 'application/x-font-ttf', extensions: ['.ttf']
register_mime_type 'application/font-woff', extensions: ['.woff']
register_pipeline :source do |env|
[]
end
register_pipeline :self do |env, type, file_type, engine_extnames|
env.self_processors_for(type, file_type, engine_extnames)
end
register_pipeline :default do |env, type, file_type, engine_extnames|
env.default_processors_for(type, file_type, engine_extnames)
end
require 'sprockets/directive_processor'
register_preprocessor 'text/css', DirectiveProcessor.new(
comments: ["//", ["/*", "*/"]]
)
register_preprocessor 'application/javascript', DirectiveProcessor.new(
comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]]
)
require 'sprockets/bundle'
register_bundle_processor 'application/javascript', Bundle
register_bundle_processor 'text/css', Bundle
register_bundle_metadata_reducer '*/*', :data, proc { "" }, :concat
register_bundle_metadata_reducer 'application/javascript', :data, proc { "" }, Utils.method(:concat_javascript_sources)
register_bundle_metadata_reducer '*/*', :links, :+
require 'sprockets/closure_compressor'
require 'sprockets/sass_compressor'
require 'sprockets/uglifier_compressor'
require 'sprockets/yui_compressor'
register_compressor 'text/css', :sass, SassCompressor
register_compressor 'text/css', :scss, SassCompressor
register_compressor 'text/css', :yui, YUICompressor
register_compressor 'application/javascript', :closure, ClosureCompressor
register_compressor 'application/javascript', :uglifier, UglifierCompressor
register_compressor 'application/javascript', :uglify, UglifierCompressor
register_compressor 'application/javascript', :yui, YUICompressor
# Mmm, CoffeeScript
require 'sprockets/coffee_script_processor'
Deprecation.silence do
register_engine '.coffee', CoffeeScriptProcessor, mime_type: 'application/javascript', silence_deprecation: true
end
# JST engines
require 'sprockets/eco_processor'
require 'sprockets/ejs_processor'
require 'sprockets/jst_processor'
Deprecation.silence do
register_engine '.jst', JstProcessor, mime_type: 'application/javascript', silence_deprecation: true
register_engine '.eco', EcoProcessor, mime_type: 'application/javascript', silence_deprecation: true
register_engine '.ejs', EjsProcessor, mime_type: 'application/javascript', silence_deprecation: true
end
# CSS engines
require 'sprockets/sass_processor'
Deprecation.silence do
register_engine '.sass', SassProcessor, mime_type: 'text/css', silence_deprecation: true
register_engine '.scss', ScssProcessor, mime_type: 'text/css', silence_deprecation: true
end
register_bundle_metadata_reducer 'text/css', :sass_dependencies, Set.new, :+
# Other
require 'sprockets/erb_processor'
register_engine '.erb', ERBProcessor, mime_type: 'text/plain', silence_deprecation: true
register_dependency_resolver 'environment-version' do |env|
env.version
end
register_dependency_resolver 'environment-paths' do |env|
env.paths.map {|path| env.compress_from_root(path) }
end
register_dependency_resolver 'file-digest' do |env, str|
env.file_digest(env.parse_file_digest_uri(str))
end
register_dependency_resolver 'processors' do |env, str|
env.resolve_processors_cache_key_uri(str)
end
depend_on 'environment-version'
depend_on 'environment-paths'
end
require 'sprockets/legacy'
sprockets-3.7.2/lib/sprockets/ 0000775 0000000 0000000 00000000000 13312220220 0016306 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/sprockets/asset.rb 0000664 0000000 0000000 00000011434 13312220220 0017755 0 ustar 00root root 0000000 0000000 require 'fileutils'
require 'sprockets/digest_utils'
module Sprockets
class Asset
attr_reader :logical_path
# Private: Intialize Asset wrapper from attributes Hash.
#
# Asset wrappers should not be initialized directly, only
# Environment#find_asset should vend them.
#
# attributes - Hash of ivars
#
# Returns Asset.
def initialize(environment, attributes = {})
@environment = environment
@attributes = attributes
@content_type = attributes[:content_type]
@filename = attributes[:filename]
@id = attributes[:id]
@load_path = attributes[:load_path]
@logical_path = attributes[:logical_path]
@metadata = attributes[:metadata]
@mtime = attributes[:mtime]
@name = attributes[:name]
@source = attributes[:source]
@uri = attributes[:uri]
end
# Internal: Return all internal instance variables as a hash.
#
# Returns a Hash.
def to_hash
@attributes
end
# Public: Metadata accumulated from pipeline process.
#
# The API status of the keys is dependent on the pipeline processors
# itself. So some values maybe considered public and others internal.
# See the pipeline proccessor documentation itself.
#
# Returns Hash.
attr_reader :metadata
# Public: Returns String path of asset.
attr_reader :filename
# Internal: Unique asset object ID.
#
# Returns a String.
attr_reader :id
# Public: Internal URI to lookup asset by.
#
# NOT a publically accessible URL.
#
# Returns URI.
attr_reader :uri
# Public: Return logical path with digest spliced in.
#
# "foo/bar-37b51d194a7513e45b56f6524f2d51f2.js"
#
# Returns String.
def digest_path
logical_path.sub(/\.(\w+)$/) { |ext| "-#{etag}#{ext}" }
end
# Public: Returns String MIME type of asset. Returns nil if type is unknown.
attr_reader :content_type
# Public: Get all externally linked asset filenames from asset.
#
# All linked assets should be compiled anytime this asset is.
#
# Returns Set of String asset URIs.
def links
metadata[:links] || Set.new
end
# Public: Get all internally required assets that were concated into this
# asset.
#
# Returns Array of String asset URIs.
def included
metadata[:included]
end
# Public: Return `String` of concatenated source.
#
# Returns String.
def source
if @source
@source
else
# File is read everytime to avoid memory bloat of large binary files
File.binread(filename)
end
end
# Public: Alias for #source.
#
# Returns String.
def to_s
source
end
# Public: Get charset of source.
#
# Returns a String charset name or nil if binary.
def charset
metadata[:charset]
end
# Public: Returns Integer length of source.
def length
metadata[:length]
end
alias_method :bytesize, :length
# Public: Returns String hexdigest of source.
def hexdigest
DigestUtils.pack_hexdigest(metadata[:digest])
end
# Deprecated: Returns String hexdigest of source.
#
# In 4.x this will be changed to return a raw Digest byte String.
alias_method :digest, :hexdigest
# Pubic: ETag String of Asset.
alias_method :etag, :hexdigest
# Public: Returns String base64 digest of source.
def base64digest
DigestUtils.pack_base64digest(metadata[:digest])
end
# Public: A "named information" URL for subresource integrity.
def integrity
DigestUtils.integrity_uri(metadata[:digest])
end
# Public: Add enumerator to allow `Asset` instances to be used as Rack
# compatible body objects.
#
# block
# part - String body chunk
#
# Returns nothing.
def each
yield to_s
end
# Deprecated: Save asset to disk.
#
# filename - String target
#
# Returns nothing.
def write_to(filename)
FileUtils.mkdir_p File.dirname(filename)
PathUtils.atomic_write(filename) do |f|
f.write source
end
# Set mtime correctly
File.utime(mtime, mtime, filename)
nil
end
# Public: Pretty inspect
#
# Returns String.
def inspect
"#<#{self.class}:#{object_id.to_s(16)} #{uri.inspect}>"
end
# Public: Implements Object#hash so Assets can be used as a Hash key or
# in a Set.
#
# Returns Integer hash of the id.
def hash
id.hash
end
# Public: Compare assets.
#
# Assets are equal if they share the same path and digest.
#
# Returns true or false.
def eql?(other)
self.class == other.class && self.id == other.id
end
alias_method :==, :eql?
end
end
sprockets-3.7.2/lib/sprockets/autoload.rb 0000664 0000000 0000000 00000000610 13312220220 0020440 0 ustar 00root root 0000000 0000000 module Sprockets
module Autoload
autoload :Closure, 'sprockets/autoload/closure'
autoload :CoffeeScript, 'sprockets/autoload/coffee_script'
autoload :Eco, 'sprockets/autoload/eco'
autoload :EJS, 'sprockets/autoload/ejs'
autoload :Sass, 'sprockets/autoload/sass'
autoload :Uglifier, 'sprockets/autoload/uglifier'
autoload :YUI, 'sprockets/autoload/yui'
end
end
sprockets-3.7.2/lib/sprockets/autoload/ 0000775 0000000 0000000 00000000000 13312220220 0020116 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/sprockets/autoload/closure.rb 0000664 0000000 0000000 00000000141 13312220220 0022113 0 ustar 00root root 0000000 0000000 require 'closure-compiler'
module Sprockets
module Autoload
Closure = ::Closure
end
end
sprockets-3.7.2/lib/sprockets/autoload/coffee_script.rb 0000664 0000000 0000000 00000000150 13312220220 0023252 0 ustar 00root root 0000000 0000000 require 'coffee_script'
module Sprockets
module Autoload
CoffeeScript = ::CoffeeScript
end
end
sprockets-3.7.2/lib/sprockets/autoload/eco.rb 0000664 0000000 0000000 00000000114 13312220220 0021205 0 ustar 00root root 0000000 0000000 require 'eco'
module Sprockets
module Autoload
Eco = ::Eco
end
end
sprockets-3.7.2/lib/sprockets/autoload/ejs.rb 0000664 0000000 0000000 00000000114 13312220220 0021220 0 ustar 00root root 0000000 0000000 require 'ejs'
module Sprockets
module Autoload
EJS = ::EJS
end
end
sprockets-3.7.2/lib/sprockets/autoload/sass.rb 0000664 0000000 0000000 00000000117 13312220220 0021413 0 ustar 00root root 0000000 0000000 require 'sass'
module Sprockets
module Autoload
Sass = ::Sass
end
end
sprockets-3.7.2/lib/sprockets/autoload/uglifier.rb 0000664 0000000 0000000 00000000133 13312220220 0022246 0 ustar 00root root 0000000 0000000 require 'uglifier'
module Sprockets
module Autoload
Uglifier = ::Uglifier
end
end
sprockets-3.7.2/lib/sprockets/autoload/yui.rb 0000664 0000000 0000000 00000000127 13312220220 0021251 0 ustar 00root root 0000000 0000000 require 'yui/compressor'
module Sprockets
module Autoload
YUI = ::YUI
end
end
sprockets-3.7.2/lib/sprockets/base.rb 0000664 0000000 0000000 00000005612 13312220220 0017551 0 ustar 00root root 0000000 0000000 require 'sprockets/asset'
require 'sprockets/bower'
require 'sprockets/cache'
require 'sprockets/configuration'
require 'sprockets/digest_utils'
require 'sprockets/errors'
require 'sprockets/loader'
require 'sprockets/path_digest_utils'
require 'sprockets/path_dependency_utils'
require 'sprockets/path_utils'
require 'sprockets/resolve'
require 'sprockets/server'
require 'sprockets/loader'
require 'sprockets/uri_tar'
module Sprockets
# `Base` class for `Environment` and `Cached`.
class Base
include PathUtils, PathDependencyUtils, PathDigestUtils, DigestUtils
include Configuration
include Server
include Resolve, Loader
include Bower
# Get persistent cache store
attr_reader :cache
# Set persistent cache store
#
# The cache store must implement a pair of getters and
# setters. Either `get(key)`/`set(key, value)`,
# `[key]`/`[key]=value`, `read(key)`/`write(key, value)`.
def cache=(cache)
@cache = Cache.new(cache, logger)
end
# Return an `Cached`. Must be implemented by the subclass.
def cached
raise NotImplementedError
end
alias_method :index, :cached
# Internal: Compute digest for path.
#
# path - String filename or directory path.
#
# Returns a String digest or nil.
def file_digest(path)
if stat = self.stat(path)
# Caveat: Digests are cached by the path's current mtime. Its possible
# for a files contents to have changed and its mtime to have been
# negligently reset thus appearing as if the file hasn't changed on
# disk. Also, the mtime is only read to the nearest second. It's
# also possible the file was updated more than once in a given second.
key = UnloadedAsset.new(path, self).file_digest_key(stat.mtime.to_i)
cache.fetch(key) do
self.stat_digest(path, stat)
end
end
end
# Find asset by logical path or expanded path.
def find_asset(path, options = {})
uri, _ = resolve(path, options.merge(compat: false))
if uri
load(uri)
end
end
def find_all_linked_assets(path, options = {})
return to_enum(__method__, path, options) unless block_given?
asset = find_asset(path, options)
return unless asset
yield asset
stack = asset.links.to_a
while uri = stack.shift
yield asset = load(uri)
stack = asset.links.to_a + stack
end
nil
end
# Preferred `find_asset` shorthand.
#
# environment['application.js']
#
def [](*args)
find_asset(*args)
end
# Pretty inspect
def inspect
"#<#{self.class}:0x#{object_id.to_s(16)} " +
"root=#{root.to_s.inspect}, " +
"paths=#{paths.inspect}>"
end
def compress_from_root(uri)
URITar.new(uri, self).compress
end
def expand_from_root(uri)
URITar.new(uri, self).expand
end
end
end
sprockets-3.7.2/lib/sprockets/bower.rb 0000664 0000000 0000000 00000003175 13312220220 0017757 0 ustar 00root root 0000000 0000000 require 'json'
module Sprockets
module Bower
# Internal: All supported bower.json files.
#
# https://github.com/bower/json/blob/0.4.0/lib/json.js#L7
POSSIBLE_BOWER_JSONS = ['bower.json', 'component.json', '.bower.json']
# Internal: Override resolve_alternates to install bower.json behavior.
#
# load_path - String environment path
# logical_path - String path relative to base
#
# Returns candiate filenames.
def resolve_alternates(load_path, logical_path)
candidates, deps = super
# bower.json can only be nested one level deep
if !logical_path.index('/')
dirname = File.join(load_path, logical_path)
if directory?(dirname)
filenames = POSSIBLE_BOWER_JSONS.map { |basename| File.join(dirname, basename) }
filename = filenames.detect { |fn| self.file?(fn) }
if filename
deps << build_file_digest_uri(filename)
read_bower_main(dirname, filename) do |path|
candidates << path
end
end
end
end
return candidates, deps
end
# Internal: Read bower.json's main directive.
#
# dirname - String path to component directory.
# filename - String path to bower.json.
#
# Returns nothing.
def read_bower_main(dirname, filename)
bower = JSON.parse(File.read(filename), create_additions: false)
case bower['main']
when String
yield File.expand_path(bower['main'], dirname)
when Array
bower['main'].each do |name|
yield File.expand_path(name, dirname)
end
end
end
end
end
sprockets-3.7.2/lib/sprockets/bundle.rb 0000664 0000000 0000000 00000004161 13312220220 0020106 0 ustar 00root root 0000000 0000000 require 'set'
require 'sprockets/utils'
module Sprockets
# Internal: Bundle processor takes a single file asset and prepends all the
# `:required` URIs to the contents.
#
# Uses pipeline metadata:
#
# :required - Ordered Set of asset URIs to prepend
# :stubbed - Set of asset URIs to substract from the required set.
#
# Also see DirectiveProcessor.
class Bundle
def self.call(input)
env = input[:environment]
type = input[:content_type]
dependencies = Set.new(input[:metadata][:dependencies])
processed_uri, deps = env.resolve(input[:filename], accept: type, pipeline: :self, compat: false)
dependencies.merge(deps)
find_required = proc { |uri| env.load(uri).metadata[:required] }
required = Utils.dfs(processed_uri, &find_required)
stubbed = Utils.dfs(env.load(processed_uri).metadata[:stubbed], &find_required)
required.subtract(stubbed)
assets = required.map { |uri| env.load(uri) }
(required + stubbed).each do |uri|
dependencies.merge(env.load(uri).metadata[:dependencies])
end
reducers = Hash[env.match_mime_type_keys(env.config[:bundle_reducers], type).flat_map(&:to_a)]
process_bundle_reducers(assets, reducers).merge(dependencies: dependencies, included: assets.map(&:uri))
end
# Internal: Run bundle reducers on set of Assets producing a reduced
# metadata Hash.
#
# assets - Array of Assets
# reducers - Array of [initial, reducer_proc] pairs
#
# Returns reduced asset metadata Hash.
def self.process_bundle_reducers(assets, reducers)
initial = {}
reducers.each do |k, (v, _)|
if v.respond_to?(:call)
initial[k] = v.call
elsif !v.nil?
initial[k] = v
end
end
assets.reduce(initial) do |h, asset|
reducers.each do |k, (_, block)|
value = k == :data ? asset.source : asset.metadata[k]
if h.key?(k)
if !value.nil?
h[k] = block.call(h[k], value)
end
else
h[k] = value
end
end
h
end
end
end
end
sprockets-3.7.2/lib/sprockets/cache.rb 0000664 0000000 0000000 00000015150 13312220220 0017700 0 ustar 00root root 0000000 0000000 require 'logger'
require 'sprockets/digest_utils'
module Sprockets
# Public: Wrapper interface to backend cache stores. Ensures a consistent API
# even when the backend uses get/set or read/write.
#
# Public cache interface
#
# Always assign the backend store instance to Environment#cache=.
#
# environment.cache = Sprockets::Cache::MemoryStore.new(1000)
#
# Environment#cache will always return a wrapped Cache interface. See the
# methods marked public on this class.
#
#
# Backend cache interface
#
# The Backend cache store must implement two methods.
#
# get(key)
#
# key - An opaque String with a length less than 250 characters.
#
# Returns an JSON serializable object.
#
# set(key, value)
#
# Will only be called once per key. Setting a key "foo" with value "bar",
# then later key "foo" with value "baz" is an undefined behavior.
#
# key - An opaque String with a length less than 250 characters.
# value - A JSON serializable object.
#
# Returns argument value.
#
class Cache
# Builtin cache stores.
autoload :FileStore, 'sprockets/cache/file_store'
autoload :MemoryStore, 'sprockets/cache/memory_store'
autoload :NullStore, 'sprockets/cache/null_store'
# Internal: Cache key version for this class. Rarely should have to change
# unless the cache format radically changes. Will be bump on major version
# releases though.
VERSION = '3.0'
def self.default_logger
logger = Logger.new($stderr)
logger.level = Logger::FATAL
logger
end
# Internal: Wrap a backend cache store.
#
# Always assign a backend cache store instance to Environment#cache= and
# use Environment#cache to retreive a wrapped interface.
#
# cache - A compatible backend cache store instance.
def initialize(cache = nil, logger = self.class.default_logger)
@cache_wrapper = get_cache_wrapper(cache)
@fetch_cache = Cache::MemoryStore.new(1024)
@logger = logger
end
# Public: Prefer API to retrieve and set values in the cache store.
#
# key - JSON serializable key
# block -
# Must return a consistent JSON serializable object for the given key.
#
# Examples
#
# cache.fetch("foo") { "bar" }
#
# Returns a JSON serializable object.
def fetch(key)
start = Time.now.to_f
expanded_key = expand_key(key)
value = @fetch_cache.get(expanded_key)
if value.nil?
value = @cache_wrapper.get(expanded_key)
if value.nil?
value = yield
@cache_wrapper.set(expanded_key, value)
@logger.debug do
ms = "(#{((Time.now.to_f - start) * 1000).to_i}ms)"
"Sprockets Cache miss #{peek_key(key)} #{ms}"
end
end
@fetch_cache.set(expanded_key, value)
end
value
end
# Public: Low level API to retrieve item directly from the backend cache
# store.
#
# This API may be used publicly, but may have undefined behavior
# depending on the backend store being used. Prefer the
# Cache#fetch API over using this.
#
# key - JSON serializable key
# local - Check local cache first (default: false)
#
# Returns a JSON serializable object or nil if there was a cache miss.
def get(key, local = false)
expanded_key = expand_key(key)
if local && value = @fetch_cache.get(expanded_key)
return value
end
value = @cache_wrapper.get(expanded_key)
@fetch_cache.set(expanded_key, value) if local
value
end
# Public: Low level API to set item directly to the backend cache store.
#
# This API may be used publicly, but may have undefined behavior
# depending on the backend store being used. Prefer the
# Cache#fetch API over using this.
#
# key - JSON serializable key
# value - A consistent JSON serializable object for the given key. Setting
# a different value for the given key has undefined behavior.
# local - Set on local cache (default: false)
#
# Returns the value argument.
def set(key, value, local = false)
expanded_key = expand_key(key)
@fetch_cache.set(expanded_key, value) if local
@cache_wrapper.set(expanded_key, value)
end
# Public: Pretty inspect
#
# Returns String.
def inspect
"#<#{self.class} local=#{@fetch_cache.inspect} store=#{@cache_wrapper.cache.inspect}>"
end
private
# Internal: Expand object cache key into a short String key.
#
# The String should be under 250 characters so its compatible with
# Memcache.
#
# key - JSON serializable key
#
# Returns a String with a length less than 250 characters.
def expand_key(key)
digest_key = DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key))
namespace = digest_key[0, 2]
"sprockets/v#{VERSION}/#{namespace}/#{digest_key}"
end
PEEK_SIZE = 100
# Internal: Show first 100 characters of cache key for logging purposes.
#
# Returns a String with a length less than 100 characters.
def peek_key(key)
case key
when Integer
key.to_s
when String
key[0, PEEK_SIZE].inspect
when Array
str = []
key.each { |k| str << peek_key(k) }
str.join(':')[0, PEEK_SIZE]
else
peek_key(DigestUtils.pack_urlsafe_base64digest(DigestUtils.digest(key)))
end
end
def get_cache_wrapper(cache)
if cache.is_a?(Cache)
cache
# `Cache#get(key)` for Memcache
elsif cache.respond_to?(:get)
GetWrapper.new(cache)
# `Cache#[key]` so `Hash` can be used
elsif cache.respond_to?(:[])
HashWrapper.new(cache)
# `Cache#read(key)` for `ActiveSupport::Cache` support
elsif cache.respond_to?(:read)
ReadWriteWrapper.new(cache)
else
cache = Sprockets::Cache::NullStore.new
GetWrapper.new(cache)
end
end
class Wrapper < Struct.new(:cache)
end
class GetWrapper < Wrapper
def get(key)
cache.get(key)
end
def set(key, value)
cache.set(key, value)
end
end
class HashWrapper < Wrapper
def get(key)
cache[key]
end
def set(key, value)
cache[key] = value
end
end
class ReadWriteWrapper < Wrapper
def get(key)
cache.read(key)
end
def set(key, value)
cache.write(key, value)
end
end
end
end
sprockets-3.7.2/lib/sprockets/cache/ 0000775 0000000 0000000 00000000000 13312220220 0017351 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/sprockets/cache/file_store.rb 0000664 0000000 0000000 00000011612 13312220220 0022032 0 ustar 00root root 0000000 0000000 require 'fileutils'
require 'logger'
require 'sprockets/encoding_utils'
require 'sprockets/path_utils'
require 'zlib'
module Sprockets
class Cache
# Public: A file system cache store that automatically cleans up old keys.
#
# Assign the instance to the Environment#cache.
#
# environment.cache = Sprockets::Cache::FileStore.new("/tmp")
#
# See Also
#
# ActiveSupport::Cache::FileStore
#
class FileStore
# Internal: Default key limit for store.
DEFAULT_MAX_SIZE = 25 * 1024 * 1024
# Internal: Default standard error fatal logger.
#
# Returns a Logger.
def self.default_logger
logger = Logger.new($stderr)
logger.level = Logger::FATAL
logger
end
# Public: Initialize the cache store.
#
# root - A String path to a directory to persist cached values to.
# max_size - A Integer of the maximum number of keys the store will hold.
# (default: 1000).
def initialize(root, max_size = DEFAULT_MAX_SIZE, logger = self.class.default_logger)
@root = root
@max_size = max_size
@gc_size = max_size * 0.75
@logger = logger
end
# Public: Retrieve value from cache.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
#
# Returns Object or nil or the value is not set.
def get(key)
path = File.join(@root, "#{key}.cache")
value = safe_open(path) do |f|
begin
EncodingUtils.unmarshaled_deflated(f.read, Zlib::MAX_WBITS)
rescue Exception => e
@logger.error do
"#{self.class}[#{path}] could not be unmarshaled: " +
"#{e.class}: #{e.message}"
end
nil
end
end
if value
FileUtils.touch(path)
value
end
end
# Public: Set a key and value in the cache.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
# value - Object value.
#
# Returns Object value.
def set(key, value)
path = File.join(@root, "#{key}.cache")
# Ensure directory exists
FileUtils.mkdir_p File.dirname(path)
# Check if cache exists before writing
exists = File.exist?(path)
# Serialize value
marshaled = Marshal.dump(value)
# Compress if larger than 4KB
if marshaled.bytesize > 4 * 1024
deflater = Zlib::Deflate.new(
Zlib::BEST_COMPRESSION,
Zlib::MAX_WBITS,
Zlib::MAX_MEM_LEVEL,
Zlib::DEFAULT_STRATEGY
)
deflater << marshaled
raw = deflater.finish
else
raw = marshaled
end
# Write data
PathUtils.atomic_write(path) do |f|
f.write(raw)
@size = size + f.size unless exists
end
# GC if necessary
gc! if size > @max_size
value
end
# Public: Pretty inspect
#
# Returns String.
def inspect
"#<#{self.class} size=#{size}/#{@max_size}>"
end
private
# Internal: Get all cache files along with stats.
#
# Returns an Array of [String filename, File::Stat] pairs sorted by
# mtime.
def find_caches
Dir.glob(File.join(@root, '**/*.cache')).reduce([]) { |stats, filename|
stat = safe_stat(filename)
# stat maybe nil if file was removed between the time we called
# dir.glob and the next stat
stats << [filename, stat] if stat
stats
}.sort_by { |_, stat| stat.mtime.to_i }
end
def size
@size ||= compute_size(find_caches)
end
def compute_size(caches)
caches.inject(0) { |sum, (_, stat)| sum + stat.size }
end
def safe_stat(fn)
File.stat(fn)
rescue Errno::ENOENT
nil
end
def safe_open(path, &block)
if File.exist?(path)
File.open(path, 'rb', &block)
end
rescue Errno::ENOENT
end
def gc!
start_time = Time.now
caches = find_caches
size = compute_size(caches)
delete_caches, keep_caches = caches.partition { |filename, stat|
deleted = size > @gc_size
size -= stat.size
deleted
}
return if delete_caches.empty?
FileUtils.remove(delete_caches.map(&:first), force: true)
@size = compute_size(keep_caches)
@logger.warn do
secs = Time.now.to_f - start_time.to_f
"#{self.class}[#{@root}] garbage collected " +
"#{delete_caches.size} files (#{(secs * 1000).to_i}ms)"
end
end
end
end
end
sprockets-3.7.2/lib/sprockets/cache/memory_store.rb 0000664 0000000 0000000 00000003163 13312220220 0022425 0 ustar 00root root 0000000 0000000 module Sprockets
class Cache
# Public: Basic in memory LRU cache.
#
# Assign the instance to the Environment#cache.
#
# environment.cache = Sprockets::Cache::MemoryStore.new(1000)
#
# See Also
#
# ActiveSupport::Cache::MemoryStore
#
class MemoryStore
# Internal: Default key limit for store.
DEFAULT_MAX_SIZE = 1000
# Public: Initialize the cache store.
#
# max_size - A Integer of the maximum number of keys the store will hold.
# (default: 1000).
def initialize(max_size = DEFAULT_MAX_SIZE)
@max_size = max_size
@cache = {}
end
# Public: Retrieve value from cache.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
#
# Returns Object or nil or the value is not set.
def get(key)
exists = true
value = @cache.delete(key) { exists = false }
if exists
@cache[key] = value
else
nil
end
end
# Public: Set a key and value in the cache.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
# value - Object value.
#
# Returns Object value.
def set(key, value)
@cache.delete(key)
@cache[key] = value
@cache.shift if @cache.size > @max_size
value
end
# Public: Pretty inspect
#
# Returns String.
def inspect
"#<#{self.class} size=#{@cache.size}/#{@max_size}>"
end
end
end
end
sprockets-3.7.2/lib/sprockets/cache/null_store.rb 0000664 0000000 0000000 00000002035 13312220220 0022064 0 ustar 00root root 0000000 0000000 module Sprockets
class Cache
# Public: A compatible cache store that doesn't store anything. Used by
# default when no Environment#cache is configured.
#
# Assign the instance to the Environment#cache.
#
# environment.cache = Sprockets::Cache::NullStore.new
#
# See Also
#
# ActiveSupport::Cache::NullStore
#
class NullStore
# Public: Simulate a cache miss.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
#
# Returns nil.
def get(key)
nil
end
# Public: Simulate setting a value in the cache.
#
# This API should not be used directly, but via the Cache wrapper API.
#
# key - String cache key.
# value - Object value.
#
# Returns Object value.
def set(key, value)
value
end
# Public: Pretty inspect
#
# Returns String.
def inspect
"#<#{self.class}>"
end
end
end
end
sprockets-3.7.2/lib/sprockets/cached_environment.rb 0000664 0000000 0000000 00000003703 13312220220 0022471 0 ustar 00root root 0000000 0000000 require 'sprockets/base'
module Sprockets
# `Cached` is a special cached version of `Environment`.
#
# The expection is that all of its file system methods are cached
# for the instances lifetime. This makes `Cached` much faster. This
# behavior is ideal in production environments where the file system
# is immutable.
#
# `Cached` should not be initialized directly. Instead use
# `Environment#cached`.
class CachedEnvironment < Base
def initialize(environment)
initialize_configuration(environment)
@cache = environment.cache
@stats = Hash.new { |h, k| h[k] = _stat(k) }
@entries = Hash.new { |h, k| h[k] = _entries(k) }
@uris = Hash.new { |h, k| h[k] = _load(k) }
@processor_cache_keys = Hash.new { |h, k| h[k] = _processor_cache_key(k) }
@resolved_dependencies = Hash.new { |h, k| h[k] = _resolve_dependency(k) }
end
# No-op return self as cached environment.
def cached
self
end
alias_method :index, :cached
# Internal: Cache Environment#entries
alias_method :_entries, :entries
def entries(path)
@entries[path]
end
# Internal: Cache Environment#stat
alias_method :_stat, :stat
def stat(path)
@stats[path]
end
# Internal: Cache Environment#load
alias_method :_load, :load
def load(uri)
@uris[uri]
end
# Internal: Cache Environment#processor_cache_key
alias_method :_processor_cache_key, :processor_cache_key
def processor_cache_key(str)
@processor_cache_keys[str]
end
# Internal: Cache Environment#resolve_dependency
alias_method :_resolve_dependency, :resolve_dependency
def resolve_dependency(str)
@resolved_dependencies[str]
end
private
# Cache is immutable, any methods that try to change the runtime config
# should bomb.
def config=(config)
raise RuntimeError, "can't modify immutable cached environment"
end
end
end
sprockets-3.7.2/lib/sprockets/closure_compressor.rb 0000664 0000000 0000000 00000002234 13312220220 0022564 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
require 'sprockets/digest_utils'
module Sprockets
# Public: Closure Compiler minifier.
#
# To accept the default options
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::ClosureCompressor
#
# Or to pass options to the Closure::Compiler class.
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::ClosureCompressor.new({ ... })
#
class ClosureCompressor
VERSION = '1'
# Public: Return singleton instance with default options.
#
# Returns ClosureCompressor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
def initialize(options = {})
@options = options
@cache_key = "#{self.class.name}:#{Autoload::Closure::VERSION}:#{Autoload::Closure::COMPILER_VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
end
def call(input)
@compiler ||= Autoload::Closure::Compiler.new(@options)
@compiler.compile(input[:data])
end
end
end
sprockets-3.7.2/lib/sprockets/coffee_script_processor.rb 0000664 0000000 0000000 00000001145 13312220220 0023546 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
module Sprockets
# Processor engine class for the CoffeeScript compiler.
# Depends on the `coffee-script` and `coffee-script-source` gems.
#
# For more infomation see:
#
# https://github.com/josh/ruby-coffee-script
#
module CoffeeScriptProcessor
VERSION = '1'
def self.cache_key
@cache_key ||= "#{name}:#{Autoload::CoffeeScript::Source.version}:#{VERSION}".freeze
end
def self.call(input)
data = input[:data]
input[:cache].fetch([self.cache_key, data]) do
Autoload::CoffeeScript.compile(data)
end
end
end
end
sprockets-3.7.2/lib/sprockets/coffee_script_template.rb 0000664 0000000 0000000 00000000633 13312220220 0023343 0 ustar 00root root 0000000 0000000 require 'sprockets/coffee_script_processor'
module Sprockets
# Deprecated
module CoffeeScriptTemplate
VERSION = CoffeeScriptProcessor::VERSION
def self.cache_key
CoffeeScriptProcessor.cache_key
end
def self.call(*args)
Deprecation.new.warn "CoffeeScriptTemplate is deprecated please use CoffeeScriptProcessor instead"
CoffeeScriptProcessor.call(*args)
end
end
end
sprockets-3.7.2/lib/sprockets/compressing.rb 0000664 0000000 0000000 00000005375 13312220220 0021176 0 ustar 00root root 0000000 0000000 require 'sprockets/utils'
module Sprockets
# `Compressing` is an internal mixin whose public methods are exposed on
# the `Environment` and `CachedEnvironment` classes.
module Compressing
include Utils
def compressors
config[:compressors]
end
def register_compressor(mime_type, sym, klass)
self.config = hash_reassoc(config, :compressors, mime_type) do |compressors|
compressors[sym] = klass
compressors
end
end
# Return CSS compressor or nil if none is set
def css_compressor
if defined? @css_compressor
@css_compressor
end
end
# Assign a compressor to run on `text/css` assets.
#
# The compressor object must respond to `compress`.
def css_compressor=(compressor)
unregister_bundle_processor 'text/css', @css_compressor if defined? @css_compressor
@css_compressor = nil
return unless compressor
if compressor.is_a?(Symbol)
@css_compressor = klass = config[:compressors]['text/css'][compressor] || raise(Error, "unknown compressor: #{compressor}")
elsif compressor.respond_to?(:compress)
klass = LegacyProcProcessor.new(:css_compressor, proc { |context, data| compressor.compress(data) })
@css_compressor = :css_compressor
else
@css_compressor = klass = compressor
end
register_bundle_processor 'text/css', klass
end
# Return JS compressor or nil if none is set
def js_compressor
if defined? @js_compressor
@js_compressor
end
end
# Assign a compressor to run on `application/javascript` assets.
#
# The compressor object must respond to `compress`.
def js_compressor=(compressor)
unregister_bundle_processor 'application/javascript', @js_compressor if defined? @js_compressor
@js_compressor = nil
return unless compressor
if compressor.is_a?(Symbol)
@js_compressor = klass = config[:compressors]['application/javascript'][compressor] || raise(Error, "unknown compressor: #{compressor}")
elsif compressor.respond_to?(:compress)
klass = LegacyProcProcessor.new(:js_compressor, proc { |context, data| compressor.compress(data) })
@js_compressor = :js_compressor
else
@js_compressor = klass = compressor
end
register_bundle_processor 'application/javascript', klass
end
# Public: Checks if Gzip is enabled.
def gzip?
config[:gzip_enabled]
end
# Public: Checks if Gzip is disabled.
def skip_gzip?
!gzip?
end
# Public: Enable or disable the creation of Gzip files.
#
# Defaults to true.
#
# environment.gzip = false
#
def gzip=(gzip)
self.config = config.merge(gzip_enabled: gzip).freeze
end
end
end
sprockets-3.7.2/lib/sprockets/configuration.rb 0000664 0000000 0000000 00000004400 13312220220 0021500 0 ustar 00root root 0000000 0000000 require 'sprockets/compressing'
require 'sprockets/dependencies'
require 'sprockets/engines'
require 'sprockets/mime'
require 'sprockets/paths'
require 'sprockets/processing'
require 'sprockets/transformers'
require 'sprockets/utils'
module Sprockets
module Configuration
include Paths, Mime, Engines, Transformers, Processing, Compressing, Dependencies, Utils
def initialize_configuration(parent)
@config = parent.config
@computed_config = parent.computed_config
@logger = parent.logger
@context_class = Class.new(parent.context_class)
end
attr_reader :config
attr_accessor :computed_config
def config=(config)
raise TypeError, "can't assign mutable config" unless config.frozen?
@config = config
end
# Get and set `Logger` instance.
attr_accessor :logger
# The `Environment#version` is a custom value used for manually
# expiring all asset caches.
#
# Sprockets is able to track most file and directory changes and
# will take care of expiring the cache for you. However, its
# impossible to know when any custom helpers change that you mix
# into the `Context`.
#
# It would be wise to increment this value anytime you make a
# configuration change to the `Environment` object.
def version
config[:version]
end
# Assign an environment version.
#
# environment.version = '2.0'
#
def version=(version)
self.config = hash_reassoc(config, :version) { version.dup }
end
# Public: Returns a `Digest` implementation class.
#
# Defaults to `Digest::SHA256`.
def digest_class
config[:digest_class]
end
# Deprecated: Assign a `Digest` implementation class. This maybe any Ruby
# `Digest::` implementation such as `Digest::SHA256` or
# `Digest::MD5`.
#
# environment.digest_class = Digest::MD5
#
def digest_class=(klass)
self.config = config.merge(digest_class: klass).freeze
end
# Deprecated: Get `Context` class.
#
# This class maybe mutated and mixed in with custom helpers.
#
# environment.context_class.instance_eval do
# include MyHelpers
# def asset_url; end
# end
#
attr_reader :context_class
end
end
sprockets-3.7.2/lib/sprockets/context.rb 0000664 0000000 0000000 00000015520 13312220220 0020322 0 ustar 00root root 0000000 0000000 require 'pathname'
require 'rack/utils'
require 'set'
require 'sprockets/errors'
module Sprockets
# Deprecated: `Context` provides helper methods to all processors.
# They are typically accessed by ERB templates. You can mix in custom helpers
# by injecting them into `Environment#context_class`. Do not mix them into
# `Context` directly.
#
# environment.context_class.class_eval do
# include MyHelper
# def asset_url; end
# end
#
# <%= asset_url "foo.png" %>
#
# The `Context` also collects dependencies declared by
# assets. See `DirectiveProcessor` for an example of this.
class Context
attr_reader :environment, :filename, :pathname
# Deprecated
attr_accessor :__LINE__
def initialize(input)
@environment = input[:environment]
@metadata = input[:metadata]
@load_path = input[:load_path]
@logical_path = input[:name]
@filename = input[:filename]
@dirname = File.dirname(@filename)
@pathname = Pathname.new(@filename)
@content_type = input[:content_type]
@required = Set.new(@metadata[:required])
@stubbed = Set.new(@metadata[:stubbed])
@links = Set.new(@metadata[:links])
@dependencies = Set.new(input[:metadata][:dependencies])
end
def metadata
{ required: @required,
stubbed: @stubbed,
links: @links,
dependencies: @dependencies }
end
# Returns the environment path that contains the file.
#
# If `app/javascripts` and `app/stylesheets` are in your path, and
# current file is `app/javascripts/foo/bar.js`, `load_path` would
# return `app/javascripts`.
attr_reader :load_path
alias_method :root_path, :load_path
# Returns logical path without any file extensions.
#
# 'app/javascripts/application.js'
# # => 'application'
#
attr_reader :logical_path
# Returns content type of file
#
# 'application/javascript'
# 'text/css'
#
attr_reader :content_type
# Public: Given a logical path, `resolve` will find and return an Asset URI.
# Relative paths will also be resolved. An accept type maybe given to
# restrict the search.
#
# resolve("foo.js")
# # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
#
# resolve("./bar.js")
# # => "file:///path/to/app/javascripts/bar.js?type=application/javascript"
#
# path - String logical or absolute path
# options
# accept - String content accept type
#
# Returns an Asset URI String.
def resolve(path, options = {})
uri, deps = environment.resolve!(path, options.merge(base_path: @dirname))
@dependencies.merge(deps)
uri
end
# Public: Load Asset by AssetURI and track it as a dependency.
#
# uri - AssetURI
#
# Returns Asset.
def load(uri)
asset = environment.load(uri)
@dependencies.merge(asset.metadata[:dependencies])
asset
end
# `depend_on` allows you to state a dependency on a file without
# including it.
#
# This is used for caching purposes. Any changes made to
# the dependency file with invalidate the cache of the
# source file.
def depend_on(path)
path = path.to_s if path.is_a?(Pathname)
if environment.absolute_path?(path) && environment.stat(path)
@dependencies << environment.build_file_digest_uri(path)
else
resolve(path, compat: false)
end
nil
end
# `depend_on_asset` allows you to state an asset dependency
# without including it.
#
# This is used for caching purposes. Any changes that would
# invalidate the dependency asset will invalidate the source
# file. Unlike `depend_on`, this will include recursively include
# the target asset's dependencies.
def depend_on_asset(path)
load(resolve(path, compat: false))
end
# `require_asset` declares `path` as a dependency of the file. The
# dependency will be inserted before the file and will only be
# included once.
#
# If ERB processing is enabled, you can use it to dynamically
# require assets.
#
# <%= require_asset "#{framework}.js" %>
#
def require_asset(path)
@required << resolve(path, accept: @content_type, pipeline: :self, compat: false)
nil
end
# `stub_asset` blacklists `path` from being included in the bundle.
# `path` must be an asset which may or may not already be included
# in the bundle.
def stub_asset(path)
@stubbed << resolve(path, accept: @content_type, pipeline: :self, compat: false)
nil
end
# `link_asset` declares an external dependency on an asset without directly
# including it. The target asset is returned from this function making it
# easy to construct a link to it.
#
# Returns an Asset or nil.
def link_asset(path)
asset = depend_on_asset(path)
@links << asset.uri
asset
end
# Returns a Base64-encoded `data:` URI with the contents of the
# asset at the specified path, and marks that path as a dependency
# of the current file.
#
# Use `asset_data_uri` from ERB with CSS or JavaScript assets:
#
# #logo { background: url(<%= asset_data_uri 'logo.png' %>) }
#
# $('
').attr('src', '<%= asset_data_uri 'avatar.jpg' %>')
#
def asset_data_uri(path)
asset = depend_on_asset(path)
data = EncodingUtils.base64(asset.source)
"data:#{asset.content_type};base64,#{Rack::Utils.escape(data)}"
end
# Expands logical path to full url to asset.
#
# NOTE: This helper is currently not implemented and should be
# customized by the application. Though, in the future, some
# basics implemention may be provided with different methods that
# are required to be overridden.
def asset_path(path, options = {})
message = <<-EOS
Custom asset_path helper is not implemented
Extend your environment context with a custom method.
environment.context_class.class_eval do
def asset_path(path, options = {})
end
end
EOS
raise NotImplementedError, message
end
# Expand logical image asset path.
def image_path(path)
asset_path(path, type: :image)
end
# Expand logical video asset path.
def video_path(path)
asset_path(path, type: :video)
end
# Expand logical audio asset path.
def audio_path(path)
asset_path(path, type: :audio)
end
# Expand logical font asset path.
def font_path(path)
asset_path(path, type: :font)
end
# Expand logical javascript asset path.
def javascript_path(path)
asset_path(path, type: :javascript)
end
# Expand logical stylesheet asset path.
def stylesheet_path(path)
asset_path(path, type: :stylesheet)
end
end
end
sprockets-3.7.2/lib/sprockets/dependencies.rb 0000664 0000000 0000000 00000003573 13312220220 0021271 0 ustar 00root root 0000000 0000000 require 'sprockets/digest_utils'
require 'sprockets/path_digest_utils'
require 'sprockets/uri_utils'
module Sprockets
# `Dependencies` is an internal mixin whose public methods are exposed on the
# `Environment` and `CachedEnvironment` classes.
module Dependencies
include DigestUtils, PathDigestUtils, URIUtils
# Public: Mapping dependency schemes to resolver functions.
#
# key - String scheme
# value - Proc.call(Environment, String)
#
# Returns Hash.
def dependency_resolvers
config[:dependency_resolvers]
end
# Public: Default set of dependency URIs for assets.
#
# Returns Set of String URIs.
def dependencies
config[:dependencies]
end
# Public: Register new dependency URI resolver.
#
# scheme - String scheme
# block -
# environment - Environment
# uri - String dependency URI
#
# Returns nothing.
def register_dependency_resolver(scheme, &block)
self.config = hash_reassoc(config, :dependency_resolvers) do |hash|
hash.merge(scheme => block)
end
end
# Public: Add environmental dependency inheirted by all assets.
#
# uri - String dependency URI
#
# Returns nothing.
def add_dependency(uri)
self.config = hash_reassoc(config, :dependencies) do |set|
set + Set.new([uri])
end
end
alias_method :depend_on, :add_dependency
# Internal: Resolve dependency URIs.
#
# Returns resolved Object.
def resolve_dependency(str)
# Optimize for the most common scheme to
# save 22k allocations on an average Spree app.
scheme = if str.start_with?('file-digest:'.freeze)
'file-digest'.freeze
else
str[/([^:]+)/, 1]
end
if resolver = config[:dependency_resolvers][scheme]
resolver.call(self, str)
else
nil
end
end
end
end
sprockets-3.7.2/lib/sprockets/deprecation.rb 0000664 0000000 0000000 00000004731 13312220220 0021135 0 ustar 00root root 0000000 0000000 module Sprockets
class Deprecation
THREAD_LOCAL__SILENCE_KEY = "_sprockets_deprecation_silence".freeze
DEFAULT_BEHAVIORS = {
raise: ->(message, callstack) {
e = DeprecationException.new(message)
e.set_backtrace(callstack.map(&:to_s))
raise e
},
stderr: ->(message, callstack) {
$stderr.puts(message)
},
}
attr_reader :callstack
def self.silence(&block)
Thread.current[THREAD_LOCAL__SILENCE_KEY] = true
block.call
ensure
Thread.current[THREAD_LOCAL__SILENCE_KEY] = false
end
def initialize(callstack = nil)
@callstack = callstack || caller(2)
end
def warn(message)
return if Thread.current[THREAD_LOCAL__SILENCE_KEY]
deprecation_message(message).tap do |m|
behavior.each { |b| b.call(m, callstack) }
end
end
private
def behavior
@behavior ||= [DEFAULT_BEHAVIORS[:stderr]]
end
def behavior=(behavior)
@behavior = Array(behavior).map { |b| DEFAULT_BEHAVIORS[b] || b }
end
def deprecation_message(message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
"DEPRECATION WARNING: #{message} #{ deprecation_caller_message }"
end
def deprecation_caller_message
file, line, method = extract_callstack
if file
if line && method
"(called from #{method} at #{file}:#{line})"
else
"(called from #{file}:#{line})"
end
end
end
SPROCKETS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
def ignored_callstack(path)
path.start_with?(SPROCKETS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir'])
end
def extract_callstack
return _extract_callstack if callstack.first.is_a? String
offending_line = callstack.find { |frame|
frame.absolute_path && !ignored_callstack(frame.absolute_path)
} || callstack.first
[offending_line.path, offending_line.lineno, offending_line.label]
end
def _extract_callstack
offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
if offending_line
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
md.captures
else
offending_line
end
end
end
end
private_constant :Deprecation
end
sprockets-3.7.2/lib/sprockets/digest_utils.rb 0000664 0000000 0000000 00000011635 13312220220 0021340 0 ustar 00root root 0000000 0000000 require 'digest/md5'
require 'digest/sha1'
require 'digest/sha2'
require 'set'
module Sprockets
# Internal: Hash functions and digest related utilities. Mixed into
# Environment.
module DigestUtils
extend self
# Internal: Default digest class.
#
# Returns a Digest::Base subclass.
def digest_class
Digest::SHA256
end
# Internal: Maps digest bytesize to the digest class.
DIGEST_SIZES = {
16 => Digest::MD5,
20 => Digest::SHA1,
32 => Digest::SHA256,
48 => Digest::SHA384,
64 => Digest::SHA512
}
# Internal: Detect digest class hash algorithm for digest bytes.
#
# While not elegant, all the supported digests have a unique bytesize.
#
# Returns Digest::Base or nil.
def detect_digest_class(bytes)
DIGEST_SIZES[bytes.bytesize]
end
ADD_VALUE_TO_DIGEST = {
String => ->(val, digest) { digest << val },
FalseClass => ->(val, digest) { digest << 'FalseClass'.freeze },
TrueClass => ->(val, digest) { digest << 'TrueClass'.freeze },
NilClass => ->(val, digest) { digest << 'NilClass'.freeze },
Symbol => ->(val, digest) {
digest << 'Symbol'.freeze
digest << val.to_s
},
Integer => ->(val, digest) {
digest << 'Integer'.freeze
digest << val.to_s
},
Array => ->(val, digest) {
digest << 'Array'.freeze
val.each do |element|
ADD_VALUE_TO_DIGEST[element.class].call(element, digest)
end
},
Hash => ->(val, digest) {
digest << 'Hash'.freeze
val.sort.each do |array|
ADD_VALUE_TO_DIGEST[Array].call(array, digest)
end
},
Set => ->(val, digest) {
digest << 'Set'.freeze
ADD_VALUE_TO_DIGEST[Array].call(val.to_a, digest)
},
Encoding => ->(val, digest) {
digest << 'Encoding'.freeze
digest << val.name
},
}
if 0.class != Integer # Ruby < 2.4
ADD_VALUE_TO_DIGEST[Fixnum] = ->(val, digest) {
digest << 'Integer'.freeze
digest << val.to_s
}
ADD_VALUE_TO_DIGEST[Bignum] = ->(val, digest) {
digest << 'Integer'.freeze
digest << val.to_s
}
end
ADD_VALUE_TO_DIGEST.default_proc = ->(_, val) {
raise TypeError, "couldn't digest #{ val }"
}
private_constant :ADD_VALUE_TO_DIGEST
# Internal: Generate a hexdigest for a nested JSON serializable object.
#
# This is used for generating cache keys, so its pretty important its
# wicked fast. Microbenchmarks away!
#
# obj - A JSON serializable object.
#
# Returns a String digest of the object.
def digest(obj)
digest = digest_class.new
ADD_VALUE_TO_DIGEST[obj.class].call(obj, digest)
digest.digest
end
# Internal: Pack a binary digest to a hex encoded string.
#
# bin - String bytes
#
# Returns hex String.
def pack_hexdigest(bin)
bin.unpack('H*').first
end
# Internal: Unpack a hex encoded digest string into binary bytes.
#
# hex - String hex
#
# Returns binary String.
def unpack_hexdigest(hex)
[hex].pack('H*')
end
# Internal: Pack a binary digest to a base64 encoded string.
#
# bin - String bytes
#
# Returns base64 String.
def pack_base64digest(bin)
[bin].pack('m0')
end
# Internal: Pack a binary digest to a urlsafe base64 encoded string.
#
# bin - String bytes
#
# Returns urlsafe base64 String.
def pack_urlsafe_base64digest(bin)
str = pack_base64digest(bin)
str.tr!('+/'.freeze, '-_'.freeze)
str.tr!('='.freeze, ''.freeze)
str
end
# Internal: Maps digest class to the CSP hash algorithm name.
HASH_ALGORITHMS = {
Digest::SHA256 => 'sha256'.freeze,
Digest::SHA384 => 'sha384'.freeze,
Digest::SHA512 => 'sha512'.freeze
}
# Public: Generate hash for use in the `integrity` attribute of an asset tag
# as per the subresource integrity specification.
#
# digest - The String byte digest of the asset content.
#
# Returns a String or nil if hash algorithm is incompatible.
def integrity_uri(digest)
case digest
when Digest::Base
digest_class = digest.class
digest = digest.digest
when String
digest_class = DIGEST_SIZES[digest.bytesize]
else
raise TypeError, "unknown digest: #{digest.inspect}"
end
if hash_name = HASH_ALGORITHMS[digest_class]
"#{hash_name}-#{pack_base64digest(digest)}"
end
end
# Public: Generate hash for use in the `integrity` attribute of an asset tag
# as per the subresource integrity specification.
#
# digest - The String hexbyte digest of the asset content.
#
# Returns a String or nil if hash algorithm is incompatible.
def hexdigest_integrity_uri(hexdigest)
integrity_uri(unpack_hexdigest(hexdigest))
end
end
end
sprockets-3.7.2/lib/sprockets/directive_processor.rb 0000664 0000000 0000000 00000031406 13312220220 0022714 0 ustar 00root root 0000000 0000000 require 'set'
require 'shellwords'
module Sprockets
# The `DirectiveProcessor` is responsible for parsing and evaluating
# directive comments in a source file.
#
# A directive comment starts with a comment prefix, followed by an "=",
# then the directive name, then any arguments.
#
# // JavaScript
# //= require "foo"
#
# # CoffeeScript
# #= require "bar"
#
# /* CSS
# *= require "baz"
# */
#
# This makes it possible to disable or modify the processor to do whatever
# you'd like. You could add your own custom directives or invent your own
# directive syntax.
#
# `Environment#processors` includes `DirectiveProcessor` by default.
#
# To remove the processor entirely:
#
# env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
# env.unregister_processor('application/javascript', Sprockets::DirectiveProcessor)
#
# Then inject your own preprocessor:
#
# env.register_processor('text/css', MyProcessor)
#
class DirectiveProcessor
VERSION = '1'
# Directives are denoted by a `=` followed by the name, then
# argument list.
#
# A few different styles are allowed:
#
# // =require foo
# //= require foo
# //= require "foo"
#
DIRECTIVE_PATTERN = /
^ \W* = \s* (\w+.*?) (\*\/)? $
/x
def self.instance
@instance ||= new(
# Deprecated: Default to C and Ruby comment styles
comments: ["//", ["/*", "*/"]] + ["#", ["###", "###"]]
)
end
def self.call(input)
instance.call(input)
end
def initialize(options = {})
@header_pattern = compile_header_pattern(Array(options[:comments]))
end
def call(input)
dup._call(input)
end
def _call(input)
@environment = input[:environment]
@uri = input[:uri]
@filename = input[:filename]
@dirname = File.dirname(@filename)
@content_type = input[:content_type]
@required = Set.new(input[:metadata][:required])
@stubbed = Set.new(input[:metadata][:stubbed])
@links = Set.new(input[:metadata][:links])
@dependencies = Set.new(input[:metadata][:dependencies])
data, directives = process_source(input[:data])
process_directives(directives)
{ data: data,
required: @required,
stubbed: @stubbed,
links: @links,
dependencies: @dependencies }
end
protected
# Directives will only be picked up if they are in the header
# of the source file. C style (/* */), JavaScript (//), and
# Ruby (#) comments are supported.
#
# Directives in comments after the first non-whitespace line
# of code will not be processed.
def compile_header_pattern(comments)
re = comments.map { |c|
case c
when String
"(?:#{Regexp.escape(c)}.*\\n?)+"
when Array
"(?:#{Regexp.escape(c[0])}(?m:.*?)#{Regexp.escape(c[1])})"
else
raise TypeError, "unknown comment type: #{c.class}"
end
}.join("|")
Regexp.compile("\\A(?:(?m:\\s*)(?:#{re}))+")
end
def process_source(source)
header = source[@header_pattern, 0] || ""
body = $' || source
header, directives = extract_directives(header)
data = ""
data.force_encoding(body.encoding)
data << header << "\n" unless header.empty?
data << body
# Ensure body ends in a new line
data << "\n" if data.length > 0 && data[-1] != "\n"
return data, directives
end
# Returns an Array of directive structures. Each structure
# is an Array with the line number as the first element, the
# directive name as the second element, followed by any
# arguments.
#
# [[1, "require", "foo"], [2, "require", "bar"]]
#
def extract_directives(header)
processed_header = ""
directives = []
header.lines.each_with_index do |line, index|
if directive = line[DIRECTIVE_PATTERN, 1]
name, *args = Shellwords.shellwords(directive)
if respond_to?("process_#{name}_directive", true)
directives << [index + 1, name, *args]
# Replace directive line with a clean break
line = "\n"
end
end
processed_header << line
end
return processed_header.chomp, directives
end
# Gathers comment directives in the source and processes them.
# Any directive method matching `process_*_directive` will
# automatically be available. This makes it easy to extend the
# processor.
#
# To implement a custom directive called `require_glob`, subclass
# `Sprockets::DirectiveProcessor`, then add a method called
# `process_require_glob_directive`.
#
# class DirectiveProcessor < Sprockets::DirectiveProcessor
# def process_require_glob_directive
# Dir["#{dirname}/#{glob}"].sort.each do |filename|
# require(filename)
# end
# end
# end
#
# Replace the current processor on the environment with your own:
#
# env.unregister_processor('text/css', Sprockets::DirectiveProcessor)
# env.register_processor('text/css', DirectiveProcessor)
#
def process_directives(directives)
directives.each do |line_number, name, *args|
begin
send("process_#{name}_directive", *args)
rescue Exception => e
e.set_backtrace(["#{@filename}:#{line_number}"] + e.backtrace)
raise e
end
end
end
# The `require` directive functions similar to Ruby's own `require`.
# It provides a way to declare a dependency on a file in your path
# and ensures its only loaded once before the source file.
#
# `require` works with files in the environment path:
#
# //= require "foo.js"
#
# Extensions are optional. If your source file is ".js", it
# assumes you are requiring another ".js".
#
# //= require "foo"
#
# Relative paths work too. Use a leading `./` to denote a relative
# path:
#
# //= require "./bar"
#
def process_require_directive(path)
@required << resolve(path, accept: @content_type, pipeline: :self)
end
# `require_self` causes the body of the current file to be inserted
# before any subsequent `require` directives. Useful in CSS files, where
# it's common for the index file to contain global styles that need to
# be defined before other dependencies are loaded.
#
# /*= require "reset"
# *= require_self
# *= require_tree .
# */
#
def process_require_self_directive
if @required.include?(@uri)
raise ArgumentError, "require_self can only be called once per source file"
end
@required << @uri
end
# `require_directory` requires all the files inside a single
# directory. It's similar to `path/*` since it does not follow
# nested directories.
#
# //= require_directory "./javascripts"
#
def process_require_directory_directive(path = ".")
path = expand_relative_dirname(:require_directory, path)
require_paths(*@environment.stat_directory_with_dependencies(path))
end
# `require_tree` requires all the nested files in a directory.
# Its glob equivalent is `path/**/*`.
#
# //= require_tree "./public"
#
def process_require_tree_directive(path = ".")
path = expand_relative_dirname(:require_tree, path)
require_paths(*@environment.stat_sorted_tree_with_dependencies(path))
end
# Allows you to state a dependency on a file without
# including it.
#
# This is used for caching purposes. Any changes made to
# the dependency file will invalidate the cache of the
# source file.
#
# This is useful if you are using ERB and File.read to pull
# in contents from another file.
#
# //= depend_on "foo.png"
#
def process_depend_on_directive(path)
resolve(path)
end
# Allows you to state a dependency on an asset without including
# it.
#
# This is used for caching purposes. Any changes that would
# invalid the asset dependency will invalidate the cache our the
# source file.
#
# Unlike `depend_on`, the path must be a requirable asset.
#
# //= depend_on_asset "bar.js"
#
def process_depend_on_asset_directive(path)
load(resolve(path))
end
# Allows dependency to be excluded from the asset bundle.
#
# The `path` must be a valid asset and may or may not already
# be part of the bundle. Once stubbed, it is blacklisted and
# can't be brought back by any other `require`.
#
# //= stub "jquery"
#
def process_stub_directive(path)
@stubbed << resolve(path, accept: @content_type, pipeline: :self)
end
# Declares a linked dependency on the target asset.
#
# The `path` must be a valid asset and should not already be part of the
# bundle. Any linked assets will automatically be compiled along with the
# current.
#
# /*= link "logo.png" */
#
def process_link_directive(path)
@links << load(resolve(path)).uri
end
# `link_directory` links all the files inside a single
# directory. It's similar to `path/*` since it does not follow
# nested directories.
#
# //= link_directory "./fonts"
#
# Use caution when linking against JS or CSS assets. Include an explicit
# extension or content type in these cases
#
# //= link_directory "./scripts" .js
#
def process_link_directory_directive(path = ".", accept = nil)
path = expand_relative_dirname(:link_directory, path)
accept = expand_accept_shorthand(accept)
link_paths(*@environment.stat_directory_with_dependencies(path), accept)
end
# `link_tree` links all the nested files in a directory.
# Its glob equivalent is `path/**/*`.
#
# //= link_tree "./images"
#
# Use caution when linking against JS or CSS assets. Include an explicit
# extension or content type in these cases
#
# //= link_tree "./styles" .css
#
def process_link_tree_directive(path = ".", accept = nil)
path = expand_relative_dirname(:link_tree, path)
accept = expand_accept_shorthand(accept)
link_paths(*@environment.stat_sorted_tree_with_dependencies(path), accept)
end
private
def expand_accept_shorthand(accept)
if accept.nil?
nil
elsif accept.include?("/")
accept
elsif accept.start_with?(".")
@environment.mime_exts[accept]
else
@environment.mime_exts[".#{accept}"]
end
end
def require_paths(paths, deps)
resolve_paths(paths, deps, accept: @content_type, pipeline: :self) do |uri|
@required << uri
end
end
def link_paths(paths, deps, accept)
resolve_paths(paths, deps, accept: accept) do |uri|
@links << load(uri).uri
end
end
def resolve_paths(paths, deps, options = {})
@dependencies.merge(deps)
paths.each do |subpath, stat|
next if subpath == @filename || stat.directory?
uri, deps = @environment.resolve(subpath, options.merge(compat: false))
@dependencies.merge(deps)
yield uri if uri
end
end
def expand_relative_dirname(directive, path)
if @environment.relative_path?(path)
path = File.expand_path(path, @dirname)
stat = @environment.stat(path)
if stat && stat.directory?
path
else
raise ArgumentError, "#{directive} argument must be a directory"
end
else
# The path must be relative and start with a `./`.
raise ArgumentError, "#{directive} argument must be a relative path"
end
end
def load(uri)
asset = @environment.load(uri)
@dependencies.merge(asset.metadata[:dependencies])
asset
end
def resolve(path, options = {})
# Prevent absolute paths in directives
if @environment.absolute_path?(path)
raise FileOutsidePaths, "can't require absolute file: #{path}"
end
uri, deps = @environment.resolve!(path, options.merge(base_path: @dirname))
@dependencies.merge(deps)
uri
end
end
end
sprockets-3.7.2/lib/sprockets/eco_processor.rb 0000664 0000000 0000000 00000001404 13312220220 0021477 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
module Sprockets
# Processor engine class for the Eco compiler. Depends on the `eco` gem.
#
# For more infomation see:
#
# https://github.com/sstephenson/ruby-eco
# https://github.com/sstephenson/eco
#
module EcoProcessor
VERSION = '1'
def self.cache_key
@cache_key ||= "#{name}:#{Autoload::Eco::Source::VERSION}:#{VERSION}".freeze
end
# Compile template data with Eco compiler.
#
# Returns a JS function definition String. The result should be
# assigned to a JS variable.
#
# # => "function(...) {...}"
#
def self.call(input)
data = input[:data]
input[:cache].fetch([cache_key, data]) do
Autoload::Eco.compile(data)
end
end
end
end
sprockets-3.7.2/lib/sprockets/eco_template.rb 0000664 0000000 0000000 00000000533 13312220220 0021275 0 ustar 00root root 0000000 0000000 require 'sprockets/eco_processor'
module Sprockets
# Deprecated
module EcoTemplate
VERSION = EcoProcessor::VERSION
def self.cache_key
EcoProcessor.cache_key
end
def self.call(*args)
Deprecation.new.warn "EcoTemplate is deprecated please use EcoProcessor instead"
EcoProcessor.call(*args)
end
end
end
sprockets-3.7.2/lib/sprockets/ejs_processor.rb 0000664 0000000 0000000 00000001270 13312220220 0021513 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
module Sprockets
# Processor engine class for the EJS compiler. Depends on the `ejs` gem.
#
# For more infomation see:
#
# https://github.com/sstephenson/ruby-ejs
#
module EjsProcessor
VERSION = '1'
def self.cache_key
@cache_key ||= "#{name}:#{VERSION}".freeze
end
# Compile template data with EJS compiler.
#
# Returns a JS function definition String. The result should be
# assigned to a JS variable.
#
# # => "function(obj){...}"
#
def self.call(input)
data = input[:data]
input[:cache].fetch([cache_key, data]) do
Autoload::EJS.compile(data)
end
end
end
end
sprockets-3.7.2/lib/sprockets/ejs_template.rb 0000664 0000000 0000000 00000000533 13312220220 0021310 0 ustar 00root root 0000000 0000000 require 'sprockets/ejs_processor'
module Sprockets
# Deprecated
module EjsTemplate
VERSION = EjsProcessor::VERSION
def self.cache_key
EjsProcessor.cache_key
end
def self.call(*args)
Deprecation.new.warn "EjsTemplate is deprecated please use EjsProcessor instead"
EjsProcessor.call(*args)
end
end
end
sprockets-3.7.2/lib/sprockets/encoding_utils.rb 0000664 0000000 0000000 00000014610 13312220220 0021643 0 ustar 00root root 0000000 0000000 require 'base64'
require 'stringio'
require 'zlib'
module Sprockets
# Internal: HTTP transport encoding and charset detecting related functions.
# Mixed into Environment.
module EncodingUtils
extend self
## Binary encodings ##
# Public: Use deflate to compress data.
#
# str - String data
#
# Returns a compressed String
def deflate(str)
deflater = Zlib::Deflate.new(
Zlib::BEST_COMPRESSION,
-Zlib::MAX_WBITS,
Zlib::MAX_MEM_LEVEL,
Zlib::DEFAULT_STRATEGY
)
deflater << str
deflater.finish
end
# Internal: Unmarshal optionally deflated data.
#
# Checks leading marshal header to see if the bytes are uncompressed
# otherwise inflate the data an unmarshal.
#
# str - Marshaled String
# window_bits - Integer deflate window size. See ZLib::Inflate.new()
#
# Returns unmarshaled Object or raises an Exception.
def unmarshaled_deflated(str, window_bits = -Zlib::MAX_WBITS)
major, minor = str[0], str[1]
if major && major.ord == Marshal::MAJOR_VERSION &&
minor && minor.ord <= Marshal::MINOR_VERSION
marshaled = str
else
begin
marshaled = Zlib::Inflate.new(window_bits).inflate(str)
rescue Zlib::DataError
marshaled = str
end
end
Marshal.load(marshaled)
end
# Public: Use gzip to compress data.
#
# str - String data
#
# Returns a compressed String
def gzip(str)
io = StringIO.new
gz = Zlib::GzipWriter.new(io, Zlib::BEST_COMPRESSION)
gz.mtime = 1
gz << str
gz.finish
io.string
end
# Public: Use base64 to encode data.
#
# str - String data
#
# Returns a encoded String
def base64(str)
Base64.strict_encode64(str)
end
## Charset encodings ##
# Internal: Shorthand aliases for detecter functions.
CHARSET_DETECT = {}
# Internal: Mapping unicode encodings to byte order markers.
BOM = {
Encoding::UTF_32LE => [0xFF, 0xFE, 0x00, 0x00],
Encoding::UTF_32BE => [0x00, 0x00, 0xFE, 0xFF],
Encoding::UTF_8 => [0xEF, 0xBB, 0xBF],
Encoding::UTF_16LE => [0xFF, 0xFE],
Encoding::UTF_16BE => [0xFE, 0xFF]
}
# Public: Basic string detecter.
#
# Attempts to parse any Unicode BOM otherwise falls back to the
# environment's external encoding.
#
# str - ASCII-8BIT encoded String
#
# Returns encoded String.
def detect(str)
str = detect_unicode_bom(str)
# Attempt Charlock detection
if str.encoding == Encoding::BINARY
charlock_detect(str)
end
# Fallback to environment's external encoding
if str.encoding == Encoding::BINARY
str.force_encoding(Encoding.default_external)
end
str
end
CHARSET_DETECT[:default] = method(:detect)
# Internal: Use Charlock Holmes to detect encoding.
#
# To enable this code path, require 'charlock_holmes'
#
# Returns encoded String.
def charlock_detect(str)
if defined? CharlockHolmes::EncodingDetector
if detected = CharlockHolmes::EncodingDetector.detect(str)
str.force_encoding(detected[:encoding]) if detected[:encoding]
end
end
str
end
# Public: Detect Unicode string.
#
# Attempts to parse Unicode BOM and falls back to UTF-8.
#
# str - ASCII-8BIT encoded String
#
# Returns encoded String.
def detect_unicode(str)
str = detect_unicode_bom(str)
# Fallback to UTF-8
if str.encoding == Encoding::BINARY
str.force_encoding(Encoding::UTF_8)
end
str
end
CHARSET_DETECT[:unicode] = method(:detect_unicode)
# Public: Detect and strip BOM from possible unicode string.
#
# str - ASCII-8BIT encoded String
#
# Returns UTF 8/16/32 encoded String without BOM or the original String if
# no BOM was present.
def detect_unicode_bom(str)
bom_bytes = str.byteslice(0, 4).bytes.to_a
BOM.each do |encoding, bytes|
if bom_bytes[0, bytes.size] == bytes
str = str.dup
str.force_encoding(Encoding::BINARY)
str.slice!(0, bytes.size)
str.force_encoding(encoding)
return str
end
end
return str
end
# Public: Detect and strip @charset from CSS style sheet.
#
# str - String.
#
# Returns a encoded String.
def detect_css(str)
str = detect_unicode_bom(str)
if name = scan_css_charset(str)
encoding = Encoding.find(name)
str = str.dup
str.force_encoding(encoding)
len = "@charset \"#{name}\";".encode(encoding).size
str.slice!(0, len)
str
end
# Fallback to UTF-8
if str.encoding == Encoding::BINARY
str.force_encoding(Encoding::UTF_8)
end
str
end
CHARSET_DETECT[:css] = method(:detect_css)
# Internal: @charset bytes
CHARSET_START = [0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22]
CHARSET_SIZE = CHARSET_START.size
# Internal: Scan binary CSS string for @charset encoding name.
#
# str - ASCII-8BIT encoded String
#
# Returns encoding String name or nil.
def scan_css_charset(str)
buf = []
i = 0
str.each_byte.each do |byte|
# Halt on line breaks
break if byte == 0x0A || byte == 0x0D
# Only ascii bytes
next unless 0x0 < byte && byte <= 0xFF
if i < CHARSET_SIZE
elsif i == CHARSET_SIZE
if buf == CHARSET_START
buf = []
else
break
end
elsif byte == 0x22
return buf.pack('C*')
end
buf << byte
i += 1
end
nil
end
# Public: Detect charset from HTML document.
#
# Attempts to parse any Unicode BOM otherwise attempt Charlock detection
# and finally falls back to the environment's external encoding.
#
# str - String.
#
# Returns a encoded String.
def detect_html(str)
str = detect_unicode_bom(str)
# Attempt Charlock detection
if str.encoding == Encoding::BINARY
charlock_detect(str)
end
# Fallback to environment's external encoding
if str.encoding == Encoding::BINARY
str.force_encoding(Encoding.default_external)
end
str
end
CHARSET_DETECT[:html] = method(:detect_html)
end
end
sprockets-3.7.2/lib/sprockets/engines.rb 0000664 0000000 0000000 00000006047 13312220220 0020272 0 ustar 00root root 0000000 0000000 require 'sprockets/legacy_tilt_processor'
require 'sprockets/utils'
module Sprockets
# `Engines` provides a global and `Environment` instance registry.
#
# An engine is a type of processor that is bound to a filename
# extension. `application.js.coffee` indicates that the
# `CoffeeScriptProcessor` engine will be ran on the file.
#
# Extensions can be stacked and will be evaulated from right to
# left. `application.js.coffee.erb` will first run `ERBProcessor`
# then `CoffeeScriptProcessor`.
#
# All `Engine`s must follow the `Template` interface. It is
# recommended to subclass `Template`.
#
# Its recommended that you register engine changes on your local
# `Environment` instance.
#
# environment.register_engine '.foo', FooProcessor
#
# The global registry is exposed for plugins to register themselves.
#
# Sprockets.register_engine '.sass', SassProcessor
#
module Engines
include Utils
# Returns a `Hash` of `Engine`s registered on the `Environment`.
# If an `ext` argument is supplied, the `Engine` associated with
# that extension will be returned.
#
# environment.engines
# # => {".coffee" => CoffeeScriptProcessor, ".sass" => SassProcessor, ...}
#
def engines
config[:engines]
end
# Internal: Returns a `Hash` of engine extensions to mime types.
#
# # => { '.coffee' => 'application/javascript' }
def engine_mime_types
config[:engine_mime_types]
end
# Registers a new Engine `klass` for `ext`. If the `ext` already
# has an engine registered, it will be overridden.
#
# environment.register_engine '.coffee', CoffeeScriptProcessor
#
def register_engine(ext, klass, options = {})
unless options[:silence_deprecation]
msg = <<-MSG
Sprockets method `register_engine` is deprecated.
Please register a mime type using `register_mime_type` then
use `register_compressor` or `register_transformer`.
https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors
MSG
Deprecation.new([caller.first]).warn(msg)
end
ext = Sprockets::Utils.normalize_extension(ext)
self.computed_config = {}
if klass.respond_to?(:call)
processor = klass
self.config = hash_reassoc(config, :engines) do |engines|
engines.merge(ext => klass)
end
if options[:mime_type]
self.config = hash_reassoc(config, :engine_mime_types) do |mime_types|
mime_types.merge(ext.to_s => options[:mime_type])
end
end
else
processor = LegacyTiltProcessor.new(klass)
self.config = hash_reassoc(config, :engines) do |engines|
engines.merge(ext => processor)
end
if klass.respond_to?(:default_mime_type) && klass.default_mime_type
self.config = hash_reassoc(config, :engine_mime_types) do |mime_types|
mime_types.merge(ext.to_s => klass.default_mime_type)
end
end
end
end
end
end
sprockets-3.7.2/lib/sprockets/environment.rb 0000664 0000000 0000000 00000002072 13312220220 0021200 0 ustar 00root root 0000000 0000000 require 'sprockets/base'
require 'sprockets/cache/memory_store'
require 'sprockets/cached_environment'
module Sprockets
class Environment < Base
# `Environment` should initialized with your application's root
# directory. This should be the same as your Rails or Rack root.
#
# env = Environment.new(Rails.root)
#
def initialize(root = ".")
initialize_configuration(Sprockets)
self.root = root
self.cache = Cache::MemoryStore.new
yield self if block_given?
end
# Returns a cached version of the environment.
#
# All its file system calls are cached which makes `cached` much
# faster. This behavior is ideal in production since the file
# system only changes between deploys.
def cached
CachedEnvironment.new(self)
end
alias_method :index, :cached
def find_asset(*args)
cached.find_asset(*args)
end
def find_all_linked_assets(*args, &block)
cached.find_all_linked_assets(*args, &block)
end
def load(*args)
cached.load(*args)
end
end
end
sprockets-3.7.2/lib/sprockets/erb_processor.rb 0000664 0000000 0000000 00000001313 13312220220 0021500 0 ustar 00root root 0000000 0000000 require 'erb'
module Sprockets
class ERBProcessor
# Public: Return singleton instance with default options.
#
# Returns ERBProcessor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def initialize(&block)
@block = block
end
def call(input)
engine = ::ERB.new(input[:data], nil, '<>')
context = input[:environment].context_class.new(input)
klass = (class << context; self; end)
klass.class_eval(&@block) if @block
engine.def_method(klass, :_evaluate_template, input[:filename])
data = context._evaluate_template
context.metadata.merge(data: data)
end
end
end
sprockets-3.7.2/lib/sprockets/erb_template.rb 0000664 0000000 0000000 00000000357 13312220220 0021303 0 ustar 00root root 0000000 0000000 require 'sprockets/erb_processor'
module Sprockets
# Deprecated
class ERBTemplate < ERBProcessor
def call(*args)
Deprecation.new.warn "ERBTemplate is deprecated please use ERBProcessor instead"
super
end
end
end
sprockets-3.7.2/lib/sprockets/errors.rb 0000664 0000000 0000000 00000000662 13312220220 0020153 0 ustar 00root root 0000000 0000000 # Define some basic Sprockets error classes
module Sprockets
class Error < StandardError; end
class ArgumentError < Error; end
class ContentTypeMismatch < Error; end
class NotImplementedError < Error; end
class NotFound < Error; end
class ConversionError < NotFound; end
class FileNotFound < NotFound; end
class FileOutsidePaths < NotFound; end
end
sprockets-3.7.2/lib/sprockets/file_reader.rb 0000664 0000000 0000000 00000000763 13312220220 0021102 0 ustar 00root root 0000000 0000000 require 'set'
module Sprockets
# Internal: The first processor in the pipeline that reads the file into
# memory and passes it along as `input[:data]`.
class FileReader
def self.call(input)
env = input[:environment]
data = env.read_file(input[:filename], input[:content_type])
dependencies = Set.new(input[:metadata][:dependencies])
dependencies += [env.build_file_digest_uri(input[:filename])]
{ data: data, dependencies: dependencies }
end
end
end
sprockets-3.7.2/lib/sprockets/http_utils.rb 0000664 0000000 0000000 00000006723 13312220220 0021042 0 ustar 00root root 0000000 0000000 module Sprockets
# Internal: HTTP URI utilities. Many adapted from Rack::Utils. Mixed into
# Environment.
module HTTPUtils
extend self
# Public: Test mime type against mime range.
#
# match_mime_type?('text/html', 'text/*') => true
# match_mime_type?('text/plain', '*') => true
# match_mime_type?('text/html', 'application/json') => false
#
# Returns true if the given value is a mime match for the given mime match
# specification, false otherwise.
def match_mime_type?(value, matcher)
v1, v2 = value.split('/', 2)
m1, m2 = matcher.split('/', 2)
(m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
end
# Public: Return values from Hash where the key matches the mime type.
#
# hash - Hash of String matcher keys to Object values
# mime_type - String mime type
#
# Returns Array of Object values.
def match_mime_type_keys(hash, mime_type)
type, subtype = mime_type.split('/', 2)
[
hash["*"],
hash["*/*"],
hash["#{type}/*"],
hash["#{type}/#{subtype}"]
].compact
end
# Internal: Parse Accept header quality values.
#
# Adapted from Rack::Utils#q_values.
#
# Returns an Array of [String, Float].
def parse_q_values(values)
values.to_s.split(/\s*,\s*/).map do |part|
value, parameters = part.split(/\s*;\s*/, 2)
quality = 1.0
if md = /\Aq=([\d.]+)/.match(parameters)
quality = md[1].to_f
end
[value, quality]
end
end
# Internal: Find all qvalue matches from an Array of available options.
#
# Adapted from Rack::Utils#q_values.
#
# Returns Array of matched Strings from available Array or [].
def find_q_matches(q_values, available, &matcher)
matcher ||= lambda { |a, b| a == b }
matches = []
case q_values
when Array
when String
q_values = parse_q_values(q_values)
when NilClass
q_values = []
else
raise TypeError, "unknown q_values type: #{q_values.class}"
end
q_values.each do |accepted, quality|
if match = available.find { |option| matcher.call(option, accepted) }
matches << [match, quality]
end
end
matches.sort_by! { |match, quality| -quality }
matches.map! { |match, quality| match }
matches
end
# Internal: Find the best qvalue match from an Array of available options.
#
# Adapted from Rack::Utils#q_values.
#
# Returns the matched String from available Array or nil.
def find_best_q_match(q_values, available, &matcher)
find_q_matches(q_values, available, &matcher).first
end
# Internal: Find the all qvalue match from an Array of available mime type
# options.
#
# Adapted from Rack::Utils#q_values.
#
# Returns Array of matched mime type Strings from available Array or [].
def find_mime_type_matches(q_value_header, available)
find_q_matches(q_value_header, available) do |a, b|
match_mime_type?(a, b)
end
end
# Internal: Find the best qvalue match from an Array of available mime type
# options.
#
# Adapted from Rack::Utils#q_values.
#
# Returns the matched mime type String from available Array or nil.
def find_best_mime_type_match(q_value_header, available)
find_best_q_match(q_value_header, available) do |a, b|
match_mime_type?(a, b)
end
end
end
end
sprockets-3.7.2/lib/sprockets/jst_processor.rb 0000664 0000000 0000000 00000002243 13312220220 0021533 0 ustar 00root root 0000000 0000000 module Sprockets
# Public: .jst engine.
#
# Exports server side compiled templates to an object.
#
# Name your template "users/show.jst.ejs", "users/new.jst.eco", etc.
#
# To accept the default options
#
# environment.register_engine '.jst',
# JstProcessor,
# mime_type: 'application/javascript'
#
# Change the default namespace.
#
# environment.register_engine '.jst',
# JstProcessor.new(namespace: 'App.templates'),
# mime_type: 'application/javascript'
#
class JstProcessor
def self.default_namespace
'this.JST'
end
# Public: Return singleton instance with default options.
#
# Returns JstProcessor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def initialize(options = {})
@namespace = options[:namespace] || self.class.default_namespace
end
def call(input)
data = input[:data].gsub(/$(.)/m, "\\1 ").strip
key = input[:name]
<<-JST
(function() { #{@namespace} || (#{@namespace} = {}); #{@namespace}[#{key.inspect}] = #{data};
}).call(this);
JST
end
end
end
sprockets-3.7.2/lib/sprockets/legacy.rb 0000664 0000000 0000000 00000022201 13312220220 0020074 0 ustar 00root root 0000000 0000000 require 'pathname'
require 'sprockets/asset'
require 'sprockets/base'
require 'sprockets/cached_environment'
require 'sprockets/context'
require 'sprockets/manifest'
require 'sprockets/resolve'
module Sprockets
autoload :CoffeeScriptTemplate, 'sprockets/coffee_script_template'
autoload :EcoTemplate, 'sprockets/eco_template'
autoload :EjsTemplate, 'sprockets/ejs_template'
autoload :ERBTemplate, 'sprockets/erb_template'
autoload :SassTemplate, 'sprockets/sass_template'
autoload :ScssTemplate, 'sprockets/sass_template'
# Deprecated
Index = CachedEnvironment
class Base
include Resolve
# Deprecated: Change default return type of resolve() to return 2.x
# compatible plain filename String. 4.x will always return an Asset URI
# and a set of file system dependencies that had to be read to compute the
# result.
#
# 2.x
#
# resolve("foo.js")
# # => "/path/to/app/javascripts/foo.js"
#
# 3.x
#
# resolve("foo.js")
# # => "/path/to/app/javascripts/foo.js"
#
# resolve("foo.js", compat: true)
# # => "/path/to/app/javascripts/foo.js"
#
# resolve("foo.js", compat: false)
# # => [
# # "file:///path/to/app/javascripts/foo.js?type=application/javascript"
# # #
# # ]
#
# 4.x
#
# resolve("foo.js")
# # => [
# # "file:///path/to/app/javascripts/foo.js?type=application/javascript"
# # #
# # ]
#
def resolve_with_compat(path, options = {})
options = options.dup
if options.delete(:compat) { true }
uri, _ = resolve_without_compat(path, options)
if uri
path, _ = parse_asset_uri(uri)
path
else
nil
end
else
resolve_without_compat(path, options)
end
end
alias_method :resolve_without_compat, :resolve
alias_method :resolve, :resolve_with_compat
# Deprecated: Iterate over all logical paths with a matcher.
#
# Remove from 4.x.
#
# args - List of matcher objects.
#
# Returns Enumerator if no block is given.
def each_logical_path(*args, &block)
return to_enum(__method__, *args) unless block_given?
filters = args.flatten.map { |arg| Manifest.compile_match_filter(arg) }
logical_paths.each do |a, b|
if filters.any? { |f| f.call(a, b) }
if block.arity == 2
yield a, b
else
yield a
end
end
end
nil
end
# Deprecated: Enumerate over all logical paths in the environment.
#
# Returns an Enumerator of [logical_path, filename].
def logical_paths
return to_enum(__method__) unless block_given?
seen = Set.new
paths.each do |load_path|
stat_tree(load_path).each do |filename, stat|
next unless stat.file?
path = split_subpath(load_path, filename)
path, mime_type, _, _ = parse_path_extnames(path)
path = normalize_logical_path(path)
path += mime_types[mime_type][:extensions].first if mime_type
if !seen.include?(path)
yield path, filename
seen << path
end
end
end
nil
end
def cache_get(key)
cache.get(key)
end
def cache_set(key, value)
cache.set(key, value)
end
def normalize_logical_path(path)
dirname, basename = File.split(path)
path = dirname if basename == 'index'
path
end
private
# Deprecated: Seriously.
def matches_filter(filters, logical_path, filename)
return true if filters.empty?
filters.any? do |filter|
if filter.is_a?(Regexp)
filter.match(logical_path)
elsif filter.respond_to?(:call)
if filter.arity == 1
filter.call(logical_path)
else
filter.call(logical_path, filename.to_s)
end
else
File.fnmatch(filter.to_s, logical_path)
end
end
end
# URI.unescape is deprecated on 1.9. We need to use URI::Parser
# if its available.
if defined? URI::DEFAULT_PARSER
def unescape(str)
str = URI::DEFAULT_PARSER.unescape(str)
str.force_encoding(Encoding.default_internal) if Encoding.default_internal
str
end
else
def unescape(str)
URI.unescape(str)
end
end
end
class Asset
# Deprecated: Use #filename instead.
#
# Returns Pathname.
def pathname
@pathname ||= Pathname.new(filename)
end
# Deprecated: Expand asset into an `Array` of parts.
#
# Appending all of an assets body parts together should give you
# the asset's contents as a whole.
#
# This allows you to link to individual files for debugging
# purposes.
#
# Use Asset#included instead. Keeping a full copy of the bundle's processed
# assets in memory (and in cache) is expensive and redundant. The common use
# case is to relink to the assets anyway.
#
# Returns Array of Assets.
def to_a
if metadata[:included]
metadata[:included].map { |uri| @environment.load(uri) }
else
[self]
end
end
# Deprecated: Get all required Assets.
#
# See Asset#to_a
#
# Returns Array of Assets.
def dependencies
to_a.reject { |a| a.filename.eql?(self.filename) }
end
# Deprecated: Returns Time of the last time the source was modified.
#
# Time resolution is normalized to the nearest second.
#
# Returns Time.
def mtime
Time.at(@mtime)
end
end
class Context
# Deprecated: Change default return type of resolve() to return 2.x
# compatible plain filename String. 4.x will always return an Asset URI.
#
# 2.x
#
# resolve("foo.js")
# # => "/path/to/app/javascripts/foo.js"
#
# 3.x
#
# resolve("foo.js")
# # => "/path/to/app/javascripts/foo.js"
#
# resolve("foo.js", compat: true)
# # => "/path/to/app/javascripts/foo.js"
#
# resolve("foo.js", compat: false)
# # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
#
# 4.x
#
# resolve("foo.js")
# # => "file:///path/to/app/javascripts/foo.js?type=application/javascript"
#
def resolve_with_compat(path, options = {})
options = options.dup
# Support old :content_type option, prefer :accept going forward
if type = options.delete(:content_type)
type = self.content_type if type == :self
options[:accept] ||= type
end
if options.delete(:compat) { true }
uri = resolve_without_compat(path, options)
path, _ = environment.parse_asset_uri(uri)
path
else
resolve_without_compat(path, options)
end
end
alias_method :resolve_without_compat, :resolve
alias_method :resolve, :resolve_with_compat
end
class Manifest
# Deprecated: Compile logical path matching filter into a proc that can be
# passed to logical_paths.select(&proc).
#
# compile_match_filter(proc { |logical_path|
# File.extname(logical_path) == '.js'
# })
#
# compile_match_filter(/application.js/)
#
# compile_match_filter("foo/*.js")
#
# Returns a Proc or raise a TypeError.
def self.compile_match_filter(filter)
# If the filter is already a proc, great nothing to do.
if filter.respond_to?(:call)
filter
# If the filter is a regexp, wrap it in a proc that tests it against the
# logical path.
elsif filter.is_a?(Regexp)
proc { |logical_path| filter.match(logical_path) }
elsif filter.is_a?(String)
# If its an absolute path, detect the matching full filename
if PathUtils.absolute_path?(filter)
proc { |logical_path, filename| filename == filter.to_s }
else
# Otherwise do an fnmatch against the logical path.
proc { |logical_path| File.fnmatch(filter.to_s, logical_path) }
end
else
raise TypeError, "unknown filter type: #{filter.inspect}"
end
end
def self.simple_logical_path?(str)
str.is_a?(String) &&
!PathUtils.absolute_path?(str) &&
str !~ /\*|\*\*|\?|\[|\]|\{|\}/
end
def self.compute_alias_logical_path(path)
dirname, basename = File.split(path)
extname = File.extname(basename)
if File.basename(basename, extname) == 'index'
"#{dirname}#{extname}"
else
nil
end
end
# Deprecated: Filter logical paths in environment. Useful for selecting what
# files you want to compile.
#
# Returns an Enumerator.
def filter_logical_paths(*args)
filters = args.flatten.map { |arg| self.class.compile_match_filter(arg) }
environment.cached.logical_paths.select do |a, b|
filters.any? { |f| f.call(a, b) }
end
end
# Deprecated alias.
alias_method :find_logical_paths, :filter_logical_paths
end
end
sprockets-3.7.2/lib/sprockets/legacy_proc_processor.rb 0000664 0000000 0000000 00000001314 13312220220 0023220 0 ustar 00root root 0000000 0000000 require 'delegate'
module Sprockets
# Deprecated: Wraps legacy process Procs with new processor call signature.
#
# Will be removed in Sprockets 4.x.
#
# LegacyProcProcessor.new(:compress,
# proc { |context, data| data.gsub(...) })
#
class LegacyProcProcessor < Delegator
def initialize(name, proc)
@name = name
@proc = proc
end
def __getobj__
@proc
end
def name
"Sprockets::LegacyProcProcessor (#{@name})"
end
def to_s
name
end
def call(input)
context = input[:environment].context_class.new(input)
data = @proc.call(context, input[:data])
context.metadata.merge(data: data.to_str)
end
end
end
sprockets-3.7.2/lib/sprockets/legacy_tilt_processor.rb 0000664 0000000 0000000 00000001227 13312220220 0023234 0 ustar 00root root 0000000 0000000 require 'delegate'
module Sprockets
# Deprecated: Wraps legacy engine and process Tilt templates with new
# processor call signature.
#
# Will be removed in Sprockets 4.x.
#
# LegacyTiltProcessor.new(Tilt::CoffeeScriptProcessor)
#
class LegacyTiltProcessor < Delegator
def initialize(klass)
@klass = klass
end
def __getobj__
@klass
end
def call(input)
filename = input[:filename]
data = input[:data]
context = input[:environment].context_class.new(input)
data = @klass.new(filename) { data }.render(context, {})
context.metadata.merge(data: data.to_str)
end
end
end
sprockets-3.7.2/lib/sprockets/loader.rb 0000664 0000000 0000000 00000033522 13312220220 0020106 0 ustar 00root root 0000000 0000000 require 'sprockets/asset'
require 'sprockets/digest_utils'
require 'sprockets/engines'
require 'sprockets/errors'
require 'sprockets/file_reader'
require 'sprockets/mime'
require 'sprockets/path_utils'
require 'sprockets/processing'
require 'sprockets/processor_utils'
require 'sprockets/resolve'
require 'sprockets/transformers'
require 'sprockets/uri_utils'
require 'sprockets/unloaded_asset'
module Sprockets
# The loader phase takes a asset URI location and returns a constructed Asset
# object.
module Loader
include DigestUtils, PathUtils, ProcessorUtils, URIUtils
include Engines, Mime, Processing, Resolve, Transformers
# Public: Load Asset by Asset URI.
#
# uri - A String containing complete URI to a file including schema
# and full path such as:
# "file:///Path/app/assets/js/app.js?type=application/javascript"
#
#
# Returns Asset.
def load(uri)
unloaded = UnloadedAsset.new(uri, self)
if unloaded.params.key?(:id)
unless asset = asset_from_cache(unloaded.asset_key)
id = unloaded.params.delete(:id)
uri_without_id = build_asset_uri(unloaded.filename, unloaded.params)
asset = load_from_unloaded(UnloadedAsset.new(uri_without_id, self))
if asset[:id] != id
@logger.warn "Sprockets load error: Tried to find #{uri}, but latest was id #{asset[:id]}"
end
end
else
asset = fetch_asset_from_dependency_cache(unloaded) do |paths|
# When asset is previously generated, its "dependencies" are stored in the cache.
# The presence of `paths` indicates dependencies were stored.
# We can check to see if the dependencies have not changed by "resolving" them and
# generating a digest key from the resolved entries. If this digest key has not
# changed the asset will be pulled from cache.
#
# If this `paths` is present but the cache returns nothing then `fetch_asset_from_dependency_cache`
# will confusingly be called again with `paths` set to nil where the asset will be
# loaded from disk.
if paths
digest = DigestUtils.digest(resolve_dependencies(paths))
if uri_from_cache = cache.get(unloaded.digest_key(digest), true)
asset_from_cache(UnloadedAsset.new(uri_from_cache, self).asset_key)
end
else
load_from_unloaded(unloaded)
end
end
end
Asset.new(self, asset)
end
private
# Internal: Load asset hash from cache
#
# key - A String containing lookup information for an asset
#
# This method converts all "compressed" paths to absolute paths.
# Returns a hash of values representing an asset
def asset_from_cache(key)
asset = cache.get(key, true)
if asset
asset[:uri] = expand_from_root(asset[:uri])
asset[:load_path] = expand_from_root(asset[:load_path])
asset[:filename] = expand_from_root(asset[:filename])
asset[:metadata][:included].map! { |uri| expand_from_root(uri) } if asset[:metadata][:included]
asset[:metadata][:links].map! { |uri| expand_from_root(uri) } if asset[:metadata][:links]
asset[:metadata][:stubbed].map! { |uri| expand_from_root(uri) } if asset[:metadata][:stubbed]
asset[:metadata][:required].map! { |uri| expand_from_root(uri) } if asset[:metadata][:required]
asset[:metadata][:dependencies].map! { |uri| uri.start_with?("file-digest://") ? expand_from_root(uri) : uri } if asset[:metadata][:dependencies]
asset[:metadata].each_key do |k|
next unless k =~ /_dependencies\z/
asset[:metadata][k].map! { |uri| expand_from_root(uri) }
end
end
asset
end
# Internal: Loads an asset and saves it to cache
#
# unloaded - An UnloadedAsset
#
# This method is only called when the given unloaded asset could not be
# successfully pulled from cache.
def load_from_unloaded(unloaded)
unless file?(unloaded.filename)
raise FileNotFound, "could not find file: #{unloaded.filename}"
end
load_path, logical_path = paths_split(config[:paths], unloaded.filename)
unless load_path
raise FileOutsidePaths, "#{unloaded.filename} is no longer under a load path: #{self.paths.join(', ')}"
end
logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
name = logical_path
if pipeline = unloaded.params[:pipeline]
logical_path += ".#{pipeline}"
end
if type = unloaded.params[:type]
logical_path += config[:mime_types][type][:extensions].first
end
if type != file_type && !config[:transformers][file_type][type]
raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
end
processors = processors_for(type, file_type, engine_extnames, pipeline)
processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline)
dependencies = config[:dependencies] + [processors_dep_uri]
# Read into memory and process if theres a processor pipeline
if processors.any?
result = call_processors(processors, {
environment: self,
cache: self.cache,
uri: unloaded.uri,
filename: unloaded.filename,
load_path: load_path,
name: name,
content_type: type,
metadata: { dependencies: dependencies }
})
validate_processor_result!(result)
source = result.delete(:data)
metadata = result
metadata[:charset] = source.encoding.name.downcase unless metadata.key?(:charset)
metadata[:digest] = digest(source)
metadata[:length] = source.bytesize
else
dependencies << build_file_digest_uri(unloaded.filename)
metadata = {
digest: file_digest(unloaded.filename),
length: self.stat(unloaded.filename).size,
dependencies: dependencies
}
end
asset = {
uri: unloaded.uri,
load_path: load_path,
filename: unloaded.filename,
name: name,
logical_path: logical_path,
content_type: type,
source: source,
metadata: metadata,
dependencies_digest: DigestUtils.digest(resolve_dependencies(metadata[:dependencies]))
}
asset[:id] = pack_hexdigest(digest(asset))
asset[:uri] = build_asset_uri(unloaded.filename, unloaded.params.merge(id: asset[:id]))
# Deprecated: Avoid tracking Asset mtime
asset[:mtime] = metadata[:dependencies].map { |u|
if u.start_with?("file-digest:")
s = self.stat(parse_file_digest_uri(u))
s ? s.mtime.to_i : nil
else
nil
end
}.compact.max
asset[:mtime] ||= self.stat(unloaded.filename).mtime.to_i
store_asset(asset, unloaded)
asset
end
# Internal: Save a given asset to the cache
#
# asset - A hash containing values of loaded asset
# unloaded - The UnloadedAsset used to lookup the `asset`
#
# This method converts all absolute paths to "compressed" paths
# which are relative if they're in the root.
def store_asset(asset, unloaded)
# Save the asset in the cache under the new URI
cached_asset = asset.dup
cached_asset[:uri] = compress_from_root(asset[:uri])
cached_asset[:filename] = compress_from_root(asset[:filename])
cached_asset[:load_path] = compress_from_root(asset[:load_path])
if cached_asset[:metadata]
# Deep dup to avoid modifying `asset`
cached_asset[:metadata] = cached_asset[:metadata].dup
if cached_asset[:metadata][:included] && !cached_asset[:metadata][:included].empty?
cached_asset[:metadata][:included] = cached_asset[:metadata][:included].dup
cached_asset[:metadata][:included].map! { |uri| compress_from_root(uri) }
end
if cached_asset[:metadata][:links] && !cached_asset[:metadata][:links].empty?
cached_asset[:metadata][:links] = cached_asset[:metadata][:links].dup
cached_asset[:metadata][:links].map! { |uri| compress_from_root(uri) }
end
if cached_asset[:metadata][:stubbed] && !cached_asset[:metadata][:stubbed].empty?
cached_asset[:metadata][:stubbed] = cached_asset[:metadata][:stubbed].dup
cached_asset[:metadata][:stubbed].map! { |uri| compress_from_root(uri) }
end
if cached_asset[:metadata][:required] && !cached_asset[:metadata][:required].empty?
cached_asset[:metadata][:required] = cached_asset[:metadata][:required].dup
cached_asset[:metadata][:required].map! { |uri| compress_from_root(uri) }
end
if cached_asset[:metadata][:dependencies] && !cached_asset[:metadata][:dependencies].empty?
cached_asset[:metadata][:dependencies] = cached_asset[:metadata][:dependencies].dup
cached_asset[:metadata][:dependencies].map! do |uri|
uri.start_with?("file-digest://".freeze) ? compress_from_root(uri) : uri
end
end
# compress all _dependencies in metadata like `sass_dependencies`
cached_asset[:metadata].each do |key, value|
next unless key =~ /_dependencies\z/
cached_asset[:metadata][key] = value.dup
cached_asset[:metadata][key].map! {|uri| compress_from_root(uri) }
end
end
# Unloaded asset and stored_asset now have a different URI
stored_asset = UnloadedAsset.new(asset[:uri], self)
cache.set(stored_asset.asset_key, cached_asset, true)
# Save the new relative path for the digest key of the unloaded asset
cache.set(unloaded.digest_key(asset[:dependencies_digest]), stored_asset.compressed_path, true)
end
# Internal: Resolve set of dependency URIs.
#
# uris - An Array of "dependencies" for example:
# ["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
# "processors:type=text/css&file_type=text/css&pipeline=self",
# "file-digest:///Full/path/app/assets/stylesheets"]
#
# Returns back array of things that the given uri dpends on
# For example the environment version, if you're using a different version of sprockets
# then the dependencies should be different, this is used only for generating cache key
# for example the "environment-version" may be resolved to "environment-1.0-3.2.0" for
# version "3.2.0" of sprockets.
#
# Any paths that are returned are converted to relative paths
#
# Returns array of resolved dependencies
def resolve_dependencies(uris)
uris.map { |uri| resolve_dependency(uri) }
end
# Internal: Retrieves an asset based on its digest
#
# unloaded - An UnloadedAsset
# limit - A Fixnum which sets the maximum number of versions of "histories"
# stored in the cache
#
# This method attempts to retrieve the last `limit` number of histories of an asset
# from the cache a "history" which is an array of unresolved "dependencies" that the asset needs
# to compile. In this case A dependency can refer to either an asset i.e. index.js
# may rely on jquery.js (so jquery.js is a depndency), or other factors that may affect
# compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
# are used.
#
# For example a history array may look something like this
#
# [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
# "processors:type=text/css&file_digesttype=text/css&pipeline=self",
# "file-digest:///Full/path/app/assets/stylesheets"]]
#
# Where the first entry is a Set of dependencies for last generated version of that asset.
# Multiple versions are stored since sprockets keeps the last `limit` number of assets
# generated present in the system.
#
# If a "history" of dependencies is present in the cache, each version of "history" will be
# yielded to the passed block which is responsible for loading the asset. If found, the existing
# history will be saved with the dependency that found a valid asset moved to the front.
#
# If no history is present, or if none of the histories could be resolved to a valid asset then,
# the block is yielded to and expected to return a valid asset.
# When this happens the dependencies for the returned asset are added to the "history", and older
# entries are removed if the "history" is above `limit`.
def fetch_asset_from_dependency_cache(unloaded, limit = 3)
key = unloaded.dependency_history_key
history = cache.get(key) || []
history.each_with_index do |deps, index|
expanded_deps = deps.map do |path|
path.start_with?("file-digest://") ? expand_from_root(path) : path
end
if asset = yield(expanded_deps)
cache.set(key, history.rotate!(index)) if index > 0
return asset
end
end
asset = yield
deps = asset[:metadata][:dependencies].dup.map! do |uri|
uri.start_with?("file-digest://") ? compress_from_root(uri) : uri
end
cache.set(key, history.unshift(deps).take(limit))
asset
end
end
end
sprockets-3.7.2/lib/sprockets/manifest.rb 0000664 0000000 0000000 00000023607 13312220220 0020451 0 ustar 00root root 0000000 0000000 require 'json'
require 'time'
require 'concurrent'
require 'sprockets/manifest_utils'
require 'sprockets/utils/gzip'
module Sprockets
# The Manifest logs the contents of assets compiled to a single directory. It
# records basic attributes about the asset for fast lookup without having to
# compile. A pointer from each logical path indicates which fingerprinted
# asset is the current one.
#
# The JSON is part of the public API and should be considered stable. This
# should make it easy to read from other programming languages and processes
# that don't have sprockets loaded. See `#assets` and `#files` for more
# infomation about the structure.
class Manifest
include ManifestUtils
attr_reader :environment
# Create new Manifest associated with an `environment`. `filename` is a full
# path to the manifest json file. The file may or may not already exist. The
# dirname of the `filename` will be used to write compiled assets to.
# Otherwise, if the path is a directory, the filename will default a random
# ".sprockets-manifest-*.json" file in that directory.
#
# Manifest.new(environment, "./public/assets/manifest.json")
#
def initialize(*args)
if args.first.is_a?(Base) || args.first.nil?
@environment = args.shift
end
@directory, @filename = args[0], args[1]
# Whether the manifest file is using the old manifest-*.json naming convention
@legacy_manifest = false
# Expand paths
@directory = File.expand_path(@directory) if @directory
@filename = File.expand_path(@filename) if @filename
# If filename is given as the second arg
if @directory && File.extname(@directory) != ""
@directory, @filename = nil, @directory
end
# Default dir to the directory of the filename
@directory ||= File.dirname(@filename) if @filename
# If directory is given w/o filename, pick a random manifest location
@rename_filename = nil
if @directory && @filename.nil?
@filename = find_directory_manifest(@directory)
# If legacy manifest name autodetected, mark to rename on save
if File.basename(@filename).start_with?("manifest")
@rename_filename = File.join(@directory, generate_manifest_path)
end
end
unless @directory && @filename
raise ArgumentError, "manifest requires output filename"
end
data = {}
begin
if File.exist?(@filename)
data = json_decode(File.read(@filename))
end
rescue JSON::ParserError => e
logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
end
@data = data
end
# Returns String path to manifest.json file.
attr_reader :filename
alias_method :path, :filename
attr_reader :directory
alias_method :dir, :directory
# Returns internal assets mapping. Keys are logical paths which
# map to the latest fingerprinted filename.
#
# Logical path (String): Fingerprint path (String)
#
# { "application.js" => "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js",
# "jquery.js" => "jquery-ae0908555a245f8266f77df5a8edca2e.js" }
#
def assets
@data['assets'] ||= {}
end
# Returns internal file directory listing. Keys are filenames
# which map to an attributes array.
#
# Fingerprint path (String):
# logical_path: Logical path (String)
# mtime: ISO8601 mtime (String)
# digest: Base64 hex digest (String)
#
# { "application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js" =>
# { 'logical_path' => "application.js",
# 'mtime' => "2011-12-13T21:47:08-06:00",
# 'digest' => "2e8e9a7c6b0aafa0c9bdeec90ea30213" } }
#
def files
@data['files'] ||= {}
end
# Public: Find all assets matching pattern set in environment.
#
# Returns Enumerator of Assets.
def find(*args)
unless environment
raise Error, "manifest requires environment for compilation"
end
return to_enum(__method__, *args) unless block_given?
paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
filters = filters.map { |arg| self.class.compile_match_filter(arg) }
environment = self.environment.cached
paths.each do |path|
environment.find_all_linked_assets(path) do |asset|
yield asset
end
end
if filters.any?
environment.logical_paths do |logical_path, filename|
if filters.any? { |f| f.call(logical_path, filename) }
environment.find_all_linked_assets(filename) do |asset|
yield asset
end
end
end
end
nil
end
# Public: Find the source of assets by paths.
#
# Returns Enumerator of assets file content.
def find_sources(*args)
return to_enum(__method__, *args) unless block_given?
if environment
find(*args).each do |asset|
yield asset.source
end
else
args.each do |path|
asset = assets[path]
yield File.binread(File.join(dir, asset)) if asset
end
end
end
# Compile and write asset to directory. The asset is written to a
# fingerprinted filename like
# `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
# also inserted into the manifest file.
#
# compile("application.js")
#
def compile(*args)
unless environment
raise Error, "manifest requires environment for compilation"
end
filenames = []
concurrent_compressors = []
concurrent_writers = []
find(*args) do |asset|
files[asset.digest_path] = {
'logical_path' => asset.logical_path,
'mtime' => asset.mtime.iso8601,
'size' => asset.bytesize,
'digest' => asset.hexdigest,
# Deprecated: Remove beta integrity attribute in next release.
# Callers should DigestUtils.hexdigest_integrity_uri to compute the
# digest themselves.
'integrity' => DigestUtils.hexdigest_integrity_uri(asset.hexdigest)
}
assets[asset.logical_path] = asset.digest_path
if alias_logical_path = self.class.compute_alias_logical_path(asset.logical_path)
assets[alias_logical_path] = asset.digest_path
end
target = File.join(dir, asset.digest_path)
if File.exist?(target)
logger.debug "Skipping #{target}, already exists"
else
logger.info "Writing #{target}"
write_file = Concurrent::Future.execute { asset.write_to target }
concurrent_writers << write_file
end
filenames << asset.filename
next if environment.skip_gzip?
gzip = Utils::Gzip.new(asset)
next if gzip.cannot_compress?(environment.mime_types)
if File.exist?("#{target}.gz")
logger.debug "Skipping #{target}.gz, already exists"
else
logger.info "Writing #{target}.gz"
concurrent_compressors << Concurrent::Future.execute do
write_file.wait! if write_file
gzip.compress(target)
end
end
end
concurrent_writers.each(&:wait!)
concurrent_compressors.each(&:wait!)
save
filenames
end
# Removes file from directory and from manifest. `filename` must
# be the name with any directory path.
#
# manifest.remove("application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js")
#
def remove(filename)
path = File.join(dir, filename)
gzip = "#{path}.gz"
logical_path = files[filename]['logical_path']
if assets[logical_path] == filename
assets.delete(logical_path)
end
files.delete(filename)
FileUtils.rm(path) if File.exist?(path)
FileUtils.rm(gzip) if File.exist?(gzip)
save
logger.info "Removed #{filename}"
nil
end
# Cleanup old assets in the compile directory. By default it will
# keep the latest version, 2 backups and any created within the past hour.
#
# Examples
#
# To force only 1 backup to be kept, set count=1 and age=0.
#
# To only keep files created within the last 10 minutes, set count=0 and
# age=600.
#
def clean(count = 2, age = 3600)
asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
asset_versions.each do |logical_path, versions|
current = assets[logical_path]
versions.reject { |path, _|
path == current
}.sort_by { |_, attrs|
# Sort by timestamp
Time.parse(attrs['mtime'])
}.reverse.each_with_index.drop_while { |(_, attrs), index|
_age = [0, Time.now - Time.parse(attrs['mtime'])].max
# Keep if under age or within the count limit
_age < age || index < count
}.each { |(path, _), _|
# Remove old assets
remove(path)
}
end
end
# Wipe directive
def clobber
FileUtils.rm_r(directory) if File.exist?(directory)
logger.info "Removed #{directory}"
nil
end
# Persist manfiest back to FS
def save
if @rename_filename
logger.info "Renaming #{@filename} to #{@rename_filename}"
FileUtils.mv(@filename, @rename_filename)
@filename = @rename_filename
@rename_filename = nil
end
data = json_encode(@data)
FileUtils.mkdir_p File.dirname(@filename)
PathUtils.atomic_write(@filename) do |f|
f.write(data)
end
end
private
def json_decode(obj)
JSON.parse(obj, create_additions: false)
end
def json_encode(obj)
JSON.generate(obj)
end
def logger
if environment
environment.logger
else
logger = Logger.new($stderr)
logger.level = Logger::FATAL
logger
end
end
end
end
sprockets-3.7.2/lib/sprockets/manifest_utils.rb 0000664 0000000 0000000 00000003021 13312220220 0021655 0 ustar 00root root 0000000 0000000 require 'securerandom'
module Sprockets
# Public: Manifest utilities.
module ManifestUtils
extend self
MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/
# Public: Generate a new random manifest path.
#
# Manifests are not intended to be accessed publicly, but typically live
# alongside public assets for convenience. To avoid being served, the
# filename is prefixed with a "." which is usually hidden by web servers
# like Apache. To help in other environments that may not control this,
# a random hex string is appended to the filename to prevent people from
# guessing the location. If directory indexes are enabled on the server,
# all bets are off.
#
# Return String path.
def generate_manifest_path
".sprockets-manifest-#{SecureRandom.hex(16)}.json"
end
# Public: Find or pick a new manifest filename for target build directory.
#
# dirname - String dirname
#
# Examples
#
# find_directory_manifest("/app/public/assets")
# # => "/app/public/assets/.sprockets-manifest-abc123.json"
#
# Returns String filename.
def find_directory_manifest(dirname)
entries = File.directory?(dirname) ? Dir.entries(dirname) : []
entry = entries.find { |e| e =~ MANIFEST_RE } ||
# Deprecated: Will be removed in 4.x
entries.find { |e| e =~ LEGACY_MANIFEST_RE } ||
generate_manifest_path
File.join(dirname, entry)
end
end
end
sprockets-3.7.2/lib/sprockets/mime.rb 0000664 0000000 0000000 00000007164 13312220220 0017572 0 ustar 00root root 0000000 0000000 require 'sprockets/encoding_utils'
require 'sprockets/http_utils'
require 'sprockets/utils'
module Sprockets
module Mime
include HTTPUtils, Utils
# Public: Mapping of MIME type Strings to properties Hash.
#
# key - MIME Type String
# value - Hash
# extensions - Array of extnames
# charset - Default Encoding or function to detect encoding
#
# Returns Hash.
def mime_types
config[:mime_types]
end
# Internal: Mapping of MIME extension Strings to MIME type Strings.
#
# Used for internal fast lookup purposes.
#
# Examples:
#
# mime_exts['.js'] #=> 'application/javascript'
#
# key - MIME extension String
# value - MIME Type String
#
# Returns Hash.
def mime_exts
config[:mime_exts]
end
# Public: Register a new mime type.
#
# mime_type - String MIME Type
# options - Hash
# extensions: Array of String extnames
# charset: Proc/Method that detects the charset of a file.
# See EncodingUtils.
#
# Returns nothing.
def register_mime_type(mime_type, options = {})
# Legacy extension argument, will be removed from 4.x
if options.is_a?(String)
options = { extensions: [options] }
end
extnames = Array(options[:extensions]).map { |extname|
Sprockets::Utils.normalize_extension(extname)
}
charset = options[:charset]
charset ||= :default if mime_type.start_with?('text/')
charset = EncodingUtils::CHARSET_DETECT[charset] if charset.is_a?(Symbol)
self.computed_config = {}
self.config = hash_reassoc(config, :mime_exts) do |mime_exts|
extnames.each do |extname|
mime_exts[extname] = mime_type
end
mime_exts
end
self.config = hash_reassoc(config, :mime_types) do |mime_types|
type = { extensions: extnames }
type[:charset] = charset if charset
mime_types.merge(mime_type => type)
end
end
# Internal: Get detecter function for MIME type.
#
# mime_type - String MIME type
#
# Returns Proc detector or nil if none is available.
def mime_type_charset_detecter(mime_type)
if type = config[:mime_types][mime_type]
if detect = type[:charset]
return detect
end
end
end
# Public: Read file on disk with MIME type specific encoding.
#
# filename - String path
# content_type - String MIME type
#
# Returns String file contents transcoded to UTF-8 or in its external
# encoding.
def read_file(filename, content_type = nil)
data = File.binread(filename)
if detect = mime_type_charset_detecter(content_type)
detect.call(data).encode(Encoding::UTF_8, :universal_newline => true)
else
data
end
end
private
def extname_map
self.computed_config[:_extnames] ||= compute_extname_map
end
def compute_extname_map
graph = {}
([nil] + pipelines.keys.map(&:to_s)).each do |pipeline|
pipeline_extname = ".#{pipeline}" if pipeline
([[nil, nil]] + config[:mime_exts].to_a).each do |format_extname, format_type|
4.times do |n|
config[:engines].keys.permutation(n).each do |engine_extnames|
key = "#{pipeline_extname}#{format_extname}#{engine_extnames.join}"
type = format_type || config[:engine_mime_types][engine_extnames.first]
graph[key] = {type: type, engines: engine_extnames, pipeline: pipeline}
end
end
end
end
graph
end
end
end
sprockets-3.7.2/lib/sprockets/path_dependency_utils.rb 0000664 0000000 0000000 00000005343 13312220220 0023212 0 ustar 00root root 0000000 0000000 require 'set'
require 'sprockets/path_utils'
require 'sprockets/uri_utils'
module Sprockets
# Internal: Related PathUtils helpers that also track all the file system
# calls they make for caching purposes. All functions return a standard
# return value and a Set of cache dependency URIs that can be used in the
# future to see if the returned value should be invalidated from cache.
#
# entries_with_dependencies("app/assets/javascripts")
# # => [
# # ["application.js", "projects.js", "users.js", ...]
# # #
# # ]
#
# The returned dependency set can be passed to resolve_dependencies(deps)
# to check if the returned result is still fresh. In this case, entry always
# returns a single path, but multiple calls should accumulate dependencies
# into a single set thats saved off and checked later.
#
# resolve_dependencies(deps)
# # => "\x01\x02\x03"
#
# Later, resolving the same set again will produce a different hash if
# something on the file system has changed.
#
# resolve_dependencies(deps)
# # => "\x03\x04\x05"
#
module PathDependencyUtils
include PathUtils
include URIUtils
# Internal: List directory entries and return a set of dependencies that
# would invalid the cached return result.
#
# See PathUtils#entries
#
# path - String directory path
#
# Returns an Array of entry names and a Set of dependency URIs.
def entries_with_dependencies(path)
return entries(path), file_digest_dependency_set(path)
end
# Internal: List directory filenames and associated Stats under a
# directory.
#
# See PathUtils#stat_directory
#
# dir - A String directory
#
# Returns an Array of filenames and a Set of dependency URIs.
def stat_directory_with_dependencies(dir)
return stat_directory(dir).to_a, file_digest_dependency_set(dir)
end
# Internal: Returns a set of dependencies for a particular path.
#
# path - String directory path
#
# Returns a Set of dependency URIs.
def file_digest_dependency_set(path)
Set.new([build_file_digest_uri(path)])
end
# Internal: List directory filenames and associated Stats under an entire
# directory tree.
#
# See PathUtils#stat_sorted_tree
#
# dir - A String directory
#
# Returns an Array of filenames and a Set of dependency URIs.
def stat_sorted_tree_with_dependencies(dir)
deps = Set.new([build_file_digest_uri(dir)])
results = stat_sorted_tree(dir).map do |path, stat|
deps << build_file_digest_uri(path) if stat.directory?
[path, stat]
end
return results, deps
end
end
end
sprockets-3.7.2/lib/sprockets/path_digest_utils.rb 0000664 0000000 0000000 00000002354 13312220220 0022352 0 ustar 00root root 0000000 0000000 require 'sprockets/digest_utils'
require 'sprockets/path_utils'
module Sprockets
# Internal: Crossover of path and digest utilities functions.
module PathDigestUtils
include DigestUtils, PathUtils
# Internal: Compute digest for file stat.
#
# path - String filename
# stat - File::Stat
#
# Returns String digest bytes.
def stat_digest(path, stat)
if stat.directory?
# If its a directive, digest the list of filenames
digest_class.digest(self.entries(path).join(','))
elsif stat.file?
# If its a file, digest the contents
digest_class.file(path.to_s).digest
else
raise TypeError, "stat was not a directory or file: #{stat.ftype}"
end
end
# Internal: Compute digest for path.
#
# path - String filename or directory path.
#
# Returns String digest bytes or nil.
def file_digest(path)
if stat = self.stat(path)
self.stat_digest(path, stat)
end
end
# Internal: Compute digest for a set of paths.
#
# paths - Array of filename or directory paths.
#
# Returns String digest bytes.
def files_digest(paths)
self.digest(paths.map { |path| self.file_digest(path) })
end
end
end
sprockets-3.7.2/lib/sprockets/path_utils.rb 0000664 0000000 0000000 00000016237 13312220220 0021020 0 ustar 00root root 0000000 0000000 module Sprockets
# Internal: File and path related utilities. Mixed into Environment.
#
# Probably would be called FileUtils, but that causes namespace annoyances
# when code actually wants to reference ::FileUtils.
module PathUtils
extend self
# Public: Like `File.stat`.
#
# path - String file or directory path
#
# Returns nil if the file does not exist.
def stat(path)
if File.exist?(path)
File.stat(path.to_s)
else
nil
end
end
# Public: Like `File.file?`.
#
# path - String file path.
#
# Returns true path exists and is a file.
def file?(path)
if stat = self.stat(path)
stat.file?
else
false
end
end
# Public: Like `File.directory?`.
#
# path - String file path.
#
# Returns true path exists and is a directory.
def directory?(path)
if stat = self.stat(path)
stat.directory?
else
false
end
end
# Public: A version of `Dir.entries` that filters out `.` files and `~`
# swap files.
#
# path - String directory path
#
# Returns an empty `Array` if the directory does not exist.
def entries(path)
if File.directory?(path)
entries = Dir.entries(path, :encoding => Encoding.default_internal)
entries.reject! { |entry|
entry.start_with?(".".freeze) ||
(entry.start_with?("#".freeze) && entry.end_with?("#".freeze)) ||
entry.end_with?("~".freeze)
}
entries.sort!
else
[]
end
end
# Public: Check if path is absolute or relative.
#
# path - String path.
#
# Returns true if path is absolute, otherwise false.
if File::ALT_SEPARATOR
require 'pathname'
# On Windows, ALT_SEPARATOR is \
# Delegate to Pathname since the logic gets complex.
def absolute_path?(path)
Pathname.new(path).absolute?
end
else
def absolute_path?(path)
path[0] == File::SEPARATOR
end
end
if File::ALT_SEPARATOR
SEPARATOR_PATTERN = "#{Regexp.quote(File::SEPARATOR)}|#{Regexp.quote(File::ALT_SEPARATOR)}"
else
SEPARATOR_PATTERN = "#{Regexp.quote(File::SEPARATOR)}"
end
# Public: Check if path is explicitly relative.
# Starts with "./" or "../".
#
# path - String path.
#
# Returns true if path is relative, otherwise false.
def relative_path?(path)
path =~ /^\.\.?($|#{SEPARATOR_PATTERN})/ ? true : false
end
# Internal: Get relative path for root path and subpath.
#
# path - String path
# subpath - String subpath of path
#
# Returns relative String path if subpath is a subpath of path, or nil if
# subpath is outside of path.
def split_subpath(path, subpath)
return "" if path == subpath
path = File.join(path, '')
if subpath.start_with?(path)
subpath[path.length..-1]
else
nil
end
end
# Internal: Detect root path and base for file in a set of paths.
#
# paths - Array of String paths
# filename - String path of file expected to be in one of the paths.
#
# Returns [String root, String path]
def paths_split(paths, filename)
paths.each do |path|
if subpath = split_subpath(path, filename)
return path, subpath
end
end
nil
end
# Internal: Get path's extensions.
#
# path - String
#
# Returns an Array of String extnames.
def path_extnames(path)
File.basename(path).scan(/\.[^.]+/)
end
# Internal: Match path extnames against available extensions.
#
# path - String
# extensions - Hash of String extnames to values
#
# Returns [String extname, Object value] or nil nothing matched.
def match_path_extname(path, extensions)
basename = File.basename(path)
i = basename.index('.'.freeze)
while i && i < basename.length - 1
extname = basename[i..-1]
if value = extensions[extname]
return extname, value
end
i = basename.index('.'.freeze, i+1)
end
nil
end
# Internal: Returns all parents for path
#
# path - String absolute filename or directory
# root - String path to stop at (default: system root)
#
# Returns an Array of String paths.
def path_parents(path, root = nil)
root = "#{root}#{File::SEPARATOR}" if root
parents = []
loop do
parent = File.dirname(path)
break if parent == path
break if root && !path.start_with?(root)
parents << path = parent
end
parents
end
# Internal: Find target basename checking upwards from path.
#
# basename - String filename: ".sprocketsrc"
# path - String path to start search: "app/assets/javascripts/app.js"
# root - String path to stop at (default: system root)
#
# Returns String filename or nil.
def find_upwards(basename, path, root = nil)
path_parents(path, root).each do |dir|
filename = File.join(dir, basename)
return filename if file?(filename)
end
nil
end
# Public: Stat all the files under a directory.
#
# dir - A String directory
#
# Returns an Enumerator of [path, stat].
def stat_directory(dir)
return to_enum(__method__, dir) unless block_given?
self.entries(dir).each do |entry|
path = File.join(dir, entry)
if stat = self.stat(path)
yield path, stat
end
end
nil
end
# Public: Recursive stat all the files under a directory.
#
# dir - A String directory
#
# Returns an Enumerator of [path, stat].
def stat_tree(dir, &block)
return to_enum(__method__, dir) unless block_given?
self.stat_directory(dir) do |path, stat|
yield path, stat
if stat.directory?
stat_tree(path, &block)
end
end
nil
end
# Public: Recursive stat all the files under a directory in alphabetical
# order.
#
# dir - A String directory
#
# Returns an Enumerator of [path, stat].
def stat_sorted_tree(dir, &block)
return to_enum(__method__, dir) unless block_given?
self.stat_directory(dir).sort_by { |path, stat|
stat.directory? ? "#{path}/" : path
}.each do |path, stat|
yield path, stat
if stat.directory?
stat_sorted_tree(path, &block)
end
end
nil
end
# Public: Write to a file atomically. Useful for situations where you
# don't want other processes or threads to see half-written files.
#
# Utils.atomic_write('important.file') do |file|
# file.write('hello')
# end
#
# Returns nothing.
def atomic_write(filename)
dirname, basename = File.split(filename)
basename = [
basename,
Thread.current.object_id,
Process.pid,
rand(1000000)
].join('.')
tmpname = File.join(dirname, basename)
File.open(tmpname, 'wb+') do |f|
yield f
end
File.rename(tmpname, filename)
ensure
File.delete(tmpname) if File.exist?(tmpname)
end
end
end
sprockets-3.7.2/lib/sprockets/paths.rb 0000664 0000000 0000000 00000003772 13312220220 0017763 0 ustar 00root root 0000000 0000000 require 'sprockets/path_utils'
require 'sprockets/utils'
module Sprockets
module Paths
include PathUtils, Utils
# Returns `Environment` root.
#
# All relative paths are expanded with root as its base. To be
# useful set this to your applications root directory. (`Rails.root`)
def root
config[:root]
end
# Internal: Change Environment root.
#
# Only the initializer should change the root.
def root=(path)
self.config = hash_reassoc(config, :root) do
File.expand_path(path)
end
end
private :root=
# Returns an `Array` of path `String`s.
#
# These paths will be used for asset logical path lookups.
def paths
config[:paths]
end
# Prepend a `path` to the `paths` list.
#
# Paths at the end of the `Array` have the least priority.
def prepend_path(path)
self.config = hash_reassoc(config, :paths) do |paths|
path = File.expand_path(path, config[:root]).freeze
paths.unshift(path)
end
end
# Append a `path` to the `paths` list.
#
# Paths at the beginning of the `Array` have a higher priority.
def append_path(path)
self.config = hash_reassoc(config, :paths) do |paths|
path = File.expand_path(path, config[:root]).freeze
paths.push(path)
end
end
# Clear all paths and start fresh.
#
# There is no mechanism for reordering paths, so its best to
# completely wipe the paths list and reappend them in the order
# you want.
def clear_paths
self.config = hash_reassoc(config, :paths) do |paths|
paths.clear
end
end
# Public: Iterate over every file under all load paths.
#
# Returns Enumerator if no block is given.
def each_file
return to_enum(__method__) unless block_given?
paths.each do |root|
stat_tree(root).each do |filename, stat|
if stat.file?
yield filename
end
end
end
nil
end
end
end
sprockets-3.7.2/lib/sprockets/processing.rb 0000664 0000000 0000000 00000020103 13312220220 0021003 0 ustar 00root root 0000000 0000000 require 'sprockets/engines'
require 'sprockets/file_reader'
require 'sprockets/legacy_proc_processor'
require 'sprockets/legacy_tilt_processor'
require 'sprockets/mime'
require 'sprockets/processor_utils'
require 'sprockets/uri_utils'
require 'sprockets/utils'
module Sprockets
# `Processing` is an internal mixin whose public methods are exposed on
# the `Environment` and `CachedEnvironment` classes.
module Processing
include ProcessorUtils, URIUtils, Utils
def pipelines
config[:pipelines]
end
def register_pipeline(name, proc = nil, &block)
proc ||= block
self.config = hash_reassoc(config, :pipelines) do |pipelines|
pipelines.merge(name.to_sym => proc)
end
end
# Preprocessors are ran before Postprocessors and Engine
# processors.
def preprocessors
config[:preprocessors]
end
alias_method :processors, :preprocessors
# Postprocessors are ran after Preprocessors and Engine processors.
def postprocessors
config[:postprocessors]
end
# Registers a new Preprocessor `klass` for `mime_type`.
#
# register_preprocessor 'text/css', Sprockets::DirectiveProcessor
#
# A block can be passed for to create a shorthand processor.
#
# register_preprocessor 'text/css', :my_processor do |context, data|
# data.gsub(...)
# end
#
def register_preprocessor(*args, &block)
register_config_processor(:preprocessors, *args, &block)
end
alias_method :register_processor, :register_preprocessor
# Registers a new Postprocessor `klass` for `mime_type`.
#
# register_postprocessor 'application/javascript', Sprockets::DirectiveProcessor
#
# A block can be passed for to create a shorthand processor.
#
# register_postprocessor 'application/javascript', :my_processor do |context, data|
# data.gsub(...)
# end
#
def register_postprocessor(*args, &block)
register_config_processor(:postprocessors, *args, &block)
end
# Remove Preprocessor `klass` for `mime_type`.
#
# unregister_preprocessor 'text/css', Sprockets::DirectiveProcessor
#
def unregister_preprocessor(*args)
unregister_config_processor(:preprocessors, *args)
end
alias_method :unregister_processor, :unregister_preprocessor
# Remove Postprocessor `klass` for `mime_type`.
#
# unregister_postprocessor 'text/css', Sprockets::DirectiveProcessor
#
def unregister_postprocessor(*args)
unregister_config_processor(:postprocessors, *args)
end
# Bundle Processors are ran on concatenated assets rather than
# individual files.
def bundle_processors
config[:bundle_processors]
end
# Registers a new Bundle Processor `klass` for `mime_type`.
#
# register_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor
#
# A block can be passed for to create a shorthand processor.
#
# register_bundle_processor 'application/javascript', :my_processor do |context, data|
# data.gsub(...)
# end
#
def register_bundle_processor(*args, &block)
register_config_processor(:bundle_processors, *args, &block)
end
# Remove Bundle Processor `klass` for `mime_type`.
#
# unregister_bundle_processor 'application/javascript', Sprockets::DirectiveProcessor
#
def unregister_bundle_processor(*args)
unregister_config_processor(:bundle_processors, *args)
end
# Public: Register bundle metadata reducer function.
#
# Examples
#
# Sprockets.register_bundle_metadata_reducer 'application/javascript', :jshint_errors, [], :+
#
# Sprockets.register_bundle_metadata_reducer 'text/css', :selector_count, 0 { |total, count|
# total + count
# }
#
# mime_type - String MIME Type. Use '*/*' applies to all types.
# key - Symbol metadata key
# initial - Initial memo to pass to the reduce funciton (default: nil)
# block - Proc accepting the memo accumulator and current value
#
# Returns nothing.
def register_bundle_metadata_reducer(mime_type, key, *args, &block)
case args.size
when 0
reducer = block
when 1
if block_given?
initial = args[0]
reducer = block
else
initial = nil
reducer = args[0].to_proc
end
when 2
initial = args[0]
reducer = args[1].to_proc
else
raise ArgumentError, "wrong number of arguments (#{args.size} for 0..2)"
end
self.config = hash_reassoc(config, :bundle_reducers, mime_type) do |reducers|
reducers.merge(key => [initial, reducer])
end
end
protected
def resolve_processors_cache_key_uri(uri)
params = parse_uri_query_params(uri[11..-1])
params[:engine_extnames] = params[:engines] ? params[:engines].split(',') : []
processors = processors_for(params[:type], params[:file_type], params[:engine_extnames], params[:pipeline])
processors_cache_keys(processors)
end
def build_processors_uri(type, file_type, engine_extnames, pipeline)
engines = engine_extnames.join(',') if engine_extnames.any?
query = encode_uri_query_params(
type: type,
file_type: file_type,
engines: engines,
pipeline: pipeline
)
"processors:#{query}"
end
def processors_for(type, file_type, engine_extnames, pipeline)
pipeline ||= :default
config[:pipelines][pipeline.to_sym].call(self, type, file_type, engine_extnames)
end
def default_processors_for(type, file_type, engine_extnames)
bundled_processors = config[:bundle_processors][type]
if bundled_processors.any?
bundled_processors
else
self_processors_for(type, file_type, engine_extnames)
end
end
def self_processors_for(type, file_type, engine_extnames)
processors = []
processors.concat config[:postprocessors][type]
if type != file_type && processor = config[:transformers][file_type][type]
processors << processor
end
processors.concat engine_extnames.map { |ext| engines[ext] }
processors.concat config[:preprocessors][file_type]
if processors.any? || mime_type_charset_detecter(type)
processors << FileReader
end
processors
end
private
def register_config_processor(type, mime_type, klass, proc = nil, &block)
proc ||= block
processor = wrap_processor(klass, proc)
self.config = hash_reassoc(config, type, mime_type) do |processors|
processors.unshift(processor)
processors
end
compute_transformers!
end
def unregister_config_processor(type, mime_type, klass)
if klass.is_a?(String) || klass.is_a?(Symbol)
klass = config[type][mime_type].detect do |cls|
cls.respond_to?(:name) && cls.name == "Sprockets::LegacyProcProcessor (#{klass})"
end
end
self.config = hash_reassoc(config, type, mime_type) do |processors|
processors.delete(klass)
processors
end
compute_transformers!
end
def deprecate_legacy_processor_interface(interface)
msg = "You are using a deprecated processor interface #{ interface.inspect }.\n" +
"Please update your processor interface:\n" +
"https://github.com/rails/sprockets/blob/master/guides/extending_sprockets.md#supporting-all-versions-of-sprockets-in-processors\n"
Deprecation.new([caller[3]]).warn msg
end
def wrap_processor(klass, proc)
if !proc
if klass.respond_to?(:call)
klass
else
deprecate_legacy_processor_interface(klass)
LegacyTiltProcessor.new(klass)
end
elsif proc.respond_to?(:arity) && proc.arity == 2
deprecate_legacy_processor_interface(proc)
LegacyProcProcessor.new(klass.to_s, proc)
else
proc
end
end
end
end
sprockets-3.7.2/lib/sprockets/processor_utils.rb 0000664 0000000 0000000 00000013310 13312220220 0022070 0 ustar 00root root 0000000 0000000 require 'set'
module Sprockets
# Functional utilities for dealing with Processor functions.
#
# A Processor is a general function that my modify or transform an asset as
# part of the pipeline. CoffeeScript to JavaScript conversion, Minification
# or Concatenation are all implemented as seperate Processor steps.
#
# Processors maybe any object that responds to call. So procs or a class that
# defines a self.call method.
#
# For ergonomics, processors may return a number of shorthand values.
# Unfortunately, this means that processors can not compose via ordinary
# function composition. The composition helpers here can help.
module ProcessorUtils
extend self
# Public: Compose processors in right to left order.
#
# processors - Array of processors callables
#
# Returns a composed Proc.
def compose_processors(*processors)
context = self
if processors.length == 1
obj = method(:call_processor).to_proc.curry[processors.first]
else
obj = method(:call_processors).to_proc.curry[processors]
end
metaclass = (class << obj; self; end)
metaclass.send(:define_method, :cache_key) do
context.processors_cache_keys(processors)
end
obj
end
# Public: Invoke list of processors in right to left order.
#
# The right to left order processing mirrors standard function composition.
# Think about:
#
# bundle.call(uglify.call(coffee.call(input)))
#
# processors - Array of processor callables
# input - Hash of input data to pass to each processor
#
# Returns a Hash with :data and other processor metadata key/values.
def call_processors(processors, input)
data = input[:data] || ""
metadata = (input[:metadata] || {}).dup
processors.reverse_each do |processor|
result = call_processor(processor, input.merge(data: data, metadata: metadata))
data = result.delete(:data)
metadata.merge!(result)
end
metadata.merge(data: data)
end
# Public: Invoke processor.
#
# processor - Processor callables
# input - Hash of input data to pass to processor
#
# Returns a Hash with :data and other processor metadata key/values.
def call_processor(processor, input)
metadata = (input[:metadata] || {}).dup
metadata[:data] = input[:data]
case result = processor.call({data: "", metadata: {}}.merge(input))
when NilClass
metadata
when Hash
metadata.merge(result)
when String
metadata.merge(data: result)
else
raise TypeError, "invalid processor return type: #{result.class}"
end
end
# Internal: Get processor defined cached key.
#
# processor - Processor function
#
# Returns JSON serializable key or nil.
def processor_cache_key(processor)
processor.cache_key if processor.respond_to?(:cache_key)
end
# Internal: Get combined cache keys for set of processors.
#
# processors - Array of processor functions
#
# Returns Array of JSON serializable keys.
def processors_cache_keys(processors)
processors.map { |processor| processor_cache_key(processor) }
end
# Internal: Set of all "simple" value types allowed to be returned in
# processor metadata.
VALID_METADATA_VALUE_TYPES = Set.new([
String,
Symbol,
TrueClass,
FalseClass,
NilClass
] + (0.class == Integer ? [Integer] : [Bignum, Fixnum])).freeze
# Internal: Set of all nested compound metadata types that can nest values.
VALID_METADATA_COMPOUND_TYPES = Set.new([
Array,
Hash,
Set
]).freeze
# Internal: Hash of all "simple" value types allowed to be returned in
# processor metadata.
VALID_METADATA_VALUE_TYPES_HASH = VALID_METADATA_VALUE_TYPES.each_with_object({}) do |type, hash|
hash[type] = true
end.freeze
# Internal: Hash of all nested compound metadata types that can nest values.
VALID_METADATA_COMPOUND_TYPES_HASH = VALID_METADATA_COMPOUND_TYPES.each_with_object({}) do |type, hash|
hash[type] = true
end.freeze
# Internal: Set of all allowed metadata types.
VALID_METADATA_TYPES = (VALID_METADATA_VALUE_TYPES + VALID_METADATA_COMPOUND_TYPES).freeze
# Internal: Validate returned result of calling a processor pipeline and
# raise a friendly user error message.
#
# result - Metadata Hash returned from call_processors
#
# Returns result or raises a TypeError.
def validate_processor_result!(result)
if !result.instance_of?(Hash)
raise TypeError, "processor metadata result was expected to be a Hash, but was #{result.class}"
end
if !result[:data].instance_of?(String)
raise TypeError, "processor :data was expected to be a String, but as #{result[:data].class}"
end
result.each do |key, value|
if !key.instance_of?(Symbol)
raise TypeError, "processor metadata[#{key.inspect}] expected to be a Symbol"
end
if !valid_processor_metadata_value?(value)
raise TypeError, "processor metadata[:#{key}] returned a complex type: #{value.inspect}\n" +
"Only #{VALID_METADATA_TYPES.to_a.join(", ")} maybe used."
end
end
result
end
# Internal: Validate object is in validate metadata whitelist.
#
# value - Any Object
#
# Returns true if class is in whitelist otherwise false.
def valid_processor_metadata_value?(value)
if VALID_METADATA_VALUE_TYPES_HASH[value.class]
true
elsif VALID_METADATA_COMPOUND_TYPES_HASH[value.class]
value.all? { |v| valid_processor_metadata_value?(v) }
else
false
end
end
end
end
sprockets-3.7.2/lib/sprockets/resolve.rb 0000664 0000000 0000000 00000015434 13312220220 0020321 0 ustar 00root root 0000000 0000000 require 'set'
require 'sprockets/http_utils'
require 'sprockets/path_dependency_utils'
require 'sprockets/uri_utils'
module Sprockets
module Resolve
include HTTPUtils, PathDependencyUtils, URIUtils
# Public: Find Asset URI for given a logical path by searching the
# environment's load paths.
#
# resolve("application.js")
# # => "file:///path/to/app/javascripts/application.js?type=application/javascript"
#
# An accept content type can be given if the logical path doesn't have a
# format extension.
#
# resolve("application", accept: "application/javascript")
# # => "file:///path/to/app/javascripts/application.coffee?type=application/javascript"
#
# The String Asset URI is returned or nil if no results are found.
def resolve(path, options = {})
path = path.to_s
paths = options[:load_paths] || config[:paths]
accept = options[:accept]
if valid_asset_uri?(path)
uri, deps = resolve_asset_uri(path)
elsif absolute_path?(path)
filename, type, deps = resolve_absolute_path(paths, path, accept)
elsif relative_path?(path)
filename, type, pipeline, deps = resolve_relative_path(paths, path, options[:base_path], accept)
else
filename, type, pipeline, deps = resolve_logical_path(paths, path, accept)
end
if filename
params = {}
params[:type] = type if type
params[:pipeline] = pipeline if pipeline
params[:pipeline] = options[:pipeline] if options[:pipeline]
uri = build_asset_uri(filename, params)
end
return uri, deps
end
# Public: Same as resolve() but raises a FileNotFound exception instead of
# nil if no assets are found.
def resolve!(path, options = {})
uri, deps = resolve(path, options.merge(compat: false))
unless uri
message = "couldn't find file '#{path}'"
if relative_path?(path) && options[:base_path]
load_path, _ = paths_split(config[:paths], options[:base_path])
message << " under '#{load_path}'"
end
message << " with type '#{options[:accept]}'" if options[:accept]
message << "\nChecked in these paths: \n #{ config[:paths].join("\n ") }"
raise FileNotFound, message
end
return uri, deps
end
protected
def resolve_asset_uri(uri)
filename, _ = parse_asset_uri(uri)
return uri, Set.new([build_file_digest_uri(filename)])
end
def resolve_absolute_path(paths, filename, accept)
deps = Set.new
filename = File.expand_path(filename)
# Ensure path is under load paths
return nil, nil, deps unless paths_split(paths, filename)
_, mime_type, _, _ = parse_path_extnames(filename)
type = resolve_transform_type(mime_type, accept)
return nil, nil, deps if accept && !type
return nil, nil, deps unless file?(filename)
deps << build_file_digest_uri(filename)
return filename, type, deps
end
def resolve_relative_path(paths, path, dirname, accept)
filename = File.expand_path(path, dirname)
load_path, _ = paths_split(paths, dirname)
if load_path && logical_path = split_subpath(load_path, filename)
resolve_logical_path([load_path], logical_path, accept)
else
return nil, nil, Set.new
end
end
def resolve_logical_path(paths, logical_path, accept)
logical_name, mime_type, _, pipeline = parse_path_extnames(logical_path)
parsed_accept = parse_accept_options(mime_type, accept)
transformed_accepts = expand_transform_accepts(parsed_accept)
filename, mime_type, deps = resolve_under_paths(paths, logical_name, transformed_accepts)
if filename
deps << build_file_digest_uri(filename)
type = resolve_transform_type(mime_type, parsed_accept)
return filename, type, pipeline, deps
else
return nil, nil, nil, deps
end
end
def resolve_under_paths(paths, logical_name, accepts)
all_deps = Set.new
return nil, nil, all_deps if accepts.empty?
logical_basename = File.basename(logical_name)
paths.each do |load_path|
candidates, deps = path_matches(load_path, logical_name, logical_basename)
all_deps.merge(deps)
candidate = find_best_q_match(accepts, candidates) do |c, matcher|
match_mime_type?(c[1] || "application/octet-stream", matcher)
end
return candidate + [all_deps] if candidate
end
return nil, nil, all_deps
end
def parse_accept_options(mime_type, types)
accepts = []
accepts += parse_q_values(types) if types
if mime_type
if accepts.empty? || accepts.any? { |accept, _| match_mime_type?(mime_type, accept) }
accepts = [[mime_type, 1.0]]
else
return []
end
end
if accepts.empty?
accepts << ['*/*', 1.0]
end
accepts
end
def path_matches(load_path, logical_name, logical_basename)
dirname = File.dirname(File.join(load_path, logical_name))
candidates = dirname_matches(dirname, logical_basename)
deps = file_digest_dependency_set(dirname)
result = resolve_alternates(load_path, logical_name)
result[0].each do |fn|
candidates << [fn, parse_path_extnames(fn)[1]]
end
deps.merge(result[1])
dirname = File.join(load_path, logical_name)
if directory? dirname
result = dirname_matches(dirname, "index")
candidates.concat(result)
end
deps.merge(file_digest_dependency_set(dirname))
return candidates.select { |fn, _| file?(fn) }, deps
end
def dirname_matches(dirname, basename)
candidates = []
entries = self.entries(dirname)
entries.each do |entry|
next unless File.basename(entry).start_with?(basename)
name, type, _, _ = parse_path_extnames(entry)
if basename == name
candidates << [File.join(dirname, entry), type]
end
end
candidates
end
def resolve_alternates(load_path, logical_name)
return [], Set.new
end
# Internal: Returns the name, mime type and `Array` of engine extensions.
#
# "foo.js.coffee.erb"
# # => ["foo", "application/javascript", [".coffee", ".erb"]]
#
def parse_path_extnames(path)
engines = []
extname, value = match_path_extname(path, extname_map)
if extname
path = path.chomp(extname)
type, engines, pipeline = value.values_at(:type, :engines, :pipeline)
end
return path, type, engines, pipeline
end
end
end
sprockets-3.7.2/lib/sprockets/sass_cache_store.rb 0000664 0000000 0000000 00000001551 13312220220 0022145 0 ustar 00root root 0000000 0000000 require 'sass'
module Sprockets
class SassProcessor
# Internal: Cache wrapper for Sprockets cache adapter.
class CacheStore < ::Sass::CacheStores::Base
VERSION = '1'
def initialize(cache, version)
@cache, @version = cache, "#{VERSION}/#{version}"
end
def _store(key, version, sha, contents)
@cache.set("#{@version}/#{version}/#{key}/#{sha}", contents, true)
end
def _retrieve(key, version, sha)
@cache.get("#{@version}/#{version}/#{key}/#{sha}", true)
end
def path_to(key)
key
end
end
end
# Deprecated: Use Sprockets::SassProcessor::CacheStore instead.
class SassCacheStore < SassProcessor::CacheStore
def initialize(*args)
Deprecation.new.warn "SassCacheStore is deprecated please use SassProcessor::CacheStore instead"
super
end
end
end
sprockets-3.7.2/lib/sprockets/sass_compressor.rb 0000664 0000000 0000000 00000002224 13312220220 0022060 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
require 'sprockets/digest_utils'
module Sprockets
# Public: Sass CSS minifier.
#
# To accept the default options
#
# environment.register_bundle_processor 'text/css',
# Sprockets::SassCompressor
#
# Or to pass options to the Sass::Engine class.
#
# environment.register_bundle_processor 'text/css',
# Sprockets::SassCompressor.new({ ... })
#
class SassCompressor
VERSION = '1'
# Public: Return singleton instance with default options.
#
# Returns SassCompressor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
def initialize(options = {})
@options = {
syntax: :scss,
cache: false,
read_cache: false,
style: :compressed
}.merge(options).freeze
@cache_key = "#{self.class.name}:#{Autoload::Sass::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
end
def call(input)
Autoload::Sass::Engine.new(input[:data], @options).render
end
end
end
sprockets-3.7.2/lib/sprockets/sass_functions.rb 0000664 0000000 0000000 00000000132 13312220220 0021670 0 ustar 00root root 0000000 0000000 # Deprecated: Require sprockets/sass_processor instead
require 'sprockets/sass_processor'
sprockets-3.7.2/lib/sprockets/sass_importer.rb 0000664 0000000 0000000 00000000132 13312220220 0021521 0 ustar 00root root 0000000 0000000 # Deprecated: Require sprockets/sass_processor instead
require 'sprockets/sass_processor'
sprockets-3.7.2/lib/sprockets/sass_processor.rb 0000664 0000000 0000000 00000017624 13312220220 0021715 0 ustar 00root root 0000000 0000000 require 'rack/utils'
require 'sprockets/autoload'
require 'uri'
module Sprockets
# Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem.
#
# For more infomation see:
#
# https://github.com/sass/sass
# https://github.com/rails/sass-rails
#
class SassProcessor
autoload :CacheStore, 'sprockets/sass_cache_store'
# Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
# may override it.
def self.syntax
:sass
end
# Public: Return singleton instance with default options.
#
# Returns SassProcessor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
# Public: Initialize template with custom options.
#
# options - Hash
# cache_version - String custom cache version. Used to force a cache
# change after code changes are made to Sass Functions.
#
def initialize(options = {}, &block)
@cache_version = options[:cache_version]
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
@functions = Module.new do
include Functions
include options[:functions] if options[:functions]
class_eval(&block) if block_given?
end
end
def call(input)
context = input[:environment].context_class.new(input)
options = {
filename: input[:filename],
syntax: self.class.syntax,
cache_store: build_cache_store(input, @cache_version),
load_paths: input[:environment].paths,
sprockets: {
context: context,
environment: input[:environment],
dependencies: context.metadata[:dependencies]
}
}
engine = Autoload::Sass::Engine.new(input[:data], options)
css = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
engine.render
end
# Track all imported files
sass_dependencies = Set.new([input[:filename]])
engine.dependencies.map do |dependency|
sass_dependencies << dependency.options[:filename]
context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename])
end
context.metadata.merge(data: css, sass_dependencies: sass_dependencies)
end
# Public: Build the cache store to be used by the Sass engine.
#
# input - the input hash.
# version - the cache version.
#
# Override this method if you need to use a different cache than the
# Sprockets cache.
def build_cache_store(input, version)
CacheStore.new(input[:cache], version)
end
private :build_cache_store
# Public: Functions injected into Sass context during Sprockets evaluation.
#
# This module may be extended to add global functionality to all Sprockets
# Sass environments. Though, scoping your functions to just your environment
# is preferred.
#
# module Sprockets::SassProcessor::Functions
# def asset_path(path, options = {})
# end
# end
#
module Functions
# Public: Generate a url for asset path.
#
# Default implementation is deprecated. Currently defaults to
# Context#asset_path.
#
# Will raise NotImplementedError in the future. Users should provide their
# own base implementation.
#
# Returns a Sass::Script::String.
def asset_path(path, options = {})
path = path.value
path, _, query, fragment = URI.split(path)[5..8]
path = sprockets_context.asset_path(path, options)
query = "?#{query}" if query
fragment = "##{fragment}" if fragment
Autoload::Sass::Script::String.new("#{path}#{query}#{fragment}", :string)
end
# Public: Generate a asset url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def asset_url(path, options = {})
Autoload::Sass::Script::String.new("url(#{asset_path(path, options).value})")
end
# Public: Generate url for image path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def image_path(path)
asset_path(path, type: :image)
end
# Public: Generate a image url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def image_url(path)
asset_url(path, type: :image)
end
# Public: Generate url for video path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def video_path(path)
asset_path(path, type: :video)
end
# Public: Generate a video url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def video_url(path)
asset_url(path, type: :video)
end
# Public: Generate url for audio path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def audio_path(path)
asset_path(path, type: :audio)
end
# Public: Generate a audio url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def audio_url(path)
asset_url(path, type: :audio)
end
# Public: Generate url for font path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def font_path(path)
asset_path(path, type: :font)
end
# Public: Generate a font url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def font_url(path)
asset_url(path, type: :font)
end
# Public: Generate url for javascript path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def javascript_path(path)
asset_path(path, type: :javascript)
end
# Public: Generate a javascript url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def javascript_url(path)
asset_url(path, type: :javascript)
end
# Public: Generate url for stylesheet path.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def stylesheet_path(path)
asset_path(path, type: :stylesheet)
end
# Public: Generate a stylesheet url() link.
#
# path - Sass::Script::String URL path
#
# Returns a Sass::Script::String.
def stylesheet_url(path)
asset_url(path, type: :stylesheet)
end
# Public: Generate a data URI for asset path.
#
# path - Sass::Script::String logical asset path
#
# Returns a Sass::Script::String.
def asset_data_url(path)
url = sprockets_context.asset_data_uri(path.value)
Autoload::Sass::Script::String.new("url(" + url + ")")
end
protected
# Public: The Environment.
#
# Returns Sprockets::Environment.
def sprockets_environment
options[:sprockets][:environment]
end
# Public: Mutatable set of dependencies.
#
# Returns a Set.
def sprockets_dependencies
options[:sprockets][:dependencies]
end
# Deprecated: Get the Context instance. Use APIs on
# sprockets_environment or sprockets_dependencies directly.
#
# Returns a Context instance.
def sprockets_context
options[:sprockets][:context]
end
end
end
class ScssProcessor < SassProcessor
def self.syntax
:scss
end
end
# Deprecated: Use Sprockets::SassProcessor::Functions instead.
SassFunctions = SassProcessor::Functions
end
sprockets-3.7.2/lib/sprockets/sass_template.rb 0000664 0000000 0000000 00000000672 13312220220 0021504 0 ustar 00root root 0000000 0000000 require 'sprockets/sass_processor'
module Sprockets
# Deprecated
class SassTemplate < SassProcessor
def self.call(*args)
Deprecation.new.warn "SassTemplate is deprecated please use SassProcessor instead"
super
end
end
# Deprecated
class ScssTemplate < ScssProcessor
def self.call(*args)
Deprecation.new.warn "ScssTemplate is deprecated please use ScssProcessor instead"
super
end
end
end
sprockets-3.7.2/lib/sprockets/server.rb 0000664 0000000 0000000 00000021520 13312220220 0020141 0 ustar 00root root 0000000 0000000 require 'time'
require 'rack/utils'
module Sprockets
# `Server` is a concern mixed into `Environment` and
# `CachedEnvironment` that provides a Rack compatible `call`
# interface and url generation helpers.
module Server
# `call` implements the Rack 1.x specification which accepts an
# `env` Hash and returns a three item tuple with the status code,
# headers, and body.
#
# Mapping your environment at a url prefix will serve all assets
# in the path.
#
# map "/assets" do
# run Sprockets::Environment.new
# end
#
# A request for `"/assets/foo/bar.js"` will search your
# environment for `"foo/bar.js"`.
def call(env)
start_time = Time.now.to_f
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
if !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
return method_not_allowed_response
end
msg = "Served asset #{env['PATH_INFO']} -"
# Extract the path from everything after the leading slash
path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
# Strip fingerprint
if fingerprint = path_fingerprint(path)
path = path.sub("-#{fingerprint}", '')
end
# URLs containing a `".."` are rejected for security reasons.
if forbidden_request?(path)
return forbidden_response(env)
end
# Look up the asset.
options = {}
options[:pipeline] = :self if body_only?(env)
asset = find_asset(path, options)
# 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests.
# 3.x/4.x prefers strong validation of fingerprint to body contents, but
# 2.x just ignored it.
if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self"
fingerprint = nil
end
if fingerprint
if_match = fingerprint
elsif env['HTTP_IF_MATCH']
if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
end
if env['HTTP_IF_NONE_MATCH']
if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
end
if asset.nil?
status = :not_found
elsif fingerprint && asset.etag != fingerprint
status = :not_found
elsif if_match && asset.etag != if_match
status = :precondition_failed
elsif if_none_match && asset.etag == if_none_match
status = :not_modified
else
status = :ok
end
case status
when :ok
logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
ok_response(asset, env)
when :not_modified
logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
not_modified_response(env, if_none_match)
when :not_found
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
not_found_response(env)
when :precondition_failed
logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
precondition_failed_response(env)
end
rescue Exception => e
logger.error "Error compiling asset #{path}:"
logger.error "#{e.class.name}: #{e.message}"
case File.extname(path)
when ".js"
# Re-throw JavaScript asset exceptions to the browser
logger.info "#{msg} 500 Internal Server Error\n\n"
return javascript_exception_response(e)
when ".css"
# Display CSS asset exceptions in the browser
logger.info "#{msg} 500 Internal Server Error\n\n"
return css_exception_response(e)
else
raise
end
end
private
def forbidden_request?(path)
# Prevent access to files elsewhere on the file system
#
# http://example.org/assets/../../../etc/passwd
#
path.include?("..") || absolute_path?(path) || path.include?("://")
end
def head_request?(env)
env['REQUEST_METHOD'] == 'HEAD'
end
# Returns a 200 OK response tuple
def ok_response(asset, env)
if head_request?(env)
[ 200, headers(env, asset, 0), [] ]
else
[ 200, headers(env, asset, asset.length), asset ]
end
end
# Returns a 304 Not Modified response tuple
def not_modified_response(env, etag)
[ 304, cache_headers(env, etag), [] ]
end
# Returns a 403 Forbidden response tuple
def forbidden_response(env)
if head_request?(env)
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
else
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
end
end
# Returns a 404 Not Found response tuple
def not_found_response(env)
if head_request?(env)
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
else
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
end
end
def method_not_allowed_response
[ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
end
def precondition_failed_response(env)
if head_request?(env)
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
else
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
end
end
# Returns a JavaScript response that re-throws a Ruby exception
# in the browser
def javascript_exception_response(exception)
err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
body = "throw Error(#{err.inspect})"
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
end
# Returns a CSS response that hides all elements on the page and
# displays the exception
def css_exception_response(exception)
message = "\n#{exception.class.name}: #{exception.message}"
backtrace = "\n #{exception.backtrace.first}"
body = <<-CSS
html {
padding: 18px 36px;
}
head {
display: block;
}
body {
margin: 0;
padding: 0;
}
body > * {
display: none !important;
}
head:after, body:before, body:after {
display: block !important;
}
head:after {
font-family: sans-serif;
font-size: large;
font-weight: bold;
content: "Error compiling CSS asset";
}
body:before, body:after {
font-family: monospace;
white-space: pre-wrap;
}
body:before {
font-weight: bold;
content: "#{escape_css_content(message)}";
}
body:after {
content: "#{escape_css_content(backtrace)}";
}
CSS
[ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
end
# Escape special characters for use inside a CSS content("...") string
def escape_css_content(content)
content.
gsub('\\', '\\\\005c ').
gsub("\n", '\\\\000a ').
gsub('"', '\\\\0022 ').
gsub('/', '\\\\002f ')
end
# Test if `?body=1` or `body=true` query param is set
def body_only?(env)
env["QUERY_STRING"].to_s =~ /body=(1|t)/
end
def cache_headers(env, etag)
headers = {}
# Set caching headers
headers["Cache-Control"] = "public"
headers["ETag"] = %("#{etag}")
# If the request url contains a fingerprint, set a long
# expires on the response
if path_fingerprint(env["PATH_INFO"])
headers["Cache-Control"] << ", max-age=31536000"
# Otherwise set `must-revalidate` since the asset could be modified.
else
headers["Cache-Control"] << ", must-revalidate"
headers["Vary"] = "Accept-Encoding"
end
headers
end
def headers(env, asset, length)
headers = {}
# Set content length header
headers["Content-Length"] = length.to_s
# Set content type header
if type = asset.content_type
# Set charset param for text/* mime types
if type.start_with?("text/") && asset.charset
type += "; charset=#{asset.charset}"
end
headers["Content-Type"] = type
end
headers.merge(cache_headers(env, asset.etag))
end
# Gets ETag fingerprint.
#
# "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
# # => "0aa2105d29558f3eb790d411d7d8fb66"
#
def path_fingerprint(path)
path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
end
end
end
sprockets-3.7.2/lib/sprockets/transformers.rb 0000664 0000000 0000000 00000010552 13312220220 0021363 0 ustar 00root root 0000000 0000000 require 'sprockets/http_utils'
require 'sprockets/processor_utils'
require 'sprockets/utils'
module Sprockets
module Transformers
include HTTPUtils, ProcessorUtils, Utils
# Public: Two level mapping of a source mime type to a target mime type.
#
# environment.transformers
# # => { 'text/coffeescript' => {
# 'application/javascript' => ConvertCoffeeScriptToJavaScript
# }
# }
#
def transformers
config[:transformers]
end
# Public: Register a transformer from and to a mime type.
#
# from - String mime type
# to - String mime type
# proc - Callable block that accepts an input Hash.
#
# Examples
#
# register_transformer 'text/coffeescript', 'application/javascript',
# ConvertCoffeeScriptToJavaScript
#
# register_transformer 'image/svg+xml', 'image/png', ConvertSvgToPng
#
# Returns nothing.
def register_transformer(from, to, proc)
self.config = hash_reassoc(config, :registered_transformers, from) do |transformers|
transformers.merge(to => proc)
end
compute_transformers!
end
# Internal: Resolve target mime type that the source type should be
# transformed to.
#
# type - String from mime type
# accept - String accept type list (default: '*/*')
#
# Examples
#
# resolve_transform_type('text/plain', 'text/plain')
# # => 'text/plain'
#
# resolve_transform_type('image/svg+xml', 'image/png, image/*')
# # => 'image/png'
#
# resolve_transform_type('text/css', 'image/png')
# # => nil
#
# Returns String mime type or nil is no type satisfied the accept value.
def resolve_transform_type(type, accept)
find_best_mime_type_match(accept || '*/*', [type].compact + config[:transformers][type].keys)
end
# Internal: Expand accept type list to include possible transformed types.
#
# parsed_accepts - Array of accept q values
#
# Examples
#
# expand_transform_accepts([['application/javascript', 1.0]])
# # => [['application/javascript', 1.0], ['text/coffeescript', 0.8]]
#
# Returns an expanded Array of q values.
def expand_transform_accepts(parsed_accepts)
accepts = []
parsed_accepts.each do |(type, q)|
accepts.push([type, q])
config[:inverted_transformers][type].each do |subtype|
accepts.push([subtype, q * 0.8])
end
end
accepts
end
# Internal: Compose multiple transformer steps into a single processor
# function.
#
# transformers - Two level Hash of a source mime type to a target mime type
# types - Array of mime type steps
#
# Returns Processor.
def compose_transformers(transformers, types)
if types.length < 2
raise ArgumentError, "too few transform types: #{types.inspect}"
end
i = 0
processors = []
loop do
src = types[i]
dst = types[i+1]
break unless src && dst
unless processor = transformers[src][dst]
raise ArgumentError, "missing transformer for type: #{src} to #{dst}"
end
processors.concat config[:postprocessors][src]
processors << processor
processors.concat config[:preprocessors][dst]
i += 1
end
if processors.size > 1
compose_processors(*processors.reverse)
elsif processors.size == 1
processors.first
end
end
private
def compute_transformers!
registered_transformers = self.config[:registered_transformers]
transformers = Hash.new { {} }
inverted_transformers = Hash.new { Set.new }
registered_transformers.keys.flat_map do |key|
dfs_paths([key]) { |k| registered_transformers[k].keys }
end.each do |types|
src, dst = types.first, types.last
processor = compose_transformers(registered_transformers, types)
transformers[src] = {} unless transformers.key?(src)
transformers[src][dst] = processor
inverted_transformers[dst] = Set.new unless inverted_transformers.key?(dst)
inverted_transformers[dst] << src
end
self.config = hash_reassoc(config, :transformers) { transformers }
self.config = hash_reassoc(config, :inverted_transformers) { inverted_transformers }
end
end
end
sprockets-3.7.2/lib/sprockets/uglifier_compressor.rb 0000664 0000000 0000000 00000002557 13312220220 0022726 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
require 'sprockets/digest_utils'
module Sprockets
# Public: Uglifier/Uglify compressor.
#
# To accept the default options
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::UglifierCompressor
#
# Or to pass options to the Uglifier class.
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::UglifierCompressor.new(comments: :copyright)
#
class UglifierCompressor
VERSION = '1'
# Public: Return singleton instance with default options.
#
# Returns UglifierCompressor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
def initialize(options = {})
# Feature detect Uglifier 2.0 option support
if Autoload::Uglifier::DEFAULTS[:copyright]
# Uglifier < 2.x
options[:copyright] ||= false
else
# Uglifier >= 2.x
options[:comments] ||= :none
end
@options = options
@cache_key = "#{self.class.name}:#{Autoload::Uglifier::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
end
def call(input)
@uglifier ||= Autoload::Uglifier.new(@options)
@uglifier.compile(input[:data])
end
end
end
sprockets-3.7.2/lib/sprockets/unloaded_asset.rb 0000664 0000000 0000000 00000012124 13312220220 0021625 0 ustar 00root root 0000000 0000000 require 'sprockets/uri_utils'
require 'sprockets/uri_tar'
module Sprockets
# Internal: Used to parse and store the URI to an unloaded asset
# Generates keys used to store and retrieve items from cache
class UnloadedAsset
# Internal: Initialize object for generating cache keys
#
# uri - A String containing complete URI to a file including scheme
# and full path such as
# "file:///Path/app/assets/js/app.js?type=application/javascript"
# env - The current "environment" that assets are being loaded into.
# We need it so we know where the +root+ (directory where sprockets
# is being invoked). We also need for the `file_digest` method,
# since, for some strange reason, memoization is provided by
# overriding methods such as `stat` in the `PathUtils` module.
#
# Returns UnloadedAsset.
def initialize(uri, env)
@uri = uri.to_s
@env = env
@compressed_path = URITar.new(uri, env).compressed_path
@params = nil # lazy loaded
@filename = nil # lazy loaded
end
attr_reader :compressed_path, :uri
# Internal: Full file path without schema
#
# This returns a string containing the full path to the asset without the schema.
# Information is loaded lazilly since we want `UnloadedAsset.new(dep, self).relative_path`
# to be fast. Calling this method the first time allocates an array and a hash.
#
# Example
#
# If the URI is `file:///Full/path/app/assets/javascripts/application.js"` then the
# filename would be `"/Full/path/app/assets/javascripts/application.js"`
#
# Returns a String.
def filename
unless @filename
load_file_params
end
@filename
end
# Internal: Hash of param values
#
# This information is generated and used internally by sprockets.
# Known keys include `:type` which store the asset's mime-type, `:id` which is a fully resolved
# digest for the asset (includes dependency digest as opposed to a digest of only file contents)
# and `:pipeline`. Hash may be empty.
#
# Example
#
# If the URI is `file:///Full/path/app/assets/javascripts/application.js"type=application/javascript`
# Then the params would be `{type: "application/javascript"}`
#
# Returns a Hash.
def params
unless @params
load_file_params
end
@params
end
# Internal: Key of asset
#
# Used to retrieve an asset from the cache based on "compressed" path to asset.
# A "compressed" path can either be relative to the root of the project or an
# absolute path.
#
# Returns a String.
def asset_key
"asset-uri:#{compressed_path}"
end
# Public: Dependency History key
#
# Used to retrieve an array of "histories" each of which contain a set of stored dependencies
# for a given asset path and filename digest.
#
# A dependency can refer to either an asset i.e. index.js
# may rely on jquery.js (so jquery.js is a dependency), or other factors that may affect
# compilation, such as the VERSION of sprockets (i.e. the environment) and what "processors"
# are used.
#
# For example a history array with one Set of dependencies may look like:
#
# [["environment-version", "environment-paths", "processors:type=text/css&file_type=text/css",
# "file-digest:///Full/path/app/assets/stylesheets/application.css",
# "processors:type=text/css&file_type=text/css&pipeline=self",
# "file-digest:///Full/path/app/assets/stylesheets"]]
#
# This method of asset lookup is used to ensure that none of the dependencies have been modified
# since last lookup. If one of them has, the key will be different and a new entry must be stored.
#
# URI depndencies are later converted to "compressed" paths
#
# Returns a String.
def dependency_history_key
"asset-uri-cache-dependencies:#{compressed_path}:#{ @env.file_digest(filename) }"
end
# Internal: Digest key
#
# Used to retrieve a string containing the "compressed" path to an asset based on
# a digest. The digest is generated from dependencies stored via information stored in
# the `dependency_history_key` after each of the "dependencies" is "resolved" for example
# "environment-version" may be resolved to "environment-1.0-3.2.0" for version "3.2.0" of sprockets
#
# Returns a String.
def digest_key(digest)
"asset-uri-digest:#{compressed_path}:#{digest}"
end
# Internal: File digest key
#
# The digest for a given file won't change if the path and the stat time hasn't changed
# We can save time by not re-computing this information and storing it in the cache
#
# Returns a String.
def file_digest_key(stat)
"file_digest:#{compressed_path}:#{stat}"
end
private
# Internal: Parses uri into filename and params hash
#
# Returns Array with filename and params hash
def load_file_params
@filename, @params = URIUtils.parse_asset_uri(uri)
end
end
end
sprockets-3.7.2/lib/sprockets/uri_tar.rb 0000664 0000000 0000000 00000005243 13312220220 0020304 0 ustar 00root root 0000000 0000000 require 'sprockets/path_utils'
module Sprockets
# Internal: used to "expand" and "compress" values for storage
class URITar
attr_reader :scheme, :root, :path
# Internal: Initialize object for compression or expansion
#
# uri - A String containing URI that may or may not contain the scheme
# env - The current "environment" that assets are being loaded into.
def initialize(uri, env)
@root = env.root
@env = env
uri = uri.to_s
if uri.include?("://".freeze)
@scheme, _, @path = uri.partition("://".freeze)
@scheme << "://".freeze
else
@scheme = "".freeze
@path = uri
end
end
# Internal: Converts full uri to a "compressed" uri
#
# If a uri is inside of an environment's root it will
# be shortened to be a relative path.
#
# If a uri is outside of the environment's root the original
# uri will be returned.
#
# Returns String
def compress
scheme + compressed_path
end
# Internal: Tells us if we are using an absolute path
#
# Nix* systems start with a `/` like /Users/schneems.
# Windows systems start with a drive letter than colon and slash
# like C:/Schneems.
def absolute_path?
PathUtils.absolute_path?(path)
end
# Internal: Convert a "compressed" uri to an absolute path
#
# If a uri is inside of the environment's root it will not
# start with a slash for example:
#
# file://this/is/a/relative/path
#
# If a uri is outside the root, it will start with a slash:
#
# file:///This/is/an/absolute/path
#
# Returns String
def expand
if absolute_path?
# Stored path was absolute, don't add root
scheme + path
else
if scheme.empty?
File.join(root, path)
else
# We always want to return an absolute uri,
# make sure the path starts with a slash.
scheme + File.join("/".freeze, root, path)
end
end
end
# Internal: Returns "compressed" path
#
# If the input uri is relative to the environment root
# it will return a path relative to the environment root.
# Otherwise an absolute path will be returned.
#
# Only path information is returned, and not scheme.
#
# Returns String
def compressed_path
# windows
if !@root.start_with?("/".freeze) && path.start_with?("/".freeze)
consistent_root = "/".freeze + @root
else
consistent_root = @root
end
if compressed_path = PathUtils.split_subpath(consistent_root, path)
compressed_path
else
path
end
end
end
end
sprockets-3.7.2/lib/sprockets/uri_utils.rb 0000664 0000000 0000000 00000012166 13312220220 0020660 0 ustar 00root root 0000000 0000000 require 'uri'
module Sprockets
# Internal: Asset URI related parsing utilities. Mixed into Environment.
#
# An Asset URI identifies the compiled Asset result. It shares the file:
# scheme and requires an absolute path.
#
# Other query parameters
#
# type - String output content type. Otherwise assumed from file extension.
# This maybe different than the extension if the asset is transformed
# from one content type to another. For an example .coffee -> .js.
#
# id - Unique fingerprint of the entire asset and all its metadata. Assets
# will only have the same id if they serialize to an identical value.
#
# pipeline - String name of pipeline.
#
module URIUtils
extend self
# Internal: Parse URI into component parts.
#
# uri - String uri
#
# Returns Array of components.
def split_uri(uri)
URI.split(uri)
end
# Internal: Join URI component parts into String.
#
# Returns String.
def join_uri(scheme, userinfo, host, port, registry, path, opaque, query, fragment)
URI::Generic.new(scheme, userinfo, host, port, registry, path, opaque, query, fragment).to_s
end
# Internal: Parse file: URI into component parts.
#
# uri - String uri
#
# Returns [scheme, host, path, query].
def split_file_uri(uri)
scheme, _, host, _, _, path, _, query, _ = URI.split(uri)
path = URI::Generic::DEFAULT_PARSER.unescape(path)
path.force_encoding(Encoding::UTF_8)
# Hack for parsing Windows "file:///C:/Users/IEUser" paths
path.gsub!(/^\/([a-zA-Z]:)/, '\1'.freeze)
[scheme, host, path, query]
end
# Internal: Join file: URI component parts into String.
#
# Returns String.
def join_file_uri(scheme, host, path, query)
str = "#{scheme}://"
str << host if host
path = "/#{path}" unless path.start_with?("/")
str << URI::Generic::DEFAULT_PARSER.escape(path)
str << "?#{query}" if query
str
end
# Internal: Check if String is a valid Asset URI.
#
# str - Possible String asset URI.
#
# Returns true or false.
def valid_asset_uri?(str)
# Quick prefix check before attempting a full parse
str.start_with?("file://") && parse_asset_uri(str) ? true : false
rescue URI::InvalidURIError
false
end
# Internal: Parse Asset URI.
#
# Examples
#
# parse("file:///tmp/js/application.coffee?type=application/javascript")
# # => "/tmp/js/application.coffee", {type: "application/javascript"}
#
# uri - String asset URI
#
# Returns String path and Hash of symbolized parameters.
def parse_asset_uri(uri)
scheme, _, path, query = split_file_uri(uri)
unless scheme == 'file'
raise URI::InvalidURIError, "expected file:// scheme: #{uri}"
end
return path, parse_uri_query_params(query)
end
# Internal: Build Asset URI.
#
# Examples
#
# build("/tmp/js/application.coffee", type: "application/javascript")
# # => "file:///tmp/js/application.coffee?type=application/javascript"
#
# path - String file path
# params - Hash of optional parameters
#
# Returns String URI.
def build_asset_uri(path, params = {})
join_file_uri("file", nil, path, encode_uri_query_params(params))
end
# Internal: Parse file-digest dependency URI.
#
# Examples
#
# parse("file-digest:/tmp/js/application.js")
# # => "/tmp/js/application.js"
#
# uri - String file-digest URI
#
# Returns String path.
def parse_file_digest_uri(uri)
scheme, _, path, _ = split_file_uri(uri)
unless scheme == 'file-digest'.freeze
raise URI::InvalidURIError, "expected file-digest scheme: #{uri}"
end
path
end
# Internal: Build file-digest dependency URI.
#
# Examples
#
# build("/tmp/js/application.js")
# # => "file-digest:/tmp/js/application.js"
#
# path - String file path
#
# Returns String URI.
def build_file_digest_uri(path)
join_file_uri('file-digest'.freeze, nil, path, nil)
end
# Internal: Serialize hash of params into query string.
#
# params - Hash of params to serialize
#
# Returns String query or nil if empty.
def encode_uri_query_params(params)
query = []
params.each do |key, value|
case value
when Integer
query << "#{key}=#{value}"
when String, Symbol
query << "#{key}=#{URI::Generic::DEFAULT_PARSER.escape(value.to_s)}"
when TrueClass
query << "#{key}"
when FalseClass, NilClass
else
raise TypeError, "unexpected type: #{value.class}"
end
end
"#{query.join('&')}" if query.any?
end
# Internal: Parse query string into hash of params
#
# query - String query string
#
# Return Hash of params.
def parse_uri_query_params(query)
query.to_s.split('&').reduce({}) do |h, p|
k, v = p.split('=', 2)
v = URI::Generic::DEFAULT_PARSER.unescape(v) if v
h[k.to_sym] = v || true
h
end
end
end
end
sprockets-3.7.2/lib/sprockets/utils.rb 0000664 0000000 0000000 00000013726 13312220220 0020004 0 ustar 00root root 0000000 0000000 require 'set'
module Sprockets
# Internal: Utils, we didn't know where else to put it! Functions may
# eventually be shuffled into more specific drawers.
module Utils
extend self
# Internal: Check if object can safely be .dup'd.
#
# Similar to ActiveSupport #duplicable? check.
#
# obj - Any Object
#
# Returns false if .dup would raise a TypeError, otherwise true.
def duplicable?(obj)
if RUBY_VERSION >= "2.4.0"
true
else
case obj
when NilClass, FalseClass, TrueClass, Symbol, Numeric
false
else
true
end
end
end
# Internal: Duplicate and store key/value on new frozen hash.
#
# Seperated for recursive calls, always use hash_reassoc(hash, *keys).
#
# hash - Hash
# key - Object key
#
# Returns Hash.
def hash_reassoc1(hash, key)
hash = hash.dup if hash.frozen?
old_value = hash[key]
old_value = old_value.dup if duplicable?(old_value)
new_value = yield old_value
new_value.freeze if duplicable?(new_value)
hash.store(key, new_value)
hash.freeze
end
# Internal: Duplicate and store key/value on new frozen hash.
#
# Similar to Hash#store for nested frozen hashes.
#
# hash - Hash
# key - Object keys. Use multiple keys for nested hashes.
# block - Receives current value at key.
#
# Examples
#
# config = {paths: ["/bin", "/sbin"]}.freeze
# new_config = hash_reassoc(config, :paths) do |paths|
# paths << "/usr/local/bin"
# end
#
# Returns duplicated frozen Hash.
def hash_reassoc(hash, *keys, &block)
if keys.size == 1
hash_reassoc1(hash, keys[0], &block)
else
hash_reassoc1(hash, keys[0]) do |value|
hash_reassoc(value, *keys[1..-1], &block)
end
end
end
# Internal: Check if string has a trailing semicolon.
#
# str - String
#
# Returns true or false.
def string_end_with_semicolon?(str)
i = str.size - 1
while i >= 0
c = str[i].ord
i -= 1
# Need to compare against the ordinals because the string can be UTF_8 or UTF_32LE encoded
# 0x0A == "\n"
# 0x20 == " "
# 0x09 == "\t"
# 0x3B == ";"
unless c == 0x0A || c == 0x20 || c == 0x09
return c === 0x3B
end
end
true
end
# Internal: Accumulate asset source to buffer and append a trailing
# semicolon if necessary.
#
# buf - String buffer to append to
# source - String source to append
#
# Returns buf String.
def concat_javascript_sources(buf, source)
if source.bytesize > 0
buf << source
# If the source contains non-ASCII characters, indexing on it becomes O(N).
# This will lead to O(N^2) performance in string_end_with_semicolon?, so we should use 32 bit encoding to make sure indexing stays O(1)
source = source.encode(Encoding::UTF_32LE) unless source.ascii_only?
if !string_end_with_semicolon?(source)
buf << ";\n"
elsif source[source.size - 1].ord != 0x0A
buf << "\n"
end
end
buf
end
# Internal: Prepends a leading "." to an extension if its missing.
#
# normalize_extension("js")
# # => ".js"
#
# normalize_extension(".css")
# # => ".css"
#
def normalize_extension(extension)
extension = extension.to_s
if extension[/^\./]
extension
else
".#{extension}"
end
end
# Internal: Feature detect if UnboundMethods can #bind to any Object or
# just Objects that share the same super class.
# Basically if RUBY_VERSION >= 2.
UNBOUND_METHODS_BIND_TO_ANY_OBJECT = begin
foo = Module.new { def bar; end }
foo.instance_method(:bar).bind(Object.new)
true
rescue TypeError
false
end
# Internal: Inject into target module for the duration of the block.
#
# mod - Module
#
# Returns result of block.
def module_include(base, mod)
old_methods = {}
mod.instance_methods.each do |sym|
old_methods[sym] = base.instance_method(sym) if base.method_defined?(sym)
end
unless UNBOUND_METHODS_BIND_TO_ANY_OBJECT
base.send(:include, mod) unless base < mod
end
mod.instance_methods.each do |sym|
method = mod.instance_method(sym)
base.send(:define_method, sym, method)
end
yield
ensure
mod.instance_methods.each do |sym|
base.send(:undef_method, sym) if base.method_defined?(sym)
end
old_methods.each do |sym, method|
base.send(:define_method, sym, method)
end
end
# Internal: Post-order Depth-First search algorithm.
#
# Used for resolving asset dependencies.
#
# initial - Initial Array of nodes to traverse.
# block -
# node - Current node to get children of
#
# Returns a Set of nodes.
def dfs(initial)
nodes, seen = Set.new, Set.new
stack = Array(initial).reverse
while node = stack.pop
if seen.include?(node)
nodes.add(node)
else
seen.add(node)
stack.push(node)
stack.concat(Array(yield node).reverse)
end
end
nodes
end
# Internal: Post-order Depth-First search algorithm that gathers all paths
# along the way.
#
# TODO: Rename function.
#
# path - Initial Array node path
# block -
# node - Current node to get children of
#
# Returns an Array of node Arrays.
def dfs_paths(path)
paths = []
stack, seen = [path], Set.new
while path = stack.pop
if !seen.include?(path.last)
seen.add(path.last)
paths << path if path.size > 1
Array(yield path.last).reverse_each do |node|
stack.push(path + [node])
end
end
end
paths
end
end
end
sprockets-3.7.2/lib/sprockets/utils/ 0000775 0000000 0000000 00000000000 13312220220 0017446 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/lib/sprockets/utils/gzip.rb 0000664 0000000 0000000 00000004251 13312220220 0020746 0 ustar 00root root 0000000 0000000 module Sprockets
module Utils
class Gzip
# Private: Generates a gzipped file based off of reference file.
def initialize(asset)
@content_type = asset.content_type
@source = asset.source
@charset = asset.charset
end
# What non-text mime types should we compress? This list comes from:
# https://www.fastly.com/blog/new-gzip-settings-and-deciding-what-compress
COMPRESSABLE_MIME_TYPES = {
"application/vnd.ms-fontobject" => true,
"application/x-font-opentype" => true,
"application/x-font-ttf" => true,
"image/x-icon" => true,
"image/svg+xml" => true
}
# Private: Returns whether or not an asset can be compressed.
#
# We want to compress any file that is text based.
# You do not want to compress binary
# files as they may already be compressed and running them
# through a compression algorithm would make them larger.
#
# Return Boolean.
def can_compress?(mime_types)
# The "charset" of a mime type is present if the value is
# encoded text. We can check this value to see if the asset
# can be compressed.
#
# We also check against our list of non-text compressible mime types
@charset || COMPRESSABLE_MIME_TYPES.include?(@content_type)
end
# Private: Opposite of `can_compress?`.
#
# Returns Boolean.
def cannot_compress?(mime_types)
!can_compress?(mime_types)
end
# Private: Generates a gzipped file based off of reference asset.
#
# Compresses the target asset's contents and puts it into a file with
# the same name plus a `.gz` extension in the same folder as the original.
# Does not modify the target asset.
#
# Returns nothing.
def compress(target)
mtime = PathUtils.stat(target).mtime
PathUtils.atomic_write("#{target}.gz") do |f|
gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION)
gz.mtime = mtime
gz.write(@source)
gz.close
File.utime(mtime, mtime, f.path)
end
nil
end
end
end
end
sprockets-3.7.2/lib/sprockets/version.rb 0000664 0000000 0000000 00000000051 13312220220 0020314 0 ustar 00root root 0000000 0000000 module Sprockets
VERSION = "3.7.2"
end
sprockets-3.7.2/lib/sprockets/yui_compressor.rb 0000664 0000000 0000000 00000002454 13312220220 0021722 0 ustar 00root root 0000000 0000000 require 'sprockets/autoload'
require 'sprockets/digest_utils'
module Sprockets
# Public: YUI compressor.
#
# To accept the default options
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::YUICompressor
#
# Or to pass options to the YUI::JavaScriptCompressor class.
#
# environment.register_bundle_processor 'application/javascript',
# Sprockets::YUICompressor.new(munge: true)
#
class YUICompressor
VERSION = '1'
# Public: Return singleton instance with default options.
#
# Returns YUICompressor object.
def self.instance
@instance ||= new
end
def self.call(input)
instance.call(input)
end
def self.cache_key
instance.cache_key
end
attr_reader :cache_key
def initialize(options = {})
@options = options
@cache_key = "#{self.class.name}:#{Autoload::YUI::Compressor::VERSION}:#{VERSION}:#{DigestUtils.digest(options)}".freeze
end
def call(input)
data = input[:data]
case input[:content_type]
when 'application/javascript'
Autoload::YUI::JavaScriptCompressor.new(@options).compress(data)
when 'text/css'
Autoload::YUI::CssCompressor.new(@options).compress(data)
else
data
end
end
end
end
sprockets-3.7.2/sprockets.gemspec 0000664 0000000 0000000 00000002756 13312220220 0017117 0 ustar 00root root 0000000 0000000 $:.unshift File.expand_path("../lib", __FILE__)
require "sprockets/version"
Gem::Specification.new do |s|
s.name = "sprockets"
s.version = Sprockets::VERSION
s.summary = "Rack-based asset packaging system"
s.description = "Sprockets is a Rack-based asset packaging system that concatenates and serves JavaScript, CoffeeScript, CSS, LESS, Sass, and SCSS."
s.license = "MIT"
s.files = Dir["README.md", "CHANGELOG.md", "LICENSE", "lib/**/*.rb"]
s.executables = ["sprockets"]
s.add_dependency "rack", "> 1", "< 3"
s.add_dependency "concurrent-ruby", "~> 1.0"
s.add_development_dependency "closure-compiler", "~> 1.1"
s.add_development_dependency "coffee-script-source", "~> 1.6"
s.add_development_dependency "coffee-script", "~> 2.2"
s.add_development_dependency "eco", "~> 1.0"
s.add_development_dependency "ejs", "~> 1.0"
s.add_development_dependency "execjs", "~> 2.0"
s.add_development_dependency "minitest", "~> 5.0"
s.add_development_dependency "nokogiri", "~> 1.3"
s.add_development_dependency "rack-test", "~> 0.6"
s.add_development_dependency "rake", "~> 10.0"
s.add_development_dependency "sass", "~> 3.1"
s.add_development_dependency "uglifier", "~> 2.3"
s.add_development_dependency "yui-compressor", "~> 0.12"
s.required_ruby_version = '>= 1.9.3'
s.authors = ["Sam Stephenson", "Joshua Peek"]
s.email = ["sstephenson@gmail.com", "josh@joshpeek.com"]
s.homepage = "https://github.com/rails/sprockets"
s.rubyforge_project = "sprockets"
end
sprockets-3.7.2/test/ 0000775 0000000 0000000 00000000000 13312220220 0014502 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/test/fixtures/ 0000775 0000000 0000000 00000000000 13312220220 0016353 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/test/fixtures/asset/ 0000775 0000000 0000000 00000000000 13312220220 0017472 5 ustar 00root root 0000000 0000000 sprockets-3.7.2/test/fixtures/asset/POW.png 0000664 0000000 0000000 00000123645 13312220220 0020660 0 ustar 00root root 0000000 0000000 PNG
IHDR , K9 tEXtSoftware Adobe ImageReadyqe<