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.
@@ -0,0 +1,7 @@
1
+ module Torba
2
+ class Engine < Rails::Engine
3
+ initializer "torba.assets" do |app|
4
+ Rails.application.config.assets.paths.concat(Torba.load_path)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module Torba
2
+ module RemoteSources
3
+ module Common
4
+ # Expands pattern inside content of the remote source.
5
+ # @return [Array<Array(String, String)>] where first element of the tuple is an absolute
6
+ # path to cached file and second element is the same path relative to cache directory.
7
+ # @param pattern [String] see {http://www.rubydoc.info/stdlib/core/Dir#glob-class_method Dir.glob}
8
+ # for pattern examples
9
+ # @note Unlike Dir.glob doesn't include directories.
10
+ def [](pattern)
11
+ ensure_cached
12
+
13
+ Dir.glob(File.join(cache_path, pattern)).reject{ |path| File.directory?(path) }.map do |path|
14
+ [path, path.sub(/#{cache_path}\/?/, "")]
15
+ end
16
+ end
17
+
18
+ def digest
19
+ raise NotImplementedError
20
+ end
21
+
22
+ private
23
+
24
+ def cache_path
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def ensure_cached
29
+ raise NotImplementedError
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ require "torba/remote_sources/zip"
2
+
3
+ module Torba
4
+ module RemoteSources
5
+ # Represents {https://help.github.com/articles/about-releases/ Github release}.
6
+ class GithubRelease < Zip
7
+ # @return [String] repository user and name.
8
+ # @example
9
+ # "jashkenas/underscore"
10
+ attr_reader :source
11
+
12
+ # @return [String] repository tag.
13
+ # @example
14
+ # "v1.8.3"
15
+ attr_reader :tag
16
+
17
+ # @param source see {#source}
18
+ # @param tag see {#tag}
19
+ def initialize(source, tag)
20
+ @source = source
21
+ @tag = tag
22
+ super("https://github.com/#{source}/archive/#{tag}.zip")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,65 @@
1
+ require "tempfile"
2
+ require "fileutils"
3
+
4
+ require "torba/remote_sources/common"
5
+
6
+ module Torba
7
+ module Errors
8
+ ShellCommandFailed = Class.new(StandardError)
9
+ end
10
+
11
+ module RemoteSources
12
+ # Represents remote zip archive.
13
+ class Zip
14
+ include Common
15
+
16
+ attr_reader :url
17
+
18
+ def initialize(url)
19
+ @url = url
20
+ end
21
+
22
+ def digest
23
+ Torba.digest(url)
24
+ end
25
+
26
+ private
27
+
28
+ def cache_path
29
+ @cache_path ||= File.join(Torba.cache_path, digest)
30
+ end
31
+
32
+ def ensure_cached
33
+ unless Dir.exist?(cache_path)
34
+ FileUtils.mkdir_p(cache_path)
35
+
36
+ tempfile = Tempfile.new("torba")
37
+ tempfile.close
38
+
39
+ Torba.ui.info "downloading '#{url}'"
40
+
41
+ [
42
+ "curl -L -o #{tempfile.path} #{url}",
43
+ "unzip -oqq -d #{cache_path} #{tempfile.path}",
44
+ ].each do |command|
45
+ system(command) || raise(Errors::ShellCommandFailed.new(command))
46
+ end
47
+
48
+ get_rid_of_top_level_directory
49
+ end
50
+ rescue
51
+ FileUtils.rm_rf(cache_path)
52
+ raise
53
+ end
54
+
55
+ def get_rid_of_top_level_directory
56
+ top_level_content = Dir.glob("#{cache_path}/*")
57
+ if top_level_content.size == 1 && File.directory?(top_level_content.first)
58
+ top_level_dir = top_level_content.first
59
+ FileUtils.cp_r("#{top_level_dir}/.", cache_path)
60
+ FileUtils.rm_rf(top_level_dir)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
data/lib/torba/ui.rb ADDED
@@ -0,0 +1,26 @@
1
+ require "thor/shell"
2
+
3
+ module Torba
4
+ # Thin wrapper around Thor::Shell.
5
+ class Ui
6
+ def initialize
7
+ @shell = Thor::Base.shell.new
8
+ end
9
+
10
+ def info(message)
11
+ @shell.say(message)
12
+ end
13
+
14
+ def confirm(message)
15
+ @shell.say(message, :green)
16
+ end
17
+
18
+ def suggest(message)
19
+ @shell.say(message, :yellow)
20
+ end
21
+
22
+ def error(message)
23
+ @shell.say(message, :red)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,2 @@
1
+ require "torba"
2
+ Torba.pretty_errors { Torba.verify }
data/lib/torba.rb ADDED
@@ -0,0 +1,80 @@
1
+ require "digest"
2
+
3
+ require "torba/ui"
4
+ require "torba/manifest"
5
+
6
+ # @since 0.1.0
7
+ module Torba
8
+ # @return [String] root path to prepared asset packages. By default it's ".torba" within
9
+ # your OS home directory (i.e. packages are shared between projects).
10
+ # @note use "TORBA_HOME_PATH" env variable to override default value
11
+ def self.home_path
12
+ @home_path ||= ENV["TORBA_HOME_PATH"] || File.join(Dir.home, ".torba")
13
+ end
14
+
15
+ # Override home path with a new value
16
+ # @param val [String] new home path
17
+ # @return [void]
18
+ def self.home_path=(val)
19
+ @home_path = val
20
+ end
21
+
22
+ # @return [String] root path to downloaded yet unprocessed asset packages. By default
23
+ # it's "cache" within {.home_path}.
24
+ # @note use "TORBA_CACHE_PATH" env variable to override default value
25
+ def self.cache_path
26
+ @cache_path ||= ENV["TORBA_CACHE_PATH"] || File.join(home_path, "cache")
27
+ end
28
+
29
+ # @return [Ui]
30
+ def self.ui
31
+ @ui ||= Ui.new
32
+ end
33
+
34
+ # @return [Manifest]
35
+ def self.manifest
36
+ @manifest ||= Manifest.build
37
+ end
38
+
39
+ # @see Manifest#pack
40
+ def self.pack
41
+ manifest.pack
42
+ end
43
+
44
+ # @see Manifest#load_path
45
+ def self.load_path
46
+ manifest.load_path
47
+ end
48
+
49
+ # @see Manifest#verify
50
+ def self.verify
51
+ manifest.verify
52
+ end
53
+
54
+ # @return [String] unique short fingerprint/hash for given string
55
+ # @param [String] string to be hashed
56
+ #
57
+ # @example
58
+ # Torba.digest("path/to/hash") #=> "23e3e63c"
59
+ def self.digest(string)
60
+ Digest::SHA1.hexdigest(string)[0..7]
61
+ end
62
+
63
+ # @yield a block, converts common exceptions into useful messages
64
+ def self.pretty_errors
65
+ yield
66
+ rescue Torba::Errors::UnbuiltPackage
67
+ ui.error "Your Torba is not packed yet."
68
+ ui.suggest "Run `bundle exec torba pack` to install missing packages."
69
+ exit(false)
70
+ rescue Torba::Errors::NothingToImport => e
71
+ ui.error "Couldn't import an asset(-s) '#{e.path}' from import list in '#{e.package}'."
72
+ ui.suggest "Check for typos."
73
+ ui.suggest "Make sure that the path has trailing '/' if its a directory."
74
+ exit(false)
75
+ rescue Torba::Errors::AssetNotFound => e
76
+ ui.error "Couldn't found an asset with path '#{e.message}'."
77
+ ui.suggest "Make sure that you've imported all image assets mentioned in a stylesheet(-s)."
78
+ exit(false)
79
+ end
80
+ end
data/test/Torbafile ADDED
@@ -0,0 +1,5 @@
1
+ gh_release "trumbowyg", source: "torba-rb/Trumbowyg", tag: "1.1.7", import: %w[
2
+ dist/trumbowyg.js
3
+ dist/ui/*.css
4
+ dist/ui/images/
5
+ ]
@@ -0,0 +1,26 @@
1
+ require "test_helper"
2
+
3
+ module Torba
4
+ class AcceptanceTest < Minitest::Test
5
+ def manifest
6
+ @manifest ||= Manifest.build("test/Torbafile")
7
+ end
8
+
9
+ def assert_exists(file_path)
10
+ super File.join(manifest.load_path, file_path)
11
+ end
12
+
13
+ def test_pack
14
+ manifest.pack
15
+
16
+ assert_exists "trumbowyg/trumbowyg.js"
17
+ assert_exists "trumbowyg/trumbowyg.css.erb"
18
+ assert_exists "trumbowyg/icons.png"
19
+ assert_exists "trumbowyg/icons-2x.png"
20
+
21
+ css = File.read File.join(manifest.load_path, "trumbowyg/trumbowyg.css.erb")
22
+ assert_includes css, "background: transparent url('<%= asset_path('trumbowyg/icons.png') %>') no-repeat;"
23
+ assert_includes css, "background-image: url('<%= asset_path('trumbowyg/icons-2x.png') %>')"
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,75 @@
1
+ require "test_helper"
2
+
3
+ module Torba
4
+ class CssUrlToErbAssetPathTest < Minitest::Test
5
+ def filter
6
+ CssUrlToErbAssetPath
7
+ end
8
+
9
+ def test_no_url
10
+ css = "p { margin: 0; }"
11
+ assert_equal css, filter.call(css, "/current/file")
12
+ end
13
+
14
+ def test_url_with_data
15
+ css = "background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaskip);"
16
+ assert_equal css, filter.call(css, "/current/file")
17
+ end
18
+
19
+ def test_absolute_url
20
+ css = "background-image: url('/icons.png');"
21
+ assert_equal "background-image: url('/icons.png');", filter.call(css, "/current_file")
22
+ end
23
+
24
+ def test_stylesheet_and_image_in_same_directory
25
+ current_file = "/home/package/dist/ui/stylesheet.css"
26
+ css = "background-image: url('icons.png');"
27
+
28
+ new_content = filter.call(css, current_file) do |image_full_url|
29
+ assert_equal "/home/package/dist/ui/icons.png", image_full_url
30
+ "ui/icons.png"
31
+ end
32
+ assert_equal "background-image: url('<%= asset_path('ui/icons.png') %>');", new_content
33
+ end
34
+
35
+ def test_url_double_quotes
36
+ current_file = "/home/package/dist/ui/stylesheet.css"
37
+ css = 'background-image: url("icons.png");'
38
+
39
+ filter.call(css, current_file) do |image_full_url|
40
+ assert_equal "/home/package/dist/ui/icons.png", image_full_url
41
+ end
42
+ end
43
+
44
+ def test_minified
45
+ current_file = "/home/package/dist/ui/stylesheet.css"
46
+ css = ".image{background-image:url(icons.png)}.border{border(1px solid)};"
47
+
48
+ filter.call(css, current_file) do |image_full_url|
49
+ assert_equal "/home/package/dist/ui/icons.png", image_full_url
50
+ end
51
+ end
52
+
53
+ def test_stylesheet_and_image_in_sibling_directories
54
+ current_file = "/home/package/dist/ui/stylesheet.css"
55
+ css = "background-image: url('../images/icons.png');"
56
+
57
+ new_content = filter.call(css, current_file) do |image_full_url|
58
+ assert_equal "/home/package/dist/images/icons.png", image_full_url
59
+ "images/icons.png"
60
+ end
61
+ assert_equal "background-image: url('<%= asset_path('images/icons.png') %>');", new_content
62
+ end
63
+
64
+ def test_image_in_child_directory_of_stylesheet
65
+ current_file = "/home/package/dist/ui/stylesheet.css"
66
+ css = "background-image: url('./images/icons.png');"
67
+
68
+ new_content = filter.call(css, current_file) do |image_full_url|
69
+ assert_equal "/home/package/dist/ui/images/icons.png", image_full_url
70
+ "ui/images/icons.png"
71
+ end
72
+ assert_equal "background-image: url('<%= asset_path('ui/images/icons.png') %>');", new_content
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,39 @@
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 test_css_assets
30
+ list = ImportList.new([js_asset, css_asset])
31
+ assert [css_asset], list.css_assets
32
+ end
33
+
34
+ def test_non_css_assets
35
+ list = ImportList.new([js_asset, css_asset])
36
+ assert [js_asset], list.non_css_assets
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,54 @@
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 test_zip
10
+ manifest.zip "angular", url: "http://angularjs.com/angularjs.zip"
11
+
12
+ assert_equal 1, manifest.packages.size
13
+
14
+ package = manifest.packages.first
15
+ assert_equal "angular", package.name
16
+
17
+ remote = package.remote_source
18
+ assert_instance_of RemoteSources::Zip, remote
19
+ assert_equal "http://angularjs.com/angularjs.zip", remote.url
20
+ end
21
+
22
+ def test_zip_wo_url
23
+ assert_raises(KeyError) do
24
+ manifest.zip "angular"
25
+ end
26
+ end
27
+
28
+ def test_gh_release
29
+ manifest.gh_release "underscore", source: "jashkenas/underscore", tag: "1.8.3"
30
+
31
+ assert_equal 1, manifest.packages.size
32
+
33
+ package = manifest.packages.first
34
+ assert_equal "underscore", package.name
35
+
36
+ remote = package.remote_source
37
+ assert_instance_of RemoteSources::GithubRelease, remote
38
+ assert_equal "jashkenas/underscore", remote.source
39
+ assert_equal "1.8.3", remote.tag
40
+ end
41
+
42
+ def test_gh_release_wo_source
43
+ assert_raises(KeyError) do
44
+ manifest.gh_release "underscore", tag: "1.8.3"
45
+ end
46
+ end
47
+
48
+ def test_gh_release_wo_tag
49
+ assert_raises(KeyError) do
50
+ manifest.gh_release "underscore", source: "jashkenas/underscore"
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,143 @@
1
+ require "test_helper"
2
+ require "fileutils"
3
+ require "torba/remote_sources/common"
4
+
5
+ module Torba
6
+ class PackageImportListTest < Minitest::Test
7
+ def source_dir
8
+ @source_dir ||= begin
9
+ dir = File.join(Torba.home_path, "source")
10
+ FileUtils.mkdir_p(dir)
11
+ dir
12
+ end
13
+ end
14
+
15
+ def touch(path)
16
+ absolute_path = File.join(source_dir, path)
17
+ FileUtils.mkdir_p(File.dirname(absolute_path))
18
+ FileUtils.touch(absolute_path)
19
+ end
20
+
21
+ class RemoteSource
22
+ include RemoteSources::Common
23
+
24
+ attr_reader :cache_path
25
+
26
+ def initialize(cache_path)
27
+ @cache_path = cache_path
28
+ end
29
+
30
+ def ensure_cached; end
31
+ end
32
+
33
+ def remote_source
34
+ @remote_source ||= RemoteSource.new(source_dir)
35
+ end
36
+
37
+ def test_single_file_path
38
+ touch("hello.js")
39
+
40
+ list = Package.new("package", remote_source, import: ["hello.js"]).import_list
41
+ assert_equal 1, list.assets.size
42
+ item = list.assets.first
43
+ assert_equal "hello.js", item.subpath
44
+ assert_equal File.join(source_dir, "hello.js"), item.absolute_path
45
+ end
46
+
47
+ def test_single_file_path_with_subdir
48
+ touch("build/hello.js")
49
+
50
+ list = Package.new("package", remote_source, import: ["build/hello.js"]).import_list
51
+ assert_equal 1, list.assets.size
52
+ item = list.assets.first
53
+ assert_equal "hello.js", item.subpath
54
+ assert_equal File.join(source_dir, "build/hello.js"), item.absolute_path
55
+
56
+ touch("build/standalone/hello.js")
57
+
58
+ list = Package.new("package", remote_source, import: ["build/standalone/hello.js"]).import_list
59
+ assert_equal 1, list.assets.size
60
+ item = list.assets.first
61
+ assert_equal "hello.js", item.subpath
62
+ assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path
63
+ end
64
+
65
+ def test_directory
66
+ touch("build/standalone/hello.js")
67
+
68
+ list = Package.new("package", remote_source, import: ["build/"]).import_list
69
+ assert_equal 1, list.assets.size
70
+ item = list.assets.first
71
+ assert_equal "standalone/hello.js", item.subpath
72
+ assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path
73
+
74
+
75
+ list = Package.new("package", remote_source, import: ["build/standalone/"]).import_list
76
+ assert_equal 1, list.assets.size
77
+ item = list.assets.first
78
+ assert_equal "hello.js", item.subpath
79
+ assert_equal File.join(source_dir, "build/standalone/hello.js"), item.absolute_path
80
+ end
81
+
82
+ def test_multiple_files
83
+ touch("build/images/first.png")
84
+ touch("build/images/second.png")
85
+
86
+ list = Package.new("package", remote_source, import: ["build/"]).import_list
87
+ assert_equal 2, list.assets.size
88
+
89
+ first_item = list.assets[0]
90
+ assert_equal "images/first.png", first_item.subpath
91
+ assert_equal File.join(source_dir, "build/images/first.png"), first_item.absolute_path
92
+
93
+ second_item = list.assets[1]
94
+ assert_equal "images/second.png", second_item.subpath
95
+ assert_equal File.join(source_dir, "build/images/second.png"), second_item.absolute_path
96
+ end
97
+
98
+ def test_multiple_import_paths
99
+ touch("images/one.jpg")
100
+ touch("js/script.js")
101
+
102
+ list = Package.new("package", remote_source, import: ["images/", "js/script.js"]).import_list
103
+ assert_equal 2, list.assets.size
104
+
105
+ first_item = list.assets[0]
106
+ assert_equal "one.jpg", first_item.subpath
107
+ assert_equal File.join(source_dir, "images/one.jpg"), first_item.absolute_path
108
+
109
+ second_item = list.assets[1]
110
+ assert_equal "script.js", second_item.subpath
111
+ assert_equal File.join(source_dir, "js/script.js"), second_item.absolute_path
112
+ end
113
+
114
+ def test_glob_pattern
115
+ touch("js/hello.js")
116
+ touch("build/css/bye.css")
117
+
118
+ list = Package.new("package", remote_source, import: ["**/*.{js,coffee}"]).import_list
119
+ assert_equal 1, list.assets.size
120
+ item = list.assets.first
121
+ assert_equal "js/hello.js", item.subpath
122
+ assert_equal File.join(source_dir, "js/hello.js"), item.absolute_path
123
+
124
+ list = Package.new("package", remote_source, import: ["build/**/*.css"]).import_list
125
+ assert_equal 1, list.assets.size
126
+ item = list.assets.first
127
+ assert_equal "css/bye.css", item.subpath
128
+ assert_equal File.join(source_dir, "build/css/bye.css"), item.absolute_path
129
+ end
130
+
131
+ def test_missing_file
132
+ assert_raises(Torba::Errors::NothingToImport) do
133
+ Package.new("package", remote_source, import: ["hello.js"]).import_list
134
+ end
135
+
136
+ touch("hello.js")
137
+
138
+ assert_raises(Torba::Errors::NothingToImport) do
139
+ Package.new("package", remote_source, import: ["hello.js", "another_missing.js"]).import_list
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,21 @@
1
+ require "test_helper"
2
+
3
+ module Torba
4
+ class GithubReleaseTest < Minitest::Test
5
+ def remote
6
+ RemoteSources::GithubRelease.new("repo/user", "v.2.0.0")
7
+ end
8
+
9
+ def test_source
10
+ assert_equal "repo/user", remote.source
11
+ end
12
+
13
+ def test_tag
14
+ assert_equal "v.2.0.0", remote.tag
15
+ end
16
+
17
+ def test_url
18
+ assert_equal "https://github.com/repo/user/archive/v.2.0.0.zip", remote.url
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
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_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
+ end
21
+ end
@@ -0,0 +1,37 @@
1
+ require "bundler/setup"
2
+ require "torba"
3
+
4
+ require "minitest/autorun"
5
+ require "tmpdir"
6
+
7
+ module Torba
8
+ module Test
9
+ module TempHome
10
+ def before_setup
11
+ Torba.home_path = @_torba_tmp_dir = Dir.mktmpdir("torba")
12
+ super
13
+ end
14
+
15
+ def after_teardown
16
+ FileUtils.rm_rf(@_torba_tmp_dir)
17
+ Torba.home_path = nil
18
+ super
19
+ end
20
+ end
21
+
22
+ module AssertExists
23
+ def assert_exists(file_path)
24
+ assert File.exists?(file_path)
25
+ end
26
+
27
+ def refute_exists(file_path)
28
+ refute File.exists?(file_path)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ class Minitest::Test
35
+ include Torba::Test::TempHome
36
+ include Torba::Test::AssertExists
37
+ end