torba 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/CONTRIBUTING.md +47 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/Rakefile +7 -0
- data/Readme.md +211 -0
- data/bin/torba +5 -0
- data/lib/torba/cli.rb +18 -0
- data/lib/torba/css_url_to_erb_asset_path.rb +51 -0
- data/lib/torba/import_list.rb +38 -0
- data/lib/torba/manifest.rb +65 -0
- data/lib/torba/package.rb +173 -0
- data/lib/torba/rails.rb +7 -0
- data/lib/torba/remote_sources/common.rb +33 -0
- data/lib/torba/remote_sources/github_release.rb +26 -0
- data/lib/torba/remote_sources/zip.rb +65 -0
- data/lib/torba/ui.rb +26 -0
- data/lib/torba/verify.rb +2 -0
- data/lib/torba.rb +80 -0
- data/test/Torbafile +5 -0
- data/test/acceptance_test.rb +26 -0
- data/test/css_url_to_erb_asset_path_test.rb +75 -0
- data/test/import_list_test.rb +39 -0
- data/test/manifest_test.rb +54 -0
- data/test/package/import_list_test.rb +143 -0
- data/test/remote_sources/github_release_test.rb +21 -0
- data/test/remote_sources/zip_test.rb +21 -0
- data/test/test_helper.rb +37 -0
- data/torba.gemspec +22 -0
- metadata +142 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c693882d1f37d1cdf2ea156a9d6fd90aaa42409e
|
4
|
+
data.tar.gz: d57f3ec19cf7274292a3bee7440f9102ddd2e3a6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3191c0a6b2610b78e148fe7f3cbf936f8fd3a82707e62d914c01378ea464bee951b3d6412530089792d10188fba62faa633b52a0ec81e72f1506f548c9418af5
|
7
|
+
data.tar.gz: aa2e6571108e54d3fc76d35e99e5b8d90f23e47ee70c8dd456d7e3aa6cfb06831d6ad5e821b72c0c746f3f80c4b463a0d19e0c2a61c7acb8968b731b0a1c929f
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
## Adding a feature
|
4
|
+
|
5
|
+
1. Open an issue and explain what you're planning to do. It is better to discuss new idea first,
|
6
|
+
rather when diving into code.
|
7
|
+
2. Add some tests.
|
8
|
+
3. Write the code.
|
9
|
+
4. Make sure all tests pass.
|
10
|
+
5. Commit with detailed explanation what you've done in a message.
|
11
|
+
6. Open pull request.
|
12
|
+
|
13
|
+
## Breaking/removing a feature
|
14
|
+
|
15
|
+
1. Add deprecation warning and fallback to old behaivour if possible.
|
16
|
+
2. Explain how to migrate to the new code in CHANGELOG.
|
17
|
+
3. Update/remove tests.
|
18
|
+
4. Update the code.
|
19
|
+
5. Make sure all tests pass.
|
20
|
+
6. Commit with detailed explanation what you've done in a message.
|
21
|
+
7. Open pull request.
|
22
|
+
|
23
|
+
## Fixing a bug
|
24
|
+
|
25
|
+
1. Add failing test.
|
26
|
+
2. Fix the bug.
|
27
|
+
3. Make sure all tests pass.
|
28
|
+
4. Commit with detailed explanation what you've done in a message.
|
29
|
+
5. Open pull request.
|
30
|
+
|
31
|
+
## Fixing a typo
|
32
|
+
|
33
|
+
1. Commit with a message that include "[ci skip]" remark.
|
34
|
+
2. Open pull request.
|
35
|
+
|
36
|
+
## Running the tests
|
37
|
+
|
38
|
+
```
|
39
|
+
rake test
|
40
|
+
```
|
41
|
+
|
42
|
+
## Working with documentation
|
43
|
+
|
44
|
+
```
|
45
|
+
yard server -dr
|
46
|
+
open http://localhost:8808
|
47
|
+
```
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Andrii Malyshko
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
data/Readme.md
ADDED
@@ -0,0 +1,211 @@
|
|
1
|
+
# Torba
|
2
|
+
|
3
|
+
[](https://travis-ci.org/torba-rb/torba)
|
4
|
+
[](https://rubygems.org/gems/torba)
|
5
|
+
|
6
|
+
**Torba** is a [Bower][bower]-less asset manager for [Sprockets][sprockets]. It makes a local copy
|
7
|
+
of a JS/CSS library and puts it under Sprockets' [load path][sprockets-load-path].
|
8
|
+
|
9
|
+
## Name origin
|
10
|
+
|
11
|
+
"Торба" [[tǒːrba][torba-pronounce]] in Ukrainian and "torba" in Polish, Turkic languages can mean
|
12
|
+
"duffel bag", "gunny sack" or, more generally, any flexible container.
|
13
|
+
|
14
|
+
## Status
|
15
|
+
|
16
|
+
Not tested in production.
|
17
|
+
|
18
|
+
## Documentation
|
19
|
+
|
20
|
+
http://rubydoc.info/github/torba-rb/torba/
|
21
|
+
|
22
|
+
## Why
|
23
|
+
|
24
|
+
De facto approach, i.e. wrapping JS and CSS libraries in a gem, requires from a
|
25
|
+
maintainer to constantly track changes in upstream repository. Even more, if the
|
26
|
+
maintainer stops using this gem itself, it will eventually become abandoned.
|
27
|
+
Many libraries still have no gem wrappers.
|
28
|
+
|
29
|
+
Among other alternatives:
|
30
|
+
|
31
|
+
* [rails-assets][rails-assets] project relies on Bower *and* it is quite complex,
|
32
|
+
* [bower-rails][bower-rails] project relies on Bower.
|
33
|
+
|
34
|
+
Problems with the Bower:
|
35
|
+
|
36
|
+
* it is not a part of Ruby ecosystem,
|
37
|
+
* frontend JS libraries are usually standalone (except for jQuery dependency), so there's
|
38
|
+
no need for complex Bundler-like solution with tree-dependency resolution,
|
39
|
+
* often we can't use optimistic version constraints, because JS community still doesn't
|
40
|
+
fully grasp the idea of [Semver][semver]. By specifying strict versions we use Bower
|
41
|
+
as a complex facade for functionality that could be done by curl.
|
42
|
+
|
43
|
+
## External dependencies
|
44
|
+
|
45
|
+
* curl
|
46
|
+
* unzip
|
47
|
+
|
48
|
+
## Design limitations
|
49
|
+
|
50
|
+
* Torba doesn't do any version dependency resolution, it's up to you to specify correct version of
|
51
|
+
each asset package,
|
52
|
+
* Torba doesn't do any builds, use remote sources with pre-built assets.
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile and run `bundle`:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
gem 'torba'
|
60
|
+
```
|
61
|
+
|
62
|
+
### Rails
|
63
|
+
|
64
|
+
in boot.rb
|
65
|
+
|
66
|
+
```diff
|
67
|
+
require 'bundler/setup' # Set up gems listed in the Gemfile.
|
68
|
+
+require 'torba/verify'
|
69
|
+
```
|
70
|
+
|
71
|
+
it config/application.rb
|
72
|
+
|
73
|
+
```diff
|
74
|
+
# Require the gems listed in Gemfile, including any gems
|
75
|
+
# you've limited to :test, :development, or :production.
|
76
|
+
Bundler.require(*Rails.groups)
|
77
|
+
+
|
78
|
+
+require 'torba/rails'
|
79
|
+
```
|
80
|
+
|
81
|
+
## Usage
|
82
|
+
|
83
|
+
1. Create Torbafile at the project root and commit it.
|
84
|
+
|
85
|
+
2. Run `bundle exec torba pack`.
|
86
|
+
|
87
|
+
3. Add "require" [Sprockets directives][sprockets-directives] to your "application.js"
|
88
|
+
and/or "@import" [Sass directives][sass-import] to "application.css".
|
89
|
+
|
90
|
+
If any changes made to the Torbafile, run `bundle exec torba pack` again.
|
91
|
+
|
92
|
+
### Torbafile
|
93
|
+
|
94
|
+
Torbafile is an assets specification. It is a plain text file that contains one or more
|
95
|
+
sections, each of them describes one remote source of assets.
|
96
|
+
|
97
|
+
Currently only zip archives and [Github releases][github-releases] are supported.
|
98
|
+
|
99
|
+
#### Zip archive package
|
100
|
+
|
101
|
+
Allows to download and unpack asset package from any source accessible by curl.
|
102
|
+
|
103
|
+
The syntax is:
|
104
|
+
|
105
|
+
```
|
106
|
+
zip "name", url: "..." [, import: %w(...)]
|
107
|
+
```
|
108
|
+
|
109
|
+
where "name" is an arbitrary name for the package, more on "import" below. For example,
|
110
|
+
|
111
|
+
```
|
112
|
+
zip "scroll_magic", url: "https://github.com/janpaepke/ScrollMagic/archive/v2.0.0.zip"
|
113
|
+
```
|
114
|
+
|
115
|
+
#### Github release package
|
116
|
+
|
117
|
+
This is a more readable version/shortcut for "https://github.com/.../archive/..." URLs.
|
118
|
+
|
119
|
+
The syntax is:
|
120
|
+
|
121
|
+
```
|
122
|
+
gh_release "name", source: "...", tag: "..." [, import: %w(...)]
|
123
|
+
```
|
124
|
+
|
125
|
+
where "source" is the user + repository and "tag" is the repository tag (exactly as on Github,
|
126
|
+
i.e. with "v" prefix if present), more on "import" below. For example,
|
127
|
+
|
128
|
+
```
|
129
|
+
gh_release "scroll_magic", source: "janpaepke/ScrollMagic", tag: "v.2.0.0"
|
130
|
+
```
|
131
|
+
|
132
|
+
### "Packing the torba" process
|
133
|
+
|
134
|
+
When you run `torba pack` the following happens:
|
135
|
+
|
136
|
+
1. All remote sources are cached locally.
|
137
|
+
|
138
|
+
2. Archives are unpacked with top level directory removed. This is done for good cause it
|
139
|
+
usually contains package version, e.g. "react-0.13.2", and you don't want to reference versions
|
140
|
+
inside your application code (except Torbafile).
|
141
|
+
|
142
|
+
3. Remote source's content is copied as is to the `Torba.home_path` location with **package name used
|
143
|
+
as a namespace**.
|
144
|
+
|
145
|
+
This is also done for good in order to avoid name collisions (since many JS projects can have
|
146
|
+
assets with the same names and all packages are placed into shared Sprockets' virtual filesystem).
|
147
|
+
The downside is that you have to use namespace in each require directive, which can lead to
|
148
|
+
duplication:
|
149
|
+
|
150
|
+
```javascript
|
151
|
+
// application.js
|
152
|
+
//= require 'underscore/underscore'
|
153
|
+
```
|
154
|
+
|
155
|
+
Hint: use "require_directory" if you strongly against such duplication:
|
156
|
+
|
157
|
+
```javascript
|
158
|
+
//= require_directory 'underscore'
|
159
|
+
```
|
160
|
+
|
161
|
+
4. Stylesheets (if any) are converted to ".css.erb" with "asset_path" helpers used in "url(...)"
|
162
|
+
statements.
|
163
|
+
|
164
|
+
### :import option
|
165
|
+
|
166
|
+
Copying whole remote source's content has one disadvantage of using remote source specific paths in your
|
167
|
+
require/import directives. For example, if an archive contains file in "dist/css" directory, you'll have
|
168
|
+
to mention it:
|
169
|
+
|
170
|
+
```css
|
171
|
+
/* application.css */
|
172
|
+
@import 'lightslider/dist/css/lightslider';
|
173
|
+
```
|
174
|
+
|
175
|
+
To mitigate this you can cherry-pick files from the source via "import" option, for example:
|
176
|
+
|
177
|
+
```
|
178
|
+
gh_release "lightslider", source: "sachinchoolur/lightslider", tag: "1.1.2", import: %w[
|
179
|
+
dist/css/lightslider.css
|
180
|
+
]
|
181
|
+
```
|
182
|
+
|
183
|
+
Such files will be copied directly to the package root (i.e. file tree becomes flatten), thus you
|
184
|
+
can omit unnesseccary paths:
|
185
|
+
|
186
|
+
```css
|
187
|
+
@import 'lightslider/lightslider';
|
188
|
+
```
|
189
|
+
|
190
|
+
You can use any Dir.glob pattern:
|
191
|
+
|
192
|
+
```
|
193
|
+
gh_release "lightslider", source: "sachinchoolur/lightslider", tag: "1.1.2", import: %w[
|
194
|
+
dist/css/lightslider.css
|
195
|
+
dist/img/*.png
|
196
|
+
]
|
197
|
+
```
|
198
|
+
|
199
|
+
In addition to this "path/" is treated as a shortcut for "path/**/*" glob pattern.
|
200
|
+
|
201
|
+
|
202
|
+
[bower]: http://bower.io/
|
203
|
+
[sprockets]: https://github.com/sstephenson/sprockets/
|
204
|
+
[sprockets-load-path]: https://github.com/sstephenson/sprockets#the-load-path
|
205
|
+
[torba-pronounce]: http://upload.wikimedia.org/wikipedia/commons/2/28/Uk-%D1%82%D0%BE%D1%80%D0%B1%D0%B0.ogg
|
206
|
+
[github-releases]: https://help.github.com/articles/about-releases/
|
207
|
+
[sprockets-directives]: https://github.com/sstephenson/sprockets#the-directive-processor
|
208
|
+
[sass-import]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#import
|
209
|
+
[rails-assets]: https://rails-assets.org/
|
210
|
+
[bower-rails]: https://github.com/rharriso/bower-rails
|
211
|
+
[semver]: http://semver.org/
|
data/bin/torba
ADDED
data/lib/torba/cli.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "torba"
|
3
|
+
|
4
|
+
module Torba
|
5
|
+
class Cli < Thor
|
6
|
+
desc "pack", "download and prepare all packages defined in Torbafile"
|
7
|
+
def pack
|
8
|
+
Torba.pretty_errors { Torba.pack }
|
9
|
+
Torba.ui.confirm "Torba has been packed!"
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "verify", "check if all packages are prepared"
|
13
|
+
def verify
|
14
|
+
Torba.pretty_errors { Torba.verify }
|
15
|
+
Torba.ui.confirm "Torba is prepared!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Torba
|
2
|
+
# Parses content of CSS file and converts its image assets paths into Sprockets'
|
3
|
+
# {https://github.com/sstephenson/sprockets#logical-paths logical paths}.
|
4
|
+
class CssUrlToErbAssetPath
|
5
|
+
URL_RE =
|
6
|
+
/
|
7
|
+
url\( # url(
|
8
|
+
\s* # optional space
|
9
|
+
(?!data) # no data URIs
|
10
|
+
['"]? # optional quote
|
11
|
+
(?!\/) # only relative location
|
12
|
+
([^'"]+?) # location
|
13
|
+
['"]? # optional quote
|
14
|
+
\s* # optional space
|
15
|
+
\) # )
|
16
|
+
/xm
|
17
|
+
|
18
|
+
# @return [String] CSS file content where image "url(...)" paths are replaced by ERB
|
19
|
+
# interpolations "url(<%= asset_path(...) %>)".
|
20
|
+
# @param content [String] content of original CSS file
|
21
|
+
# @param file_path [String] absolute path to original CSS file
|
22
|
+
# @yield [image_file_path]
|
23
|
+
# @yieldparam image_file_path [String] absolute path to original image file which is mentioned
|
24
|
+
# within CSS file
|
25
|
+
# @yieldreturn [String] logical path to image file within Sprockets' virtual filesystem.
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# content = \
|
29
|
+
# ".react-toolbar {
|
30
|
+
# width: 100%;
|
31
|
+
# background: url('./images/toolbar.png');
|
32
|
+
# }"
|
33
|
+
#
|
34
|
+
# new_content = CssUrlToErbAssetPath.call(content, "/var/downloads/react_unzipped/styles.css") do |url|
|
35
|
+
# url.sub("/var/downloads/react_unzipped/images", "react-toolbar-js"
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# new_content #=>
|
39
|
+
# ".react-toolbar {
|
40
|
+
# width: 100%;
|
41
|
+
# background: url('<%= asset_path('react-toolbar-js/toolbar.png') %>');
|
42
|
+
# }"
|
43
|
+
def self.call(content, file_path)
|
44
|
+
content.gsub(URL_RE) do
|
45
|
+
absolute_image_file_path = File.expand_path($1, File.dirname(file_path))
|
46
|
+
sprockets_file_path = yield absolute_image_file_path
|
47
|
+
"url('<%= asset_path('#{sprockets_file_path}') %>')"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Torba
|
2
|
+
module Errors
|
3
|
+
AssetNotFound = Class.new(StandardError)
|
4
|
+
end
|
5
|
+
|
6
|
+
# Represents a list of assets to be imported from a remote source.
|
7
|
+
class ImportList
|
8
|
+
class Asset < Struct.new(:absolute_path, :subpath)
|
9
|
+
def css?
|
10
|
+
absolute_path.end_with?(".css")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Array<Asset>] full list of assets to be imported.
|
15
|
+
attr_reader :assets
|
16
|
+
|
17
|
+
def initialize(assets)
|
18
|
+
@assets = assets
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Asset] asset with given path.
|
22
|
+
# @param path [String] absolute path of an asset.
|
23
|
+
# @raise [Errors::AssetNotFound] if nothing found
|
24
|
+
def find_by_absolute_path(path)
|
25
|
+
assets.find { |asset| asset.absolute_path == path } || raise(Errors::AssetNotFound.new(path))
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Array<Asset>] list of stylesheets to be imported.
|
29
|
+
def css_assets
|
30
|
+
assets.find_all { |asset| asset.css? }
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array<Asset>] list of assets to be imported except stylesheets.
|
34
|
+
def non_css_assets
|
35
|
+
assets.find_all { |asset| !asset.css? }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require "torba/package"
|
2
|
+
require "torba/remote_sources/zip"
|
3
|
+
require "torba/remote_sources/github_release"
|
4
|
+
|
5
|
+
module Torba
|
6
|
+
# Represents Torbafile.
|
7
|
+
class Manifest
|
8
|
+
# all packages defined in Torbafile
|
9
|
+
attr_reader :packages
|
10
|
+
|
11
|
+
# Reads Torbafile and evaluates it.
|
12
|
+
# @return [Manifest]
|
13
|
+
#
|
14
|
+
# @overload self.build(file_path)
|
15
|
+
# @param file_path [String] absolute path to Torbafile
|
16
|
+
#
|
17
|
+
# @overload self.build
|
18
|
+
# Reads Torbafile from current directory
|
19
|
+
def self.build(file_path = nil)
|
20
|
+
file_path ||= File.join(Dir.pwd, "Torbafile")
|
21
|
+
|
22
|
+
manifest = new
|
23
|
+
content = File.read(file_path)
|
24
|
+
manifest.instance_eval(content, file_path)
|
25
|
+
manifest
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@packages = []
|
30
|
+
end
|
31
|
+
|
32
|
+
# Adds {Package} with {RemoteSources::Zip} to {#packages}
|
33
|
+
def zip(name, options = {})
|
34
|
+
url = options.fetch(:url)
|
35
|
+
remote_source = RemoteSources::Zip.new(url)
|
36
|
+
packages << Package.new(name, remote_source, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds {Package} with {RemoteSources::GithubRelease} to {#packages}
|
40
|
+
def gh_release(name, options = {})
|
41
|
+
source = options.fetch(:source)
|
42
|
+
tag = options.fetch(:tag)
|
43
|
+
remote_source = RemoteSources::GithubRelease.new(source, tag)
|
44
|
+
packages << Package.new(name, remote_source, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Builds all {#packages}
|
48
|
+
# @return [void]
|
49
|
+
def pack
|
50
|
+
packages.each(&:build)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Array<String>] list of paths to each prepared asset package.
|
54
|
+
# It should be appended to the Sprockets' load_path.
|
55
|
+
def load_path
|
56
|
+
packages.map(&:load_path)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Verifies all {#packages}
|
60
|
+
# @return [void]
|
61
|
+
def verify
|
62
|
+
packages.each(&:verify)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
require "torba/css_url_to_erb_asset_path"
|
4
|
+
require "torba/import_list"
|
5
|
+
|
6
|
+
module Torba
|
7
|
+
module Errors
|
8
|
+
UnbuiltPackage = Class.new(StandardError)
|
9
|
+
|
10
|
+
class NothingToImport < StandardError
|
11
|
+
attr_reader :package, :path
|
12
|
+
|
13
|
+
def initialize(options)
|
14
|
+
@package = options.fetch(:package)
|
15
|
+
@path = options.fetch(:path)
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Represents remote source with explicit paths/files to be imported (i.e.
|
22
|
+
# copied from an archive, git repository etc).
|
23
|
+
# Stylesheets (if any) are treated specially because static "url(...)"
|
24
|
+
# definitions should be replaced with Sprockets-aware "asset_path" helpers.
|
25
|
+
class Package
|
26
|
+
# @return [String] short package name, acts as as namespace within Sprockets' load path.
|
27
|
+
# Doesn't need to be equal to remote package name.
|
28
|
+
attr_reader :name
|
29
|
+
|
30
|
+
# @return instance that implements {RemoteSources::Common}
|
31
|
+
attr_reader :remote_source
|
32
|
+
|
33
|
+
# @return [Array<String>] list of file paths to import (relative to remote source root).
|
34
|
+
# @example Direct path to a file
|
35
|
+
# ["build/underscore.js"]
|
36
|
+
# @example {http://www.rubydoc.info/stdlib/core/Dir#glob-class_method Dir.glob} pattern
|
37
|
+
# ["build/*.js", "**/*.css"]
|
38
|
+
# @example Any file within directory (including subdirectories)
|
39
|
+
# ["build/"] # same as ["build/**/*"]
|
40
|
+
attr_reader :import_paths
|
41
|
+
|
42
|
+
# @param name [String] see {#name}
|
43
|
+
# @param remote_source [#[]] see {#remote_source}
|
44
|
+
# @param options [Hash]
|
45
|
+
# @option options [Array<String>] :import list assigned to {#import_paths}
|
46
|
+
def initialize(name, remote_source, options = {})
|
47
|
+
@name = name
|
48
|
+
@remote_source = remote_source
|
49
|
+
@import_paths = (options[:import] || ["**/*"]).sort.map do |path|
|
50
|
+
if path.end_with?("/")
|
51
|
+
File.join(path, "**/*")
|
52
|
+
else
|
53
|
+
path
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @raise [Errors::UnbuiltPackage] if package is not build.
|
59
|
+
def verify
|
60
|
+
raise Errors::UnbuiltPackage.new(name) unless built?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Cache remote source and import specified assets to {#load_path}.
|
64
|
+
# @return [void]
|
65
|
+
# @note Directories explicitly specified in {#import_paths} are not preserved after importing,
|
66
|
+
# i.e. resulted file tree becomes flatten. This way you can omit build specific directories
|
67
|
+
# when requiring assets in your project. If you want to preserve remote source file tree,
|
68
|
+
# use glob patterns without mentioning subdirectories in them.
|
69
|
+
#
|
70
|
+
# In addition {#name} is used as a namespace folder within {#load_path} to protect file names
|
71
|
+
# clashing across packages.
|
72
|
+
#
|
73
|
+
# package.name #=> "datepicker"
|
74
|
+
# package.import_paths #=> ["css/stylesheet.css", "js/*.js"]
|
75
|
+
# Dir[package.load_path + "/**/*"] #=> ["datepicker/stylesheet.css", "datepicker/script.js"]
|
76
|
+
#
|
77
|
+
# package.name #=> "datepicker"
|
78
|
+
# package.import_paths #=> ["**/*.{js,css}"]
|
79
|
+
# Dir[package.load_path + "/**/*"] #=> ["datepicker/css/stylesheet.css", "datepicker/js/script.js"]
|
80
|
+
def build
|
81
|
+
return if built?
|
82
|
+
process_stylesheets
|
83
|
+
process_other_assets
|
84
|
+
rescue
|
85
|
+
remove
|
86
|
+
raise
|
87
|
+
end
|
88
|
+
|
89
|
+
# @return [String] path where processed files of the package reside. It's located within
|
90
|
+
# {Torba.home_path} directory.
|
91
|
+
def load_path
|
92
|
+
@load_path ||= File.join(Torba.home_path, folder_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [ImportList]
|
96
|
+
def import_list
|
97
|
+
@import_list ||= build_import_list
|
98
|
+
end
|
99
|
+
|
100
|
+
# Remove self from filesystem.
|
101
|
+
# @return [void]
|
102
|
+
def remove
|
103
|
+
FileUtils.rm_rf(load_path)
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def built?
|
109
|
+
Dir.exist?(load_path)
|
110
|
+
end
|
111
|
+
|
112
|
+
def folder_name
|
113
|
+
digest = Torba.digest(import_paths.join << remote_source.digest)
|
114
|
+
"#{name}-#{digest}"
|
115
|
+
end
|
116
|
+
|
117
|
+
def build_import_list
|
118
|
+
assets = import_paths.flat_map do |import_path|
|
119
|
+
path_wo_glob_metacharacters = import_path.sub(/\*.+$/, "")
|
120
|
+
|
121
|
+
assets = remote_source[import_path].map do |absolute_path, relative_path|
|
122
|
+
subpath =
|
123
|
+
if relative_path == import_path
|
124
|
+
File.basename(relative_path)
|
125
|
+
else
|
126
|
+
relative_path.sub(path_wo_glob_metacharacters, "")
|
127
|
+
end
|
128
|
+
|
129
|
+
ImportList::Asset.new(absolute_path, subpath)
|
130
|
+
end
|
131
|
+
|
132
|
+
if assets.empty?
|
133
|
+
raise Errors::NothingToImport.new(package: name, path: import_path)
|
134
|
+
end
|
135
|
+
|
136
|
+
assets
|
137
|
+
end
|
138
|
+
|
139
|
+
ImportList.new(assets)
|
140
|
+
end
|
141
|
+
|
142
|
+
def process_stylesheets
|
143
|
+
import_list.css_assets.each do |asset|
|
144
|
+
content = File.read(asset.absolute_path)
|
145
|
+
|
146
|
+
new_content = CssUrlToErbAssetPath.call(content, asset.absolute_path) do |image_file_path|
|
147
|
+
image_asset = import_list.find_by_absolute_path(image_file_path)
|
148
|
+
with_namespace(image_asset.subpath)
|
149
|
+
end
|
150
|
+
|
151
|
+
new_absolute_path = File.join(load_path, with_namespace(asset.subpath + ".erb"))
|
152
|
+
ensure_directory(new_absolute_path)
|
153
|
+
File.write(new_absolute_path, new_content)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def process_other_assets
|
158
|
+
import_list.non_css_assets.each do |asset|
|
159
|
+
new_absolute_path = File.join(load_path, with_namespace(asset.subpath))
|
160
|
+
ensure_directory(new_absolute_path)
|
161
|
+
FileUtils.cp(asset.absolute_path, new_absolute_path)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def with_namespace(file_name)
|
166
|
+
File.join(name, file_name)
|
167
|
+
end
|
168
|
+
|
169
|
+
def ensure_directory(file)
|
170
|
+
FileUtils.mkdir_p(File.dirname(file))
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|