├── .gitignore
├── .travis.yml
├── .yardopts
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
└── torba
├── lib
├── torba.rb
└── torba
│ ├── cli.rb
│ ├── css_url_to_erb_asset_path.rb
│ ├── import_list.rb
│ ├── manifest.rb
│ ├── package.rb
│ ├── rake_task.rb
│ ├── remote_sources
│ ├── common.rb
│ ├── get_file.rb
│ ├── github_release.rb
│ ├── npm.rb
│ ├── targz.rb
│ └── zip.rb
│ ├── ui.rb
│ └── verify.rb
├── test
├── acceptance-cli
│ ├── open_test.rb
│ ├── pack_test.rb
│ ├── show_test.rb
│ └── verify_test.rb
├── css_url_to_erb_asset_path_test.rb
├── fixtures
│ ├── home_path
│ │ ├── 01
│ │ │ └── trumbowyg
│ │ │ │ ├── icons-2x.png
│ │ │ │ ├── icons.png
│ │ │ │ ├── trumbowyg.css.erb
│ │ │ │ └── trumbowyg.js
│ │ ├── 02
│ │ │ └── lo_dash
│ │ │ │ └── lodash.js
│ │ └── 03
│ │ │ └── bourbon
│ │ │ ├── _border-image.scss
│ │ │ ├── _font-source-declaration.scss
│ │ │ └── _retina-image.scss
│ └── torbafiles
│ │ ├── 01_gh_release.rb
│ │ ├── 01_image_asset_not_specified.rb
│ │ ├── 01_targz.rb
│ │ ├── 01_zip.rb
│ │ ├── 02_npm.rb
│ │ ├── 03_not_existed_assets.rb
│ │ └── 04_similar_names.rb
├── import_list_test.rb
├── manifest_test.rb
├── package
│ ├── import_list_test.rb
│ └── logical_paths_test.rb
├── package_test.rb
├── rake_task_test.rb
├── remote_sources
│ ├── common_test.rb
│ ├── get_file_test.rb
│ ├── github_release_test.rb
│ ├── npm_test.rb
│ ├── targz_test.rb
│ └── zip_test.rb
├── test_helper.rb
└── torba_test.rb
└── torba.gemspec
/.gitignore:
--------------------------------------------------------------------------------
1 | /.bundle/
2 | /.yardoc
3 | /Gemfile.lock
4 | /_yardoc/
5 | /coverage/
6 | /doc/
7 | /pkg/
8 | /spec/reports/
9 | /tmp/
10 | *.bundle
11 | *.so
12 | *.o
13 | *.a
14 | mkmf.log
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: ruby
2 | rvm:
3 | - 1.9.3
4 | - 2.0.0
5 | - 2.1
6 | - 2.2
7 | - 2.3
8 | - 2.4
9 | - 2.5
10 | - 2.6
11 | - 2.7
12 | - jruby
13 | bundler_args: --without doc debug
14 |
--------------------------------------------------------------------------------
/.yardopts:
--------------------------------------------------------------------------------
1 | --no-private
2 | -
3 | CHANGELOG.md
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## Unreleased
2 |
3 | ## Version 1.2.0
4 |
5 | ### Enhancements
6 |
7 | * Support scoped names for NPM packages (e.g. "@lottiefiles/lottie-player")
8 |
9 | ## Version 1.1.1
10 |
11 | ### Enhancements
12 |
13 | * Test files have been removed from the package to reduce
14 | file size (and to spend less internet traffic too).
15 |
16 | ## Version 1.1.0
17 |
18 | ### Enhancements
19 |
20 | * Thor dependency has been relaxed to support Rails 6.x.
21 |
22 | ## Version 1.0.1
23 |
24 | ### Bug fixes
25 |
26 | * Fix unpacking tar.gz when running on unpriveleged containers
27 |
28 | ## Version 1.0.0
29 |
30 | ### Breaking changes
31 |
32 | * Rails support has been removed. Use [torba-rails][torba-rails] instead.
33 | * `torba/verify` no longer checks, whether it is executed within Rake task
34 | or not. This was a workaround for Rails support, which is no longer a goal
35 | of this gem.
36 | * `torba/verify` no longer checks `TORBA_DONT_VERIFY` env variable. Same as
37 | above.
38 |
39 | ### Upgrading notes
40 |
41 | If you are a non-Rails user, this should be a non-breaking update. In rare cases,
42 | when you depend on old behaviour of `torba/verify`, please, add the conditionals
43 | to your application code.
44 |
45 | Rails users should:
46 |
47 | 1. Replace `gem "torba"` with `gem "torba-rails"` in the Gemfile.
48 | 2. Remove `require "torba/verify"` from `boot.rb`.
49 | 3. Remove `require "torba/rails"` from `application.rb`.
50 | 4. Unset obsolete `TORBA_DONT_VERIFY` env variable if present.
51 |
52 | ### Enhancements
53 |
54 | * Rake task accepts a block to be executed before packing process
55 |
56 | [torba-rails]: https://github.com/torba-rb/torba-rails
57 |
58 | ## Version 0.7.0
59 |
60 | ### Enhancements
61 |
62 | * `torba show` command
63 | * `torba open` command
64 |
65 | ## Version 0.6.0
66 |
67 | ### Enhancements
68 |
69 | * Torba.cache_path= setter
70 |
71 | ### Bug fixes
72 |
73 | * Handle stylesheets that mention nonexisting assets (e.g. with SCSS variables)
74 |
75 | ## Version 0.5.1
76 |
77 | ### Bug fixes
78 |
79 | * Treat SASS/SCSS files as regular stylesheets
80 |
81 | ## Version 0.5.0
82 |
83 | ### Upgrading notes
84 |
85 | Rails users are advised to remove manual execution of `torba pack` from
86 | their deployment scripts, since the command now is a part of
87 | `rake assets:precompile`.
88 |
89 | ### Enhancements
90 |
91 | * Support deployment to Heroku
92 | * `torba pack` is automatically injected into `rake assets:precompile`
93 | in Rails applications
94 | * `torba install` command is a mapping for `torba pack`
95 |
96 | ### Bug fixes
97 |
98 | * Retry 5 times if fetching a remote file fails
99 |
100 | ## Version 0.4.2
101 |
102 | ### Bug fixes
103 |
104 | * Remote url() assets should not be given to asset_path
105 | * Support both url(data: and url('data: in css
106 |
107 | ## Version 0.4.1
108 |
109 | ### Bug fixes
110 |
111 | * Do not add the `.erb` extension for stylesheets that don't need it.
112 |
113 | ## Version 0.4.0
114 |
115 | ### Enhancements
116 |
117 | * The exception that gets raised during `torba verify` now list the missing
118 | packages
119 |
120 | ## Version 0.3.1
121 |
122 | ### Bug fixes
123 |
124 | * Fix no asset found when a CSS url contains "?#iefix" or "#svg-fragment"
125 |
126 | ## Version 0.3.0
127 |
128 | ### Enhancements
129 |
130 | * Rails setup automatically populates `Rails.application.config.assets.precompile`
131 | If libraries added via Torba have image/font content, you can remove it from
132 | that list, no need for manual manipulation
133 | * Support .tar.gz remote sources
134 | * Support npm packages
135 |
136 | ## Version 0.2.1
137 |
138 | ### Bug fixes
139 |
140 | * Cached GithubRelease remote contains in path repository name, not nameless
141 | version
142 |
143 | ## Version 0.2.0
144 |
145 | ### Enhancements
146 |
147 | * Name for GH releases can be optional
148 | * Cached zip remote contains URL filename in path for better introspection
149 | * GithubRelease remote: introduce #repository_user, #repository_name
150 |
151 | ### Bug fixes
152 |
153 | * Display actual exception (if any) instead of SystemExitError for pow
154 | * Remote source always returns absolute path even if Torba.home_path/cache_path
155 | is relative to current directory
156 |
157 | ## Version 0.1.1
158 |
159 | ### Bug fixes
160 |
161 | * Fail fast and report on 404 resources
162 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
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 | ```
48 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gemspec
4 |
5 | gem "pry", :group => :debug
6 | gem "yard", "~> 0.8", :group => :doc
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
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.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
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 | Production ready.
17 |
18 | ## Documentation
19 |
20 | [Released version](http://rubydoc.info/gems/torba/1.2.0)
21 |
22 | ## Why
23 |
24 | De facto approach, i.e. wrapping JS and CSS libraries in a gem, requires a
25 | maintainer to constantly track changes in an upstream repository. Even more so, if a gem
26 | maintainer stops using that specific library, the gem will eventually become abandoned.
27 | Additionally, many libraries still have no gem wrappers.
28 |
29 | Other alternatives:
30 |
31 | * [rails-assets][rails-assets] relies on Bower *and* it is quite complex,
32 | * [bower-rails][bower-rails] relies on Bower, see below for why this can be an issue.
33 |
34 | Problems with the Bower:
35 |
36 | * it is not a part of the Ruby ecosystem,
37 | * frontend JS libraries are usually standalone (except for a potential jQuery dependency), so there's
38 | no need for a complex Bundler-like solution with tree-dependency resolution,
39 | * often we can't use optimistic version constraints, because the JavaScript community does not consistenly apply the principles of [Semver][semver]. By specifying strict versions we use Bower as a complex facade for functionality that could be accomplished with curl.
40 |
41 | ## External dependencies
42 |
43 | * curl
44 | * unzip
45 | * gzip
46 | * tar
47 |
48 | ## Design limitations
49 |
50 | * Torba doesn't do any version dependency resolution, it's up to you to specify the correct version of
51 | each asset package,
52 | * Torba doesn't do any builds, you should use remote sources with pre-built assets.
53 |
54 | ## Installation
55 |
56 | ### Rails
57 |
58 | Use [torba-rails][torba-rails-github].
59 |
60 | ### Sinatra
61 |
62 | See this [example project][sinatra-example].
63 |
64 | ### Other Ruby application
65 |
66 | Add this line to your application's Gemfile and run bundle:
67 |
68 | ```ruby
69 | gem 'torba'
70 | ```
71 |
72 | ## Usage
73 |
74 | 1. Create Torbafile at the project root and commit it.
75 |
76 | 2. Run `bundle exec torba pack`.
77 |
78 | 3. Add "require" [Sprockets directives][sprockets-directives] to your "application.js"
79 | and/or "@import" [Sass directives][sass-import] to "application.css".
80 |
81 | If any changes made to the Torbafile, run `bundle exec torba pack` again.
82 |
83 | ### Torbafile
84 |
85 | Torbafile is an assets specification. It is a plain text file that contains one or more
86 | sections, each of them describes one remote source of assets.
87 |
88 | Currently only zip, tar.gz archives, [Github releases][github-releases] and
89 | [npm packages][npm] are supported.
90 |
91 | #### Zip archive package
92 |
93 | Allows to download and unpack asset package from any source accessible by curl.
94 |
95 | The syntax is:
96 |
97 | ```ruby
98 | zip "name", url: "..." [, import: %w(...)]
99 | ```
100 |
101 | where "name" is an arbitrary name for the package, more on "import" below. For example,
102 |
103 | ```ruby
104 | zip "scroll_magic", url: "https://github.com/janpaepke/ScrollMagic/archive/v2.0.0.zip"
105 | ```
106 |
107 | #### Tar.gz archive package
108 |
109 | The syntax is same as for a zip package:
110 |
111 | ```ruby
112 | targz "name", url: "..." [, import: %w(...)]
113 | ```
114 |
115 | for example,
116 |
117 | ```ruby
118 | targz "scroll_magic", url: "https://github.com/janpaepke/ScrollMagic/archive/v2.0.0.tar.gz"
119 | ```
120 |
121 | #### Github release package
122 |
123 | This is a more readable version/shortcut for "https://github.com/.../archive/..." URLs.
124 |
125 | The syntax is:
126 |
127 | ```ruby
128 | gh_release "name", source: "...", tag: "..." [, import: %w(...)]
129 | ```
130 |
131 | where "source" is the user + repository and "tag" is the repository tag (exactly as on Github,
132 | i.e. with "v" prefix if present), more on "import" below. For example,
133 |
134 | ```ruby
135 | gh_release "scroll_magic", source: "janpaepke/ScrollMagic", tag: "v.2.0.0"
136 | ```
137 |
138 | You can omit the name, it will be equal to the repository name:
139 |
140 | ```ruby
141 | gh_release source: "janpaepke/ScrollMagic", tag: "v.2.0.0" # "ScrollMagic" is assumed
142 | ```
143 |
144 | #### npm package
145 |
146 | Allows to download packages from npm registry.
147 |
148 | The syntax is:
149 |
150 | ```ruby
151 | npm "name", package: "...", version: "..." [, import: %w(...)]
152 | ```
153 |
154 | where "package" is the package name as published on npm registry and "version" is its version,
155 | more on "import" below. For example,
156 |
157 | ```ruby
158 | npm "coffee", package: "coffee-script", version: "1.9.2"
159 | ```
160 |
161 | You can omit the name, it will be equal to the package name:
162 |
163 | ```ruby
164 | npm package: "coffee-script", version: "1.9.2"
165 | ```
166 |
167 | ### Examples
168 |
169 | See [Torbafiles][torbafile-examples] used for testing.
170 |
171 | ### "Packing the torba" process
172 |
173 | When you run `torba pack` the following happens:
174 |
175 | 1. All remote sources are cached locally.
176 |
177 | 2. Archives are unpacked with top level directory removed. This is done for good because it
178 | usually contains the package version in the name, e.g. "react-0.13.2", and you don't want to have to reference versions
179 | inside your application code (except Torbafile).
180 |
181 | 3. Remote source's content is copied as is to the `Torba.home_path` location with **package name used
182 | as a namespace**.
183 |
184 | This is also done for good reason in order to avoid name collisions (since many JS projects can have
185 | assets with the same names and all packages are placed into Sprockets' shared virtual filesystem).
186 | The downside is that you have to use namespaces in each require directive, which can lead to
187 | duplication:
188 |
189 | ```javascript
190 | // application.js
191 | //= require 'underscore/underscore'
192 | ```
193 |
194 | Hint: use "require_directory" if you're strongly against such duplication:
195 |
196 | ```javascript
197 | //= require_directory 'underscore'
198 | ```
199 |
200 | 4. Stylesheets (if any) are converted to ".css.erb" with "asset_path" helpers used in "url(...)"
201 | statements.
202 |
203 | ### :import option
204 |
205 | Copying whole remote source's content has the disadvantage of using remote source specific paths in your
206 | require/import directives. For example, if an archive contains files in the "dist/css" directory, you'll have
207 | to mention it:
208 |
209 | ```css
210 | /* application.css */
211 | @import 'lightslider/dist/css/lightslider';
212 | ```
213 |
214 | To mitigate this you can cherry-pick files from the source via the "import" option, for example:
215 |
216 | ```ruby
217 | gh_release "lightslider", source: "sachinchoolur/lightslider", tag: "1.1.2", import: %w[
218 | dist/css/lightslider.css
219 | ]
220 | ```
221 |
222 | Such files will be copied directly to the package root (i.e. file tree becomes flatten), thus you
223 | can omit unnecessary paths:
224 |
225 | ```css
226 | @import 'lightslider/lightslider';
227 | ```
228 |
229 | You can use any Dir.glob pattern:
230 |
231 | ```ruby
232 | gh_release "lightslider", source: "sachinchoolur/lightslider", tag: "1.1.2", import: %w[
233 | dist/css/lightslider.css
234 | dist/img/*.png
235 | ]
236 | ```
237 |
238 | In addition to this "path/" is treated as a shortcut for "path/**/*" glob pattern.
239 |
240 | [bower]: http://bower.io/
241 | [sprockets]: https://github.com/rails/sprockets/
242 | [sprockets-load-path]: https://github.com/rails/sprockets#the-load-path
243 | [torba-pronounce]: http://upload.wikimedia.org/wikipedia/commons/2/28/Uk-%D1%82%D0%BE%D1%80%D0%B1%D0%B0.ogg
244 | [github-releases]: https://help.github.com/articles/about-releases/
245 | [sprockets-directives]: https://github.com/rails/sprockets#the-directive-processor
246 | [sass-import]: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#import
247 | [rails-assets]: https://rails-assets.org/
248 | [bower-rails]: https://github.com/rharriso/bower-rails
249 | [semver]: http://semver.org/
250 | [npm]: https://npmjs.com
251 | [torba-rails-github]: https://github.com/torba-rb/torba-rails
252 | [sinatra-example]: https://github.com/xfalcox/sinatra-assets-seed
253 | [torbafile-examples]: https://github.com/torba-rb/torba/tree/master/test/fixtures/torbafiles
254 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 |
3 | task :default => :test
4 | task :test do
5 | $LOAD_PATH.unshift("test")
6 | Dir.glob("./test/**/*_test.rb").each { |file| require file}
7 | end
8 |
--------------------------------------------------------------------------------
/bin/torba:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 |
3 | require "torba/cli"
4 |
5 | Torba::Cli.start
6 |
--------------------------------------------------------------------------------
/lib/torba.rb:
--------------------------------------------------------------------------------
1 | require "digest"
2 |
3 | require "torba/ui"
4 | require "torba/manifest"
5 |
6 | # @since 0.1.0
7 | module Torba
8 | module Errors
9 | ShellCommandFailed = Class.new(StandardError)
10 | end
11 |
12 | # @return [String] root path to prepared asset packages. By default it's ".torba" within
13 | # your OS home directory (i.e. packages are shared between projects).
14 | # @note use "TORBA_HOME_PATH" env variable to override default value
15 | def self.home_path
16 | @home_path ||= ENV["TORBA_HOME_PATH"] || File.join(Dir.home, ".torba")
17 | end
18 |
19 | # Override home path with a new value
20 | # @param val [String] new home path
21 | # @return [void]
22 | def self.home_path=(val)
23 | @home_path = val
24 | end
25 |
26 | # @return [String] root path to downloaded yet unprocessed asset packages. By default
27 | # it's "cache" within {.home_path}.
28 | # @note use "TORBA_CACHE_PATH" env variable to override default value
29 | def self.cache_path
30 | @cache_path ||= ENV["TORBA_CACHE_PATH"] || File.join(home_path, "cache")
31 | end
32 |
33 | # Override cache path with a new value
34 | # @param val [String] new cache path
35 | # @return [void]
36 | # @since 0.6.0
37 | def self.cache_path=(val)
38 | @cache_path = val
39 | end
40 |
41 | # @return [Ui]
42 | def self.ui
43 | @ui ||= Ui.new
44 | end
45 |
46 | # @return [Manifest]
47 | def self.manifest
48 | @manifest ||= Manifest.build
49 | end
50 |
51 | # @see Manifest#pack
52 | def self.pack
53 | manifest.pack
54 | end
55 |
56 | # @see Manifest#load_path
57 | def self.load_path
58 | manifest.load_path
59 | end
60 |
61 | # @see Manifest#non_js_css_logical_paths
62 | # @since 0.3.0
63 | def self.non_js_css_logical_paths
64 | manifest.non_js_css_logical_paths
65 | end
66 |
67 | # @see Manifest#verify
68 | def self.verify
69 | manifest.verify
70 | end
71 |
72 | # @return [String] unique short fingerprint/hash for given string
73 | # @param [String] string to be hashed
74 | #
75 | # @example
76 | # Torba.digest("path/to/hash") #=> "23e3e63c"
77 | def self.digest(string)
78 | Digest::SHA1.hexdigest(string)[0..7]
79 | end
80 |
81 | # @see Manifest#find_packages_by_name
82 | # @since 0.7.0
83 | def self.find_packages_by_name(name)
84 | manifest.find_packages_by_name(name)
85 | end
86 |
87 | # @yield a block, converts common exceptions into useful messages
88 | def self.pretty_errors
89 | yield
90 | rescue Errors::MissingPackages => e
91 | ui.error "Your Torba is not packed yet."
92 | ui.error "Missing packages:"
93 | e.packages.each do |package|
94 | ui.error " * #{package.name}"
95 | end
96 | ui.suggest "Run `bundle exec torba pack` to install missing packages."
97 | exit(false)
98 | rescue Errors::ShellCommandFailed => e
99 | ui.error "Couldn't execute command '#{e.message}'"
100 | exit(false)
101 | rescue Errors::NothingToImport => e
102 | ui.error "Couldn't import an asset(-s) '#{e.path}' from import list in '#{e.package}'."
103 | ui.suggest "Check for typos."
104 | ui.suggest "Make sure that the path has trailing '/' if its a directory."
105 | exit(false)
106 | rescue Errors::AssetNotFound => e
107 | ui.error "Unknown asset to process with path '#{e.message}'."
108 | ui.suggest "Make sure that you've imported all image/font assets mentioned in a stylesheet(-s)."
109 | exit(false)
110 | end
111 | end
112 |
--------------------------------------------------------------------------------
/lib/torba/cli.rb:
--------------------------------------------------------------------------------
1 | require "thor"
2 | require "shellwords"
3 | require "torba"
4 |
5 | module Torba
6 | class Cli < Thor
7 | desc "pack", "download and prepare all packages defined in Torbafile"
8 | def pack
9 | Torba.pretty_errors { Torba.pack }
10 | Torba.ui.confirm "Torba has been packed!"
11 | end
12 | map install: :pack
13 |
14 | desc "verify", "check if all packages are prepared"
15 | def verify
16 | Torba.pretty_errors { Torba.verify }
17 | Torba.ui.confirm "Torba is prepared!"
18 | end
19 |
20 | desc "show PACKAGE", "show the source location of a particular package"
21 | def show(name)
22 | Torba.pretty_errors do
23 | Torba.pack
24 | Torba.ui.info(find_package(name).load_path)
25 | end
26 | end
27 |
28 | desc "open PACKAGE", "open a particular package in editor"
29 | def open(name)
30 | editor = [ENV["TORBA_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find { |e| !e.nil? && !e.empty? }
31 | unless editor
32 | Torba.ui.error("To open a package, set $EDITOR or $TORBA_EDITOR")
33 | exit(false)
34 | end
35 |
36 | Torba.pretty_errors do
37 | Torba.pack
38 |
39 | command = Shellwords.split(editor) << find_package(name).load_path
40 | system(*command) || Torba.ui.error("Could not run '#{command.join(" ")}'")
41 | end
42 | end
43 |
44 | private
45 |
46 | def find_package(name)
47 | packages = Torba.find_packages_by_name(name)
48 | case packages.size
49 | when 0
50 | Torba.ui.error "Could not find package '#{name}'."
51 | exit(false)
52 | when 1
53 | packages.first
54 | else
55 | index = Torba.ui.choose_one(packages.map(&:name))
56 | if index
57 | packages[index]
58 | else
59 | exit(false)
60 | end
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/lib/torba/css_url_to_erb_asset_path.rb:
--------------------------------------------------------------------------------
1 | module Torba
2 | # Parses content of CSS file and converts its image assets paths into Sprockets'
3 | # {https://github.com/rails/sprockets#logical-paths logical paths}.
4 | class CssUrlToErbAssetPath
5 | URL_RE =
6 | /
7 | ( # $1
8 | url\( # url(
9 | \s* # optional space
10 | ['"]? # optional quote
11 | (?!data) # no data URIs
12 | (?!http[s]?:\/\/) # no remote URIs
13 | (?!\/) # only relative location
14 | )
15 | ( # $2
16 | [^'"?#]+? # location
17 | )
18 | ( # $3
19 | ([?#][^'"]+?)? # optional query or fragment
20 | ['"]? # optional quote
21 | \s* # optional space
22 | \) # )
23 | )
24 | /xm
25 |
26 | # @return [String] CSS file content where image "url(...)" paths are replaced by ERB
27 | # interpolations "url(<%= asset_path(...) %>)".
28 | # @param content [String] content of original CSS file
29 | # @param file_path [String] absolute path to original CSS file
30 | # @yield [image_file_path]
31 | # @yieldparam image_file_path [String] absolute path to original image file which is mentioned
32 | # within CSS file
33 | # @yieldreturn [String] logical path to image file within Sprockets' virtual filesystem.
34 | #
35 | # @example
36 | # content = \
37 | # ".react-toolbar {
38 | # width: 100%;
39 | # background: url('./images/toolbar.png');
40 | # }"
41 | #
42 | # new_content = CssUrlToErbAssetPath.call(content, "/var/downloads/react_unzipped/styles.css") do |url|
43 | # url.sub("/var/downloads/react_unzipped/images", "react-toolbar-js"
44 | # end
45 | #
46 | # new_content #=>
47 | # ".react-toolbar {
48 | # width: 100%;
49 | # background: url('<%= asset_path('react-toolbar-js/toolbar.png') %>');
50 | # }"
51 | def self.call(content, file_path)
52 | content.gsub(URL_RE) do
53 | absolute_image_file_path = File.expand_path($2, File.dirname(file_path))
54 | sprockets_file_path = yield absolute_image_file_path
55 | if sprockets_file_path
56 | "#{$1}<%= asset_path('#{sprockets_file_path}') %>#{$3}"
57 | else
58 | $&
59 | end
60 | end
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/torba/import_list.rb:
--------------------------------------------------------------------------------
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, :logical_path)
9 | def css?
10 | absolute_path =~ /\.(sass|scss|css)$/
11 | end
12 |
13 | def js?
14 | absolute_path.end_with?(".js")
15 | end
16 | end
17 |
18 | # @return [Array
', '
') 374 | .replace(' ', '') 375 | ); 376 | t.semanticCode(); 377 | } 378 | 379 | 380 | 381 | t.$editor 382 | .on('dblclick', 'img', function(e){ 383 | var $img = $(this); 384 | t.openModalInsert(t.lang.insertImage, { 385 | url: { 386 | label: 'URL', 387 | value: $img.attr('src'), 388 | required: true 389 | }, 390 | alt: { 391 | label: 'description', 392 | value: $img.attr('alt') 393 | } 394 | }, function(v){ 395 | $img.attr({ 396 | src: v.url, 397 | alt: v.alt 398 | }); 399 | }); 400 | e.stopPropagation(); 401 | }) 402 | .on('keyup', function(e){ 403 | t.semanticCode(false, e.which === 13); 404 | }) 405 | .on('focus', function(){ 406 | t.$creator.trigger('tbwfocus'); 407 | }) 408 | .on('blur', function(){ 409 | t.syncCode(); 410 | t.$creator.trigger('tbwblur'); 411 | }); 412 | }, 413 | 414 | 415 | // Build the Textarea which contain HTML generated code 416 | buildTextarea: function(){ 417 | return $('', { 418 | name: this.$e.attr('id'), 419 | height: this.height 420 | }); 421 | }, 422 | 423 | 424 | // Build button pane, use o.btns and o.btnsAdd options 425 | buildBtnPane: function(){ 426 | var t = this, 427 | pfx = t.o.prefix; 428 | 429 | if(t.o.btns === false) 430 | return; 431 | 432 | t.$btnPane = $('
783 | semanticCode: function(force, full){ 784 | var t = this; 785 | t.syncCode(force); 786 | 787 | if(t.o.semantic){ 788 | t.semanticTag('b', 'strong'); 789 | t.semanticTag('i', 'em'); 790 | t.semanticTag('strike', 'del'); 791 | 792 | if(full){ 793 | // Wrap text nodes in p 794 | t.$editor.contents() 795 | .filter(function(){ 796 | // Only non-empty text nodes 797 | return this.nodeType === 3 && $.trim(this.nodeValue).length > 0; 798 | }).wrap('
').end() 799 | 800 | // Remove all br 801 | .filter('br').remove(); 802 | 803 | t.saveSelection(); 804 | t.semanticTag('div', 'p'); 805 | t.restoreSelection(); 806 | } 807 | 808 | t.$e.val(t.$editor.html()); 809 | } 810 | }, 811 | semanticTag: function(oldTag, newTag){ 812 | $(oldTag, this.$editor).each(function(){ 813 | $(this).replaceWith(function(){ 814 | return '<'+newTag+'>' + $(this).html() + ''+newTag+'>'; 815 | }); 816 | }); 817 | }, 818 | 819 | 820 | // Function call when user click on "Insert Link" 821 | createLink: function(){ 822 | var t = this; 823 | t.saveSelection(); 824 | t.openModalInsert(t.lang.createLink, { 825 | url: { 826 | label: 'URL', 827 | value: 'http://', 828 | required: true 829 | }, 830 | title: { 831 | label: t.lang.title, 832 | value: t.selection 833 | }, 834 | text: { 835 | label: t.lang.text, 836 | value: t.selection 837 | } 838 | }, function(v){ // v is value 839 | t.execCmd('createLink', v.url); 840 | var l = $('a[href="'+v.url+'"]:not([title])', t.$box); 841 | if(v.text.length > 0) 842 | l.text(v.text); 843 | 844 | if(v.title.length > 0) 845 | l.attr('title', v.title); 846 | 847 | return true; 848 | }); 849 | }, 850 | insertImage: function(){ 851 | var t = this; 852 | t.saveSelection(); 853 | t.openModalInsert(t.lang.insertImage, { 854 | url: { 855 | label: 'URL', 856 | value: 'http://', 857 | required: true 858 | }, 859 | alt: { 860 | label: t.lang.description, 861 | value: t.selection 862 | } 863 | }, function(v){ // v are values 864 | t.execCmd('insertImage', v.url); 865 | $('img[src="'+v.url+'"]:not([alt])', t.$box).attr('alt', v.alt); 866 | return true; 867 | }); 868 | }, 869 | 870 | 871 | /* 872 | * Call method of trumbowyg if exist 873 | * else try to call anonymous function 874 | * and finaly native execCommand 875 | */ 876 | execCmd: function(cmd, param){ 877 | var t = this; 878 | if(cmd != 'dropdown') 879 | t.$editor.focus(); 880 | 881 | try { 882 | t[cmd](param); 883 | } catch(e){ 884 | try { 885 | cmd(param, t); 886 | } catch(e2){ 887 | //t.$editor.focus(); 888 | if(cmd == 'insertHorizontalRule') 889 | param = null; 890 | else if(cmd == 'formatBlock' && (navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > 0)) 891 | param = '<' + param + '>'; 892 | 893 | t.doc.execCommand(cmd, false, param); 894 | } 895 | } 896 | t.syncCode(); 897 | }, 898 | 899 | 900 | // Open a modal box 901 | openModal: function(title, content){ 902 | var t = this, 903 | pfx = t.o.prefix; 904 | 905 | // No open a modal box when exist other modal box 906 | if($('.' + pfx + 'modal-box', t.$box).size() > 0) 907 | return false; 908 | 909 | t.saveSelection(); 910 | t.showOverlay(); 911 | 912 | // Disable all btnPane btns 913 | t.$btnPane.addClass(pfx + 'disable'); 914 | 915 | // Build out of ModalBox, it's the mask for animations 916 | var $modal = $('', { 917 | class: pfx + 'modal ' + pfx + 'fixed-top' 918 | }).css({ 919 | top: (parseInt(t.$btnPane.css('height')) + 1) + 'px' 920 | }).appendTo(t.$box); 921 | 922 | // Click on overflay close modal by cancelling them 923 | t.$overlay.one('click', function(e){ 924 | e.preventDefault(); 925 | $modal.trigger(pfx + 'cancel'); 926 | }); 927 | 928 | // Build the form 929 | var $form = $('', { 930 | action: '', 931 | html: content 932 | }) 933 | .on('submit', function(e){ 934 | e.preventDefault(); 935 | $modal.trigger(pfx + 'confirm'); 936 | }) 937 | .on('reset', function(e){ 938 | e.preventDefault(); 939 | $modal.trigger(pfx + 'cancel'); 940 | }); 941 | 942 | 943 | // Build ModalBox and animate to show them 944 | var $box = $('', { 945 | class: pfx + 'modal-box', 946 | html: $form 947 | }) 948 | .css({ 949 | top: '-' + parseInt(t.$btnPane.outerHeight()) + 'px', 950 | opacity: 0 951 | }) 952 | .appendTo($modal) 953 | .animate({ 954 | top: 0, 955 | opacity: 1 956 | }, t.o.duration / 2); 957 | 958 | 959 | // Append title 960 | $('', { 961 | text: title, 962 | class: pfx + 'modal-title' 963 | }).prependTo($box); 964 | 965 | 966 | // Focus in modal box 967 | $box.find('input:first').focus(); 968 | 969 | 970 | // Append Confirm and Cancel buttons 971 | t.buildModalBtn('submit', $box); 972 | t.buildModalBtn('reset', $box); 973 | 974 | 975 | $(window).trigger('scroll'); 976 | 977 | return $modal; 978 | }, 979 | // @param n is name of modal 980 | buildModalBtn: function(n, modal){ 981 | var t = this, 982 | pfx = t.o.prefix; 983 | 984 | return $('', { 985 | class: pfx + 'modal-button ' + pfx + 'modal-' + n, 986 | type: n, 987 | text: t.lang[n] || n 988 | }).appendTo(modal.find('form')); 989 | }, 990 | // close current modal box 991 | closeModal: function(){ 992 | var t = this, 993 | pfx = t.o.prefix; 994 | 995 | t.$btnPane.removeClass(pfx + 'disable'); 996 | t.$overlay.off(); 997 | 998 | var $modalBox = $('.' + pfx + 'modal-box', t.$box); 999 | 1000 | $modalBox.animate({ 1001 | top: '-' + $modalBox.css('height') 1002 | }, t.o.duration/2, function(){ 1003 | $(this).parent().remove(); 1004 | t.hideOverlay(); 1005 | }); 1006 | }, 1007 | // Preformated build and management modal 1008 | openModalInsert: function(title, fields, cmd){ 1009 | var t = this, 1010 | pfx = t.o.prefix, 1011 | lg = t.lang, 1012 | html = ''; 1013 | 1014 | for(var f in fields){ 1015 | var fd = fields[f], // field definition 1016 | label = (fd.label === undefined) ? (lg[f] ? lg[f] : f) : (lg[fd.label] ? lg[fd.label] : fd.label); 1017 | 1018 | if(fd.name === undefined) 1019 | fd.name = f; 1020 | 1021 | if(!fd.pattern && f === 'url'){ 1022 | fd.pattern = /^(http|https):\/\/([\w~#!:.?+=&%@!\-\/]+)$/; 1023 | fd.patternError = lg.invalidUrl; 1024 | } 1025 | 1026 | html += ''; 1027 | } 1028 | 1029 | return t.openModal(title, html) 1030 | .on(pfx + 'confirm', function(){ 1031 | var $form = $(this).find('form'), 1032 | valid = true, 1033 | v = {}; // values 1034 | 1035 | for(var f in fields){ 1036 | var $field = $('input[name="'+f+'"]', $form); 1037 | 1038 | v[f] = $.trim($field.val()); 1039 | 1040 | // Validate value 1041 | if(fields[f].required && v[f] === ''){ 1042 | valid = false; 1043 | t.addErrorOnModalField($field, t.lang.required); 1044 | } else if(fields[f].pattern && !fields[f].pattern.test(v[f])){ 1045 | valid = false; 1046 | t.addErrorOnModalField($field, fields[f].patternError); 1047 | } 1048 | } 1049 | 1050 | if(valid){ 1051 | t.restoreSelection(); 1052 | 1053 | if(cmd(v, fields)){ 1054 | t.syncCode(); 1055 | t.closeModal(); 1056 | $(this).off(pfx + 'confirm'); 1057 | } 1058 | } 1059 | }) 1060 | .one(pfx + 'cancel', function(){ 1061 | $(this).off(pfx + 'confirm'); 1062 | t.closeModal(); 1063 | t.restoreSelection(); 1064 | }); 1065 | }, 1066 | addErrorOnModalField: function($field, err){ 1067 | var pfx = this.o.prefix, 1068 | $label = $field.parent(); 1069 | 1070 | $field.on('change keyup', function(){ 1071 | $label.removeClass(pfx + 'input-error'); 1072 | }); 1073 | $label 1074 | .addClass(pfx + 'input-error') 1075 | .find('input+span').append( 1076 | $('', { 1077 | class: pfx +'msg-error', 1078 | text: err 1079 | }) 1080 | ); 1081 | }, 1082 | 1083 | 1084 | 1085 | 1086 | // Selection management 1087 | saveSelection: function(){ 1088 | var t = this, 1089 | d = t.doc; 1090 | 1091 | t.selection = null; 1092 | if(window.getSelection){ 1093 | var s = window.getSelection(); 1094 | if(s.getRangeAt && s.rangeCount) 1095 | t.selection = s.getRangeAt(0); 1096 | } else if(d.selection && d.selection.createRange) 1097 | t.selection = d.selection.createRange(); 1098 | }, 1099 | restoreSelection: function(){ 1100 | var t = this, 1101 | range = t.selection; 1102 | 1103 | if(range){ 1104 | if(window.getSelection){ 1105 | var s = window.getSelection(); 1106 | s.removeAllRanges(); 1107 | s.addRange(range); 1108 | } else if(t.doc.selection && range.select) 1109 | range.select(); 1110 | } 1111 | }, 1112 | 1113 | 1114 | 1115 | // Return true if must enable Trumbowyg on this mobile device 1116 | isEnabled: function(){ 1117 | var exprTablet = new RegExp("(iPad|webOS)"), 1118 | exprMobile = new RegExp("(iPhone|iPod|Android|BlackBerry|Windows Phone|ZuneWP7)"), 1119 | ua = navigator.userAgent; 1120 | 1121 | return (this.o.tablet === true && exprTablet.test(ua)) || (this.o.mobile === true && exprMobile.test(ua)); 1122 | } 1123 | }; 1124 | })(window, document, jQuery); 1125 | -------------------------------------------------------------------------------- /test/fixtures/home_path/03/bourbon/_border-image.scss: -------------------------------------------------------------------------------- 1 | @mixin border-image($borders...) { 2 | $webkit-borders: (); 3 | $spec-borders: (); 4 | 5 | @each $border in $borders { 6 | $webkit-border: (); 7 | $spec-border: (); 8 | $border-type: type-of($border); 9 | 10 | @if $border-type == string or list { 11 | $border-str: if($border-type == list, nth($border, 1), $border); 12 | 13 | $url-str: str-slice($border-str, 1, 3); 14 | $gradient-type: str-slice($border-str, 1, 6); 15 | 16 | @if $url-str == "url" { 17 | $webkit-border: $border; 18 | $spec-border: $border; 19 | } 20 | 21 | @else if $gradient-type == "linear" { 22 | $gradients: _linear-gradient-parser("#{$border}"); 23 | $webkit-border: map-get($gradients, webkit-image); 24 | $spec-border: map-get($gradients, spec-image); 25 | } 26 | 27 | @else if $gradient-type == "radial" { 28 | $gradients: _radial-gradient-parser("#{$border}"); 29 | $webkit-border: map-get($gradients, webkit-image); 30 | $spec-border: map-get($gradients, spec-image); 31 | } 32 | 33 | @else { 34 | $webkit-border: $border; 35 | $spec-border: $border; 36 | } 37 | } 38 | 39 | @else { 40 | $webkit-border: $border; 41 | $spec-border: $border; 42 | } 43 | 44 | $webkit-borders: append($webkit-borders, $webkit-border, comma); 45 | $spec-borders: append($spec-borders, $spec-border, comma); 46 | } 47 | 48 | -webkit-border-image: $webkit-borders; 49 | border-image: $spec-borders; 50 | border-style: solid; 51 | } 52 | 53 | //Examples: 54 | // @include border-image(url("image.png")); 55 | // @include border-image(url("image.png") 20 stretch); 56 | // @include border-image(linear-gradient(45deg, orange, yellow)); 57 | // @include border-image(linear-gradient(45deg, orange, yellow) stretch); 58 | // @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round); 59 | // @include border-image(radial-gradient(top, cover, orange, yellow, orange)); 60 | -------------------------------------------------------------------------------- /test/fixtures/home_path/03/bourbon/_font-source-declaration.scss: -------------------------------------------------------------------------------- 1 | // Used for creating the source string for fonts using @font-face 2 | // Reference: http://goo.gl/Ru1bKP 3 | 4 | @function font-url-prefixer($asset-pipeline) { 5 | @if $asset-pipeline == true { 6 | @return font-url; 7 | } @else { 8 | @return url; 9 | } 10 | } 11 | 12 | @function font-source-declaration( 13 | $font-family, 14 | $file-path, 15 | $asset-pipeline, 16 | $file-formats, 17 | $font-url) { 18 | 19 | $src: (); 20 | 21 | $formats-map: ( 22 | eot: "#{$file-path}.eot?#iefix" format("embedded-opentype"), 23 | woff2: "#{$file-path}.woff2" format("woff2"), 24 | woff: "#{$file-path}.woff" format("woff"), 25 | ttf: "#{$file-path}.ttf" format("truetype"), 26 | svg: "#{$file-path}.svg##{$font-family}" format("svg") 27 | ); 28 | 29 | @each $key, $values in $formats-map { 30 | @if contains($file-formats, $key) { 31 | $file-path: nth($values, 1); 32 | $font-format: nth($values, 2); 33 | 34 | @if $asset-pipeline == true { 35 | $src: append($src, font-url($file-path) $font-format, comma); 36 | } @else { 37 | $src: append($src, url($file-path) $font-format, comma); 38 | } 39 | } 40 | } 41 | 42 | @return $src; 43 | } 44 | -------------------------------------------------------------------------------- /test/fixtures/home_path/03/bourbon/_retina-image.scss: -------------------------------------------------------------------------------- 1 | @mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: $asset-pipeline) { 2 | @if $asset-pipeline { 3 | background-image: image-url("#{$filename}.#{$extension}"); 4 | } @else { 5 | background-image: url("#{$filename}.#{$extension}"); 6 | } 7 | 8 | @include hidpi { 9 | @if $asset-pipeline { 10 | @if $retina-filename { 11 | background-image: image-url("#{$retina-filename}.#{$extension}"); 12 | } @else { 13 | background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}"); 14 | } 15 | } @else { 16 | @if $retina-filename { 17 | background-image: url("#{$retina-filename}.#{$extension}"); 18 | } @else { 19 | background-image: url("#{$filename}#{$retina-suffix}.#{$extension}"); 20 | } 21 | } 22 | 23 | background-size: $background-size; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/01_gh_release.rb: -------------------------------------------------------------------------------- 1 | gh_release "trumbowyg", 2 | source: "torba-rb/Trumbowyg", 3 | tag: "1.1.6", 4 | import: %w[ 5 | dist/trumbowyg.js 6 | dist/ui/trumbowyg.css 7 | dist/ui/images/ 8 | ] 9 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/01_image_asset_not_specified.rb: -------------------------------------------------------------------------------- 1 | gh_release "trumbowyg", 2 | source: "torba-rb/Trumbowyg", 3 | tag: "1.1.6", 4 | import: %w[ 5 | dist/ui/trumbowyg.css 6 | dist/ui/images/icons.png 7 | ] 8 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/01_targz.rb: -------------------------------------------------------------------------------- 1 | targz "trumbowyg", 2 | url: "https://github.com/torba-rb/Trumbowyg/archive/1.1.6.tar.gz", 3 | import: %w[ 4 | dist/trumbowyg.js 5 | dist/ui/trumbowyg.css 6 | dist/ui/images/ 7 | ] 8 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/01_zip.rb: -------------------------------------------------------------------------------- 1 | zip "trumbowyg", 2 | url: "https://github.com/torba-rb/Trumbowyg/archive/1.1.6.zip", 3 | import: %w[ 4 | dist/trumbowyg.js 5 | dist/ui/trumbowyg.css 6 | dist/ui/images/ 7 | ] 8 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/02_npm.rb: -------------------------------------------------------------------------------- 1 | npm "lo_dash", package: "lodash", version: "0.1.0", import: ["lodash.js"] 2 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/03_not_existed_assets.rb: -------------------------------------------------------------------------------- 1 | npm package: 'bourbon', version: '4.2.6', import: %w[ 2 | app/assets/stylesheets/addons/_retina-image.scss 3 | app/assets/stylesheets/helpers/_font-source-declaration.scss 4 | app/assets/stylesheets/css3/_border-image.scss 5 | ] 6 | -------------------------------------------------------------------------------- /test/fixtures/torbafiles/04_similar_names.rb: -------------------------------------------------------------------------------- 1 | npm package: 'bourbon', version: '4.2.6' 2 | npm package: 'bourbon-neat', version: '1.7.4' 3 | -------------------------------------------------------------------------------- /test/import_list_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class ImportListTest < Minitest::Test 5 | def test_find_by_absolute_path 6 | item = ImportList::Asset.new("/dir/subdir/file.jpg", "file.jpg") 7 | list = ImportList.new([item]) 8 | 9 | found = list.find_by_absolute_path("/dir/subdir/file.jpg") 10 | assert_equal item, found 11 | end 12 | 13 | def test_find_by_absolute_path_missing 14 | list = ImportList.new([]) 15 | 16 | assert_raises(Errors::AssetNotFound) do 17 | list.find_by_absolute_path("file.jpg") 18 | end 19 | end 20 | 21 | def js_asset 22 | ImportList::Asset.new("/dir/script.js", "script.js") 23 | end 24 | 25 | def css_asset 26 | ImportList::Asset.new("/dir/stylesheet.css", "stylesheet.css") 27 | end 28 | 29 | def scss_asset 30 | ImportList::Asset.new("/dir/stylesheet.scss", "stylesheet.scss") 31 | end 32 | 33 | def sass_asset 34 | ImportList::Asset.new("/dir/stylesheet.sass", "stylesheet.sass") 35 | end 36 | 37 | def test_css_assets 38 | list = ImportList.new([js_asset, css_asset, scss_asset, sass_asset]) 39 | assert [css_asset, scss_asset, sass_asset], list.css_assets 40 | end 41 | 42 | def test_non_css_assets 43 | list = ImportList.new([js_asset, css_asset, scss_asset, sass_asset]) 44 | assert [js_asset], list.non_css_assets 45 | end 46 | 47 | def test_non_js_css_assets 48 | img_asset = ImportList::Asset.new("/dir/image.png", "image.png") 49 | list = ImportList.new([js_asset, css_asset, scss_asset, sass_asset, img_asset]) 50 | assert [img_asset], list.non_js_css_assets 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/manifest_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class ManifestTest < Minitest::Test 5 | def manifest 6 | @manifest ||= Manifest.new 7 | end 8 | 9 | def package 10 | manifest.packages.first 11 | end 12 | 13 | def remote 14 | package.remote_source 15 | end 16 | 17 | def test_zip 18 | manifest.zip "angular", url: "http://angularjs.com/angularjs.zip" 19 | 20 | assert_equal 1, manifest.packages.size 21 | assert_equal "angular", package.name 22 | assert_instance_of RemoteSources::Zip, remote 23 | assert_equal "http://angularjs.com/angularjs.zip", remote.url 24 | end 25 | 26 | def test_targz 27 | manifest.targz "angular", url: "http://angularjs.com/angularjs.targz" 28 | 29 | assert_equal 1, manifest.packages.size 30 | assert_equal "angular", package.name 31 | assert_instance_of RemoteSources::Targz, remote 32 | assert_equal "http://angularjs.com/angularjs.targz", remote.url 33 | end 34 | 35 | def test_zip_wo_url 36 | assert_raises(KeyError) do 37 | manifest.zip "angular" 38 | end 39 | end 40 | 41 | def test_gh_release 42 | manifest.gh_release "lo-dash", source: "jashkenas/underscore", tag: "1.8.3" 43 | 44 | assert_equal 1, manifest.packages.size 45 | assert_equal "lo-dash", package.name 46 | assert_instance_of RemoteSources::GithubRelease, remote 47 | assert_equal "jashkenas/underscore", remote.source 48 | assert_equal "1.8.3", remote.tag 49 | end 50 | 51 | def test_gh_release_implicit_name 52 | manifest.gh_release source: "jashkenas/underscore", tag: "1.8.3" 53 | assert_equal "underscore", package.name 54 | end 55 | 56 | def test_gh_release_wo_source 57 | assert_raises(KeyError) do 58 | manifest.gh_release "underscore", tag: "1.8.3" 59 | end 60 | end 61 | 62 | def test_gh_release_wo_tag 63 | assert_raises(KeyError) do 64 | manifest.gh_release "underscore", source: "jashkenas/underscore" 65 | end 66 | end 67 | 68 | def test_npm 69 | manifest.npm "coffee", package: "coffee-script", version: "1.8.3" 70 | 71 | assert_equal 1, manifest.packages.size 72 | assert_equal "coffee", package.name 73 | assert_instance_of RemoteSources::Npm, remote 74 | assert_equal "coffee-script", remote.package 75 | assert_equal "1.8.3", remote.version 76 | end 77 | 78 | def test_npm_implicit_name 79 | manifest.npm package: "coffee-script", version: "1.8.3" 80 | assert_equal "coffee-script", package.name 81 | end 82 | 83 | def test_npm_implicit_scoped_name 84 | manifest.npm package: "@lottiefiles/lottie-player", version: "1.8.3" 85 | assert_equal "@lottiefiles/lottie-player", package.name 86 | end 87 | 88 | def test_npm_wo_package 89 | assert_raises(KeyError) do 90 | manifest.npm version: "1.8.3" 91 | end 92 | end 93 | 94 | def test_npm_wo_version 95 | assert_raises(KeyError) do 96 | manifest.npm package: "underscore" 97 | end 98 | end 99 | 100 | def test_non_js_css_logical_paths 101 | manifest.zip "angular", url: "http://angularjs.com/angularjs.zip" 102 | manifest.zip "backbone", url: "http://backbonejs.com/backbonejs.zip" 103 | 104 | manifest.packages[0].stub :non_js_css_logical_paths, ["angular.png"] do 105 | manifest.packages[1].stub :non_js_css_logical_paths, ["backbone.png"] do 106 | assert_equal %w[angular.png backbone.png], manifest.non_js_css_logical_paths 107 | end 108 | end 109 | end 110 | 111 | def test_verify 112 | manifest.zip "angular", url: "http://angularjs.com/angularjs.zip" 113 | manifest.npm package: "coffee-script", version: "1.8.3" 114 | 115 | error = assert_raises(Torba::Errors::MissingPackages) { manifest.verify } 116 | 117 | assert_equal error.packages.map(&:name), %w(angular coffee-script) 118 | end 119 | 120 | def test_find_packages_by_name_empty 121 | assert_equal [], manifest.find_packages_by_name("coffee-script") 122 | end 123 | 124 | def test_find_packages_by_name_no_match 125 | manifest.npm "coffee", package: "coffee-script", version: "1.8.3" 126 | manifest.npm "angular", package: "angular", version: "1.3.5" 127 | assert_equal [], manifest.find_packages_by_name("coffee-script") 128 | end 129 | 130 | def test_find_packages_by_name_single_match 131 | manifest.npm "coffee", package: "coffee-script", version: "1.8.3" 132 | manifest.npm "angular", package: "angular", version: "1.3.5" 133 | assert_equal [manifest.packages[0]], manifest.find_packages_by_name("coffee") 134 | end 135 | 136 | def test_find_packages_by_name_multiple_match 137 | manifest.npm "angular", package: "angular", version: "1.3.5" 138 | manifest.npm "angular-routes", package: "angular-routes", version: "1.3.5" 139 | assert_equal manifest.packages, manifest.find_packages_by_name("angular") 140 | end 141 | 142 | def test_find_packages_by_name_ignorecase 143 | manifest.npm "Angular", package: "angular", version: "1.3.5" 144 | assert_equal manifest.packages, manifest.find_packages_by_name("angular") 145 | end 146 | end 147 | end 148 | -------------------------------------------------------------------------------- /test/package/import_list_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class PackageImportListTest < Minitest::Test 5 | def source_dir 6 | @source_dir ||= File.join(Torba.home_path, "source") 7 | end 8 | 9 | def touch(path) 10 | super File.join(source_dir, path) 11 | end 12 | 13 | def remote_source 14 | @remote_source ||= Test::RemoteSource.new(source_dir) 15 | end 16 | 17 | def test_single_file_path 18 | touch("hello.js") 19 | 20 | list = Package.new("package", remote_source, import: ["hello.js"]).import_list 21 | assert_equal 1, list.assets.size 22 | item = list.assets.first 23 | assert_equal "package/hello.js", item.logical_path 24 | assert_equal File.join(source_dir, "hello.js"), item.absolute_path 25 | end 26 | 27 | def test_single_file_path_with_subdir 28 | touch("build/hello.js") 29 | 30 | list = Package.new("package", remote_source, import: ["build/hello.js"]).import_list 31 | assert_equal 1, list.assets.size 32 | item = list.assets.first 33 | assert_equal "package/hello.js", item.logical_path 34 | assert_equal File.join(source_dir, "build/hello.js"), item.absolute_path 35 | 36 | touch("build/standalone/hello.js") 37 | 38 | list = Package.new("package", remote_source, import: ["build/standalone/hello.js"]).import_list 39 | assert_equal 1, list.assets.size 40 | item = list.assets.first 41 | assert_equal "package/hello.js", item.logical_path 42 | assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path 43 | end 44 | 45 | def test_directory 46 | touch("build/standalone/hello.js") 47 | 48 | list = Package.new("package", remote_source, import: ["build/"]).import_list 49 | assert_equal 1, list.assets.size 50 | item = list.assets.first 51 | assert_equal "package/standalone/hello.js", item.logical_path 52 | assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path 53 | 54 | 55 | list = Package.new("package", remote_source, import: ["build/standalone/"]).import_list 56 | assert_equal 1, list.assets.size 57 | item = list.assets.first 58 | assert_equal "package/hello.js", item.logical_path 59 | assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path 60 | end 61 | 62 | def test_multiple_files 63 | touch("build/images/first.png") 64 | touch("build/images/second.png") 65 | 66 | list = Package.new("package", remote_source, import: ["build/"]).import_list 67 | assert_equal 2, list.assets.size 68 | 69 | first_item = list.assets[0] 70 | assert_equal "package/images/first.png", first_item.logical_path 71 | assert_equal File.join(source_dir, "build/images/first.png"), first_item.absolute_path 72 | 73 | second_item = list.assets[1] 74 | assert_equal "package/images/second.png", second_item.logical_path 75 | assert_equal File.join(source_dir, "build/images/second.png"), second_item.absolute_path 76 | end 77 | 78 | def test_multiple_import_paths 79 | touch("images/one.jpg") 80 | touch("js/script.js") 81 | 82 | list = Package.new("package", remote_source, import: ["images/", "js/script.js"]).import_list 83 | assert_equal 2, list.assets.size 84 | 85 | first_item = list.assets[0] 86 | assert_equal "package/one.jpg", first_item.logical_path 87 | assert_equal File.join(source_dir, "images/one.jpg"), first_item.absolute_path 88 | 89 | second_item = list.assets[1] 90 | assert_equal "package/script.js", second_item.logical_path 91 | assert_equal File.join(source_dir, "js/script.js"), second_item.absolute_path 92 | end 93 | 94 | def test_glob_pattern 95 | touch("js/hello.js") 96 | touch("build/css/bye.css") 97 | 98 | list = Package.new("package", remote_source, import: ["**/*.{js,coffee}"]).import_list 99 | assert_equal 1, list.assets.size 100 | item = list.assets.first 101 | assert_equal "package/js/hello.js", item.logical_path 102 | assert_equal File.join(source_dir, "js/hello.js"), item.absolute_path 103 | 104 | list = Package.new("package", remote_source, import: ["build/**/*.css"]).import_list 105 | assert_equal 1, list.assets.size 106 | item = list.assets.first 107 | assert_equal "package/css/bye.css", item.logical_path 108 | assert_equal File.join(source_dir, "build/css/bye.css"), item.absolute_path 109 | end 110 | 111 | def test_missing_file 112 | assert_raises(Torba::Errors::NothingToImport) do 113 | Package.new("package", remote_source, import: ["hello.js"]).import_list 114 | end 115 | 116 | touch("hello.js") 117 | 118 | assert_raises(Torba::Errors::NothingToImport) do 119 | Package.new("package", remote_source, import: ["hello.js", "another_missing.js"]).import_list 120 | end 121 | end 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /test/package/logical_paths_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class PackageLogicalPathsTest < Minitest::Test 5 | def import_list 6 | js_asset = ImportList::Asset.new("/dir/script.js", "script.js") 7 | css_asset = ImportList::Asset.new("/dir/stylesheet.css", "stylesheet.css") 8 | img_asset = ImportList::Asset.new("/dir/image.svg", "image.svg") 9 | ImportList.new([js_asset, css_asset, img_asset]) 10 | end 11 | 12 | def package 13 | @package ||= Package.new("hello", nil) 14 | end 15 | 16 | def test_logical_paths 17 | package.stub :import_list, import_list do 18 | assert_equal %w[script.js stylesheet.css image.svg], package.logical_paths 19 | end 20 | end 21 | 22 | def test_non_js_css_logical_paths 23 | package.stub :import_list, import_list do 24 | assert_equal %w[image.svg], package.non_js_css_logical_paths 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/package_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class PackageTest < Minitest::Test 5 | def source_dir 6 | @source_dir ||= File.join(Torba.home_path, "source") 7 | end 8 | 9 | def touch(path) 10 | super File.join(source_dir, path) 11 | end 12 | 13 | def fixture(path, content = nil) 14 | touch(path) 15 | if content 16 | File.write(File.join(source_dir, path), content) 17 | end 18 | end 19 | 20 | def test_package_css_with_urls 21 | touch "image.png" 22 | fixture "hello.css" , "body {\nbackground-image: url(image.png)\n}" 23 | source = Torba::Test::RemoteSource.new(source_dir) 24 | package = Package.new("package", source, import: ["hello.css", "image.png"]) 25 | 26 | package.build 27 | 28 | assert_exists File.join(package.load_path, "package", "hello.css.erb") 29 | assert_exists File.join(package.load_path, "package", "image.png") 30 | end 31 | 32 | def test_package_css_without_urls 33 | fixture "hello.css" 34 | source = Torba::Test::RemoteSource.new(source_dir) 35 | package = Package.new("package", source, import: ["hello.css"]) 36 | 37 | package.build 38 | 39 | assert_exists File.join(package.load_path, "package", "hello.css") 40 | end 41 | 42 | def test_package_css_with_urls_pointing_to_nonexisting_assets 43 | fixture "hello.css", "/*body {\nbackground-image: url(image.png)\n}*/" 44 | source = Torba::Test::RemoteSource.new(source_dir) 45 | package = Package.new("package", source, import: ["hello.css"]) 46 | 47 | package.build 48 | 49 | assert_exists File.join(package.load_path, "package", "hello.css") 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/rake_task_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "torba/rake_task" 3 | 4 | module Torba 5 | class RakeTaskTest < Minitest::Test 6 | include Rake::DSL 7 | 8 | def setup 9 | task "environment" 10 | task "assets:precompile" => "environment" 11 | Torba::RakeTask.new("torba:pack", :before => "assets:precompile") 12 | end 13 | 14 | def teardown 15 | Rake::Task.clear 16 | end 17 | 18 | def test_torba_pack_task_is_defined 19 | assert(Rake::Task.task_defined?("torba:pack")) 20 | end 21 | 22 | def test_torba_pack_task_has_description 23 | torba_task = Rake::Task["torba:pack"] 24 | torba_task.comment =~ /Torbafile/ 25 | end 26 | 27 | def test_torba_pack_is_installed_as_prerequisite 28 | precompile_task = Rake::Task["assets:precompile"] 29 | assert_equal(%w(environment torba:pack), precompile_task.prerequisites) 30 | end 31 | 32 | def test_invoking_torba_pack 33 | Torba.expects(:pack).once 34 | Rake::Task["torba:pack"].invoke 35 | end 36 | 37 | def test_calls_a_block_before_torba_pack 38 | Rake::Task.clear 39 | 40 | configuration = Object.new 41 | 42 | order = sequence("correct order") 43 | configuration.expects(:setup).in_sequence(order) 44 | Torba.expects(:pack).in_sequence(order) 45 | 46 | task = Torba::RakeTask.new("torba:pack") { configuration.setup } 47 | Rake::Task["torba:pack"].invoke 48 | end 49 | 50 | def test_defines_task_if_doesnt_exist 51 | Rake::Task.clear 52 | Torba::RakeTask.new("torba:pack", :before => "something:else") 53 | 54 | assert(Rake::Task.task_defined?("something:else")) 55 | other_task = Rake::Task["something:else"] 56 | assert_equal(%w(torba:pack), other_task.prerequisites) 57 | end 58 | 59 | def test_alternative_task_name_can_be_specified 60 | Rake::Task.clear 61 | Torba::RakeTask.new("package") 62 | 63 | assert(Rake::Task.task_defined?("package")) 64 | 65 | Torba.expects(:pack).once 66 | Rake::Task["package"].invoke 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/remote_sources/common_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "fileutils" 3 | 4 | module Torba 5 | class CommonRemoteSourceTest < Minitest::Test 6 | def cache_path 7 | Torba.home_path 8 | end 9 | 10 | def remote 11 | @remote ||= Test::RemoteSource.new(cache_path) 12 | end 13 | 14 | def touch(path) 15 | super File.join(cache_path, path) 16 | end 17 | 18 | def assert_valid_tuple(path, tuple) 19 | assert_equal File.join(cache_path, path), tuple[0] 20 | assert_equal path, tuple[1] 21 | end 22 | 23 | def test_glob 24 | touch "one.jpg" 25 | touch "two.jpg" 26 | 27 | tuples = remote["*"] 28 | assert_equal 2, tuples.size 29 | 30 | tuple1, tuple2 = tuples 31 | assert_valid_tuple "one.jpg", tuple1 32 | assert_valid_tuple "two.jpg", tuple2 33 | end 34 | 35 | def test_no_directories 36 | touch "hello/one.jpg" 37 | 38 | tuples = remote["*"] 39 | assert_equal 0, remote["*"].size 40 | 41 | tuples = remote["**/*"] 42 | assert_equal 1, tuples.size 43 | 44 | assert_valid_tuple "hello/one.jpg", tuples.first 45 | end 46 | 47 | def test_always_absolute_path 48 | touch "hello/one.jpg" 49 | 50 | Dir.chdir(cache_path) do 51 | remote = Test::RemoteSource.new("./hello") 52 | 53 | tuples = remote["*"] 54 | assert_equal 1, tuples.size 55 | 56 | tuple = tuples.first 57 | assert_equal File.join(cache_path, "hello/one.jpg"), tuple[0] 58 | assert_equal "one.jpg", tuple[1] 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /test/remote_sources/get_file_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class GetFileTest < Minitest::Test 5 | def test_404 6 | exception = assert_raises(Errors::ShellCommandFailed) do 7 | RemoteSources::GetFile.process("http://jquery.com/jquery.zip") 8 | end 9 | assert_includes exception.message, "curl" 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/remote_sources/github_release_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class GithubReleaseTest < Minitest::Test 5 | def remote 6 | RemoteSources::GithubRelease.new("user/repo", "v.2.0.0") 7 | end 8 | 9 | def test_source 10 | assert_equal "user/repo", remote.source 11 | end 12 | 13 | def test_repository_name 14 | assert_equal "repo", remote.repository_name 15 | end 16 | 17 | def test_repository_user 18 | assert_equal "user", remote.repository_user 19 | end 20 | 21 | def test_tag 22 | assert_equal "v.2.0.0", remote.tag 23 | end 24 | 25 | def test_url 26 | assert_equal "https://github.com/user/repo/archive/v.2.0.0.zip", remote.url 27 | end 28 | 29 | def test_unique_digest 30 | remote = RemoteSources::GithubRelease.new("user/repo", "v.2.0.0") 31 | same_remote = RemoteSources::GithubRelease.new("user/repo", "v.2.0.0") 32 | assert_equal remote.digest, same_remote.digest 33 | 34 | another_remote = RemoteSources::GithubRelease.new("another/repo", "v.2.0.0") 35 | refute_equal remote.digest, another_remote.digest 36 | end 37 | 38 | def test_digest_contains_repository_name 39 | assert_match /^repo-/, remote.digest 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/remote_sources/npm_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class NpmTest < Minitest::Test 5 | def remote 6 | RemoteSources::Npm.new("coffee-script", "2.0.0") 7 | end 8 | 9 | def test_package 10 | assert_equal "coffee-script", remote.package 11 | end 12 | 13 | def test_tag 14 | assert_equal "2.0.0", remote.version 15 | end 16 | 17 | def test_url 18 | assert_equal "https://registry.npmjs.org/coffee-script/-/coffee-script-2.0.0.tgz", remote.url 19 | end 20 | 21 | def test_unique_digest 22 | remote = RemoteSources::Npm.new("coffee-script", "2.0.0") 23 | same_remote = RemoteSources::Npm.new("coffee-script", "2.0.0") 24 | assert_equal remote.digest, same_remote.digest 25 | 26 | another_remote = RemoteSources::Npm.new("coffee-script", "2.0.1") 27 | refute_equal remote.digest, another_remote.digest 28 | end 29 | 30 | def test_digest_contains_repository_name 31 | assert_match /^coffee-script-/, remote.digest 32 | end 33 | 34 | def test_scoped_repo 35 | remote = RemoteSources::Npm.new("@lottiefiles/lottie-player", "2.0.2") 36 | assert_equal "@lottiefiles/lottie-player", remote.package 37 | assert_equal "2.0.2", remote.version 38 | assert_equal "https://registry.npmjs.org/@lottiefiles/lottie-player/-/lottie-player-2.0.2.tgz", remote.url 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /test/remote_sources/targz_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class TargzTest < Minitest::Test 5 | def test_url 6 | remote = RemoteSources::Targz.new("http://jquery.com/jquery.tar.gz") 7 | assert_equal "http://jquery.com/jquery.tar.gz", remote.url 8 | end 9 | 10 | def test_unique_digest 11 | remote = RemoteSources::Targz.new("http://jquery.com/jquery.tar.gz") 12 | same_remote = RemoteSources::Targz.new("http://jquery.com/jquery.tar.gz") 13 | 14 | assert_equal remote.digest, same_remote.digest 15 | 16 | another_remote = RemoteSources::Targz.new("http://zeptojs.com/jquery.tar.gz") 17 | 18 | refute_equal remote.digest, another_remote.digest 19 | end 20 | 21 | def test_digest_contains_filename 22 | remote = RemoteSources::Targz.new("http://jquery.com/jquery.tar.gz") 23 | assert_match /^jquery-/, remote.digest 24 | 25 | remote = RemoteSources::Targz.new("http://jquery.com/jquery.tgz") 26 | assert_match /^jquery-/, remote.digest 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /test/remote_sources/zip_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Torba 4 | class ZipTest < Minitest::Test 5 | def test_url 6 | remote = RemoteSources::Zip.new("http://jquery.com/jquery.zip") 7 | assert_equal "http://jquery.com/jquery.zip", remote.url 8 | end 9 | 10 | def test_unique_digest 11 | remote = RemoteSources::Zip.new("http://jquery.com/jquery.zip") 12 | same_remote = RemoteSources::Zip.new("http://jquery.com/jquery.zip") 13 | 14 | assert_equal remote.digest, same_remote.digest 15 | 16 | another_remote = RemoteSources::Zip.new("http://angularjs.com/angular.zip") 17 | 18 | refute_equal remote.digest, another_remote.digest 19 | end 20 | 21 | def test_digest_contains_filename 22 | remote = RemoteSources::Zip.new("http://jquery.com/jquery.zip") 23 | assert_match /^jquery-/, remote.digest 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "bundler/setup" 2 | require "torba" 3 | require "torba/remote_sources/common" 4 | 5 | require "minitest/autorun" 6 | require "minitest/assert_dirs_equal" 7 | require "mocha/minitest" 8 | require "tmpdir" 9 | require "fileutils" 10 | require "open3" 11 | 12 | module Torba 13 | module Test 14 | module TempHome 15 | attr_reader :torba_tmp_dir 16 | 17 | def self.persistent_tmp_dir 18 | @persistent_tmp_dir ||= File.realpath(Dir.mktmpdir("torba")) 19 | end 20 | 21 | def before_setup 22 | Torba.home_path = @torba_tmp_dir = File.realpath(Dir.mktmpdir("torba")) 23 | super 24 | end 25 | 26 | def after_teardown 27 | FileUtils.rm_rf(torba_tmp_dir) 28 | Torba.home_path = nil 29 | super 30 | end 31 | 32 | Minitest.after_run do 33 | FileUtils.rm_rf(persistent_tmp_dir) 34 | end 35 | end 36 | 37 | module FileSystem 38 | def assert_exists(file_path) 39 | assert File.exists?(file_path) 40 | end 41 | 42 | def refute_exists(file_path) 43 | refute File.exists?(file_path) 44 | end 45 | 46 | def touch(path) 47 | FileUtils.mkdir_p(File.dirname(path)) 48 | FileUtils.touch(path) 49 | end 50 | 51 | def path_to_packaged(name, home = torba_tmp_dir) 52 | path = Dir.glob("#{home}/*").sort.grep(Regexp.new(name)).first 53 | assert path, "Couldn't find packaged #{name.inspect} in #{home.inspect}" 54 | path 55 | end 56 | end 57 | 58 | module Exec 59 | def torba(torba_cmd, options = {}) 60 | env = {"TORBA_HOME_PATH" => torba_tmp_dir, "TORBA_CACHE_PATH" => TempHome.persistent_tmp_dir} 61 | 62 | if (torbafile = options.delete(:torbafile)) 63 | env.merge!("TORBA_FILE" => "test/fixtures/torbafiles/#{torbafile}") 64 | end 65 | 66 | if (home_path = options.delete(:home_path)) 67 | env.merge!("TORBA_HOME_PATH" => home_path) 68 | end 69 | 70 | if (cache_path = options.delete(:cache_path)) 71 | env.merge!("TORBA_CACHE_PATH" => cache_path) 72 | end 73 | 74 | if (additional_env = options.delete(:env)) 75 | env.merge!(additional_env) 76 | end 77 | 78 | cmd = "ruby bin/torba #{torba_cmd}" 79 | Open3.capture3(env, cmd, options) 80 | end 81 | 82 | def skip_java_capture3_bug 83 | if RUBY_PLATFORM == "java" 84 | skip "Skipping test due to Open3.capture3 bug in Jruby" 85 | end 86 | end 87 | end 88 | 89 | class RemoteSource 90 | include RemoteSources::Common 91 | 92 | attr_reader :cache_path 93 | 94 | def initialize(cache_path) 95 | @cache_path = cache_path 96 | end 97 | 98 | def ensure_cached; end 99 | 100 | def digest; '' end 101 | end 102 | end 103 | end 104 | 105 | class Minitest::Test 106 | include Torba::Test::TempHome 107 | include Torba::Test::FileSystem 108 | include Torba::Test::Exec 109 | end 110 | -------------------------------------------------------------------------------- /test/torba_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TorbaTest < Minitest::Test 4 | def test_home_path 5 | Torba.home_path = "/new/home/path" 6 | assert_equal "/new/home/path", Torba.home_path 7 | end 8 | 9 | def test_cache_path 10 | Torba.cache_path = "/new/cache/path" 11 | assert_equal "/new/cache/path", Torba.cache_path 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /torba.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |spec| 2 | spec.name = "torba" 3 | spec.version = "1.2.0" 4 | spec.authors = ["Andrii Malyshko"] 5 | spec.email = ["mail@nashbridges.me"] 6 | spec.description = "Bundler for Sprockets" 7 | spec.summary = spec.description 8 | spec.homepage = "https://github.com/torba-rb/torba" 9 | spec.license = "MIT" 10 | 11 | spec.files = `git ls-files`.split($/).grep_v(%r{^test/}) 12 | spec.executables = spec.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 13 | spec.bindir = "bin" 14 | spec.require_paths = ["lib"] 15 | 16 | spec.add_dependency "thor", ">= 0.19.1", "< 2" 17 | 18 | spec.add_development_dependency "bundler" 19 | spec.add_development_dependency "rake" 20 | spec.add_development_dependency "minitest", "~> 5.4" 21 | spec.add_development_dependency "mocha" 22 | spec.add_development_dependency "assert_dirs_equal", "~> 0.1" 23 | end 24 | --------------------------------------------------------------------------------