linner-hc 1.0.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 +19 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CHANGELOG +129 -0
- data/Gemfile +14 -0
- data/LICENSE +22 -0
- data/README.md +74 -0
- data/Rakefile +8 -0
- data/bin/linner +11 -0
- data/docs/commands.md +29 -0
- data/docs/config.md +192 -0
- data/lib/linner.rb +261 -0
- data/lib/linner/archive.rb +53 -0
- data/lib/linner/asset.rb +102 -0
- data/lib/linner/bundler.rb +85 -0
- data/lib/linner/cache.rb +19 -0
- data/lib/linner/command.rb +133 -0
- data/lib/linner/compressor.rb +17 -0
- data/lib/linner/environment.rb +76 -0
- data/lib/linner/helper.rb +39 -0
- data/lib/linner/notifier.rb +24 -0
- data/lib/linner/reactor.rb +87 -0
- data/lib/linner/sprite.rb +127 -0
- data/lib/linner/template.rb +77 -0
- data/lib/linner/templates/app/images/.gitkeep +0 -0
- data/lib/linner/templates/app/images/logo.png +0 -0
- data/lib/linner/templates/app/scripts/app.coffee +3 -0
- data/lib/linner/templates/app/styles/app.scss +1 -0
- data/lib/linner/templates/app/templates/welcome.hbs +1 -0
- data/lib/linner/templates/app/views/index.html +21 -0
- data/lib/linner/templates/bin/server +3 -0
- data/lib/linner/templates/config.yml +54 -0
- data/lib/linner/templates/public/.gitkeep +1 -0
- data/lib/linner/templates/test/.gitkeep +0 -0
- data/lib/linner/templates/vendor/.gitkeep +1 -0
- data/lib/linner/version.rb +3 -0
- data/lib/linner/wrapper.rb +39 -0
- data/linner.gemspec +35 -0
- data/linner.gemspec.bak +34 -0
- data/spec/fixtures/app.js +1 -0
- data/spec/fixtures/config.yml +30 -0
- data/spec/linner/asset_spec.rb +33 -0
- data/spec/linner/bundler_spec.rb +26 -0
- data/spec/linner/environment_spec.rb +22 -0
- data/spec/linner/helper_spec.rb +26 -0
- data/spec/linner/sprites_spec.rb +23 -0
- data/spec/linner/template_spec.rb +16 -0
- data/spec/linner/wrapper_spec.rb +20 -0
- data/spec/spec_helper.rb +11 -0
- data/vendor/config.default.yml +13 -0
- data/vendor/livereload.js +1055 -0
- data/vendor/require_definition.js +60 -0
- metadata +289 -0
data/lib/linner.rb
ADDED
@@ -0,0 +1,261 @@
|
|
1
|
+
require "linner/version"
|
2
|
+
require "linner/command"
|
3
|
+
require "linner/asset"
|
4
|
+
require "linner/cache"
|
5
|
+
require "linner/helper"
|
6
|
+
require "linner/sprite"
|
7
|
+
require "linner/archive"
|
8
|
+
require "linner/bundler"
|
9
|
+
require "linner/reactor"
|
10
|
+
require "linner/wrapper"
|
11
|
+
require "linner/template"
|
12
|
+
require "linner/notifier"
|
13
|
+
require "linner/compressor"
|
14
|
+
require "linner/environment"
|
15
|
+
|
16
|
+
module Linner
|
17
|
+
extend self
|
18
|
+
|
19
|
+
attr_accessor :env, :compile, :strict
|
20
|
+
|
21
|
+
def root
|
22
|
+
@root ||= Pathname('.').realpath
|
23
|
+
end
|
24
|
+
|
25
|
+
def config_file
|
26
|
+
linner_file = root.join("Linnerfile")
|
27
|
+
config_file = root.join("config.yml")
|
28
|
+
File.exist?(linner_file) ? linner_file : config_file
|
29
|
+
end
|
30
|
+
|
31
|
+
def env
|
32
|
+
@env ||= Environment.new config_file
|
33
|
+
end
|
34
|
+
|
35
|
+
def cache
|
36
|
+
@cache ||= Cache.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def manifest
|
40
|
+
@manifest ||= begin
|
41
|
+
hash = {}
|
42
|
+
copy_assets = []
|
43
|
+
concat_assets = []
|
44
|
+
template_assets = []
|
45
|
+
sprite_assets = []
|
46
|
+
cdn = env.revision["cdn"] || ""
|
47
|
+
prefix = env.revision["prefix"] || ""
|
48
|
+
|
49
|
+
env.groups.each do |config|
|
50
|
+
concat_assets << config["concat"].keys if config["concat"]
|
51
|
+
template_assets << config["template"].keys if config["template"]
|
52
|
+
sprite_assets << config["sprite"].keys if config["sprite"]
|
53
|
+
config["copy"].each do |dest, pattern|
|
54
|
+
copy_assets << Dir.glob(pattern).map do |path|
|
55
|
+
logical_path = Asset.new(path).logical_path
|
56
|
+
dest_path = File.join(dest, logical_path)
|
57
|
+
end
|
58
|
+
end if config["copy"]
|
59
|
+
end
|
60
|
+
|
61
|
+
# revision sprite assets
|
62
|
+
sprite_assets.flatten.each do |dest|
|
63
|
+
name = File.basename(dest).sub /[^.]+\z/, "png"
|
64
|
+
dest = File.join env.sprites["path"], name
|
65
|
+
asset = Asset.new(File.join env.public_folder, dest)
|
66
|
+
hash[prefix + dest] = cdn + prefix + asset.relative_digest_path
|
67
|
+
asset.revision!
|
68
|
+
|
69
|
+
(concat_assets + copy_assets).flatten.each do |file|
|
70
|
+
path = File.join env.public_folder, file
|
71
|
+
next unless Asset.new(path).stylesheet?
|
72
|
+
url = env.sprites["url"] || env.sprites["path"]
|
73
|
+
puts = File.read(path).gsub(File.join(url, File.basename(dest)), File.join(cdn, url, File.basename(asset.relative_digest_path)))
|
74
|
+
File.open(path, "w") { |file| file << puts }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# revision concat template and copy assets
|
79
|
+
(concat_assets + template_assets + copy_assets).flatten.each do |dest|
|
80
|
+
asset = Asset.new(File.join env.public_folder, dest)
|
81
|
+
next unless asset.revable?
|
82
|
+
hash[prefix + dest] = cdn + prefix + asset.relative_digest_path
|
83
|
+
asset.revision!
|
84
|
+
end
|
85
|
+
|
86
|
+
hash
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def compile?
|
91
|
+
@compile
|
92
|
+
end
|
93
|
+
|
94
|
+
def strict?
|
95
|
+
@strict
|
96
|
+
end
|
97
|
+
|
98
|
+
def perform(*asset)
|
99
|
+
env.groups.each do |config|
|
100
|
+
precompile(config) if config["precompile"]
|
101
|
+
sprite(config) if config["sprite"]
|
102
|
+
end
|
103
|
+
env.groups.each do |config|
|
104
|
+
copy(config) if config["copy"]
|
105
|
+
compile(config) if config["compile"]
|
106
|
+
concat(config) if config["concat"]
|
107
|
+
end
|
108
|
+
env.groups.each do |config|
|
109
|
+
tar(config) if config["tar"]
|
110
|
+
end
|
111
|
+
revision if compile? and env.revision
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
def concat(config)
|
116
|
+
config["concat"].each_with_index do |pair, index|
|
117
|
+
dest, pattern, order = pair.first, pair.last, config["order"]||[]
|
118
|
+
matches = Dir.glob(pattern).sort_by(&:downcase).order_by(order)
|
119
|
+
next if matches.select {|path| cache.miss?(dest, path)}.empty?
|
120
|
+
write_asset(dest, matches)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def copy(config)
|
125
|
+
config["copy"].each do |dest, pattern|
|
126
|
+
Dir.glob(pattern).each do |path|
|
127
|
+
asset = Asset.new(path)
|
128
|
+
dest_path = File.join(env.public_folder, dest, asset.logical_path)
|
129
|
+
FileUtils.mkdir_p File.dirname(dest_path)
|
130
|
+
FileUtils.cp_r path, dest_path
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def compile(config)
|
136
|
+
config["compile"].each do |dest, pattern|
|
137
|
+
Dir.glob(pattern).each do |path|
|
138
|
+
next if not cache.miss?(dest, path)
|
139
|
+
asset = Asset.new(path)
|
140
|
+
dest_path = File.join(env.public_folder, dest, asset.logical_path)
|
141
|
+
if asset.javascript? or asset.stylesheet?
|
142
|
+
asset.content
|
143
|
+
asset.compress if compile?
|
144
|
+
dest_path = dest_path.sub(/[^.]+\z/, "js") if asset.javascript?
|
145
|
+
dest_path = dest_path.sub(/[^.]+\z/, "css") if asset.stylesheet?
|
146
|
+
asset.path = dest_path
|
147
|
+
asset.write
|
148
|
+
elsif asset.eruby?
|
149
|
+
base, ext = path.split(".")
|
150
|
+
dest_path = if ext == "erb"
|
151
|
+
dest_path.sub(/[.]+\z/, "html")
|
152
|
+
else
|
153
|
+
dest_path.gsub(File.basename(dest_path), File.basename(dest_path, File.extname(dest_path)))
|
154
|
+
end
|
155
|
+
asset.content(config["context"])
|
156
|
+
asset.path = dest_path
|
157
|
+
asset.write
|
158
|
+
else
|
159
|
+
FileUtils.mkdir_p File.dirname(dest_path)
|
160
|
+
FileUtils.cp_r path, dest_path
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def precompile(config)
|
167
|
+
config["precompile"].each do |dest, pattern|
|
168
|
+
matches = Dir.glob(pattern).sort_by(&:downcase)
|
169
|
+
next if matches.select { |path| cache.miss?(dest, path) }.empty?
|
170
|
+
write_template(dest, matches)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def sprite(config)
|
175
|
+
config["sprite"].each do |dest, pattern|
|
176
|
+
matches = Dir.glob(pattern).sort_by(&:downcase)
|
177
|
+
next if matches.select { |path| cache.miss?(dest, path) }.empty?
|
178
|
+
paint_sprite(dest, matches)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def tar(config)
|
183
|
+
config["tar"].each do |dest, pattern|
|
184
|
+
path = File.join(env.public_folder, dest)
|
185
|
+
FileUtils.mkdir_p File.dirname(path)
|
186
|
+
Archive.tar(pattern, path)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def revision
|
191
|
+
dump_manifest
|
192
|
+
files = env.revision["files"] || []
|
193
|
+
files.flatten.each do |rev|
|
194
|
+
file = File.join env.public_folder, rev.to_s
|
195
|
+
next if not File.exist?(file)
|
196
|
+
replace_attributes file
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def paint_sprite(dest, images)
|
201
|
+
images = images.map do |path|
|
202
|
+
ImageProxy.new(path, ChunkyPNG::Image.from_file(path))
|
203
|
+
end
|
204
|
+
sprite = Sprite.new(images).pack!
|
205
|
+
map = ChunkyPNG::Image.new(sprite.root[:w], sprite.root[:h], ChunkyPNG::Color::TRANSPARENT)
|
206
|
+
|
207
|
+
sprite.images.each do |image|
|
208
|
+
map.compose!(image.image, image.left, image.top)
|
209
|
+
end
|
210
|
+
|
211
|
+
name = File.basename(dest).sub(/[^.]+\z/, "png")
|
212
|
+
path = File.join(env.public_folder, env.sprites["path"], name)
|
213
|
+
FileUtils.mkdir_p File.dirname(path)
|
214
|
+
map.save path
|
215
|
+
|
216
|
+
asset = Asset.new(File.join env.public_folder, dest)
|
217
|
+
asset.content = sprite.generate_style(env.sprites, name)
|
218
|
+
asset.write
|
219
|
+
end
|
220
|
+
|
221
|
+
def write_template(dest, child_assets)
|
222
|
+
asset = Asset.new(File.join env.public_folder, dest)
|
223
|
+
content = child_assets.inject("") {|s, m| s << cache["#{dest}:#{m}"].content}
|
224
|
+
asset.content = Wrapper::Template.definition(content)
|
225
|
+
asset.compress if compile?
|
226
|
+
asset.write
|
227
|
+
end
|
228
|
+
|
229
|
+
def write_asset(dest, child_assets)
|
230
|
+
asset = Asset.new(File.join env.public_folder, dest)
|
231
|
+
definition = (asset.path == env.definition ? Wrapper::Module.definition : "")
|
232
|
+
asset.content = child_assets.inject(definition) {|s, m| s << cache["#{dest}:#{m}"].content}
|
233
|
+
asset.compress if compile?
|
234
|
+
asset.write
|
235
|
+
end
|
236
|
+
|
237
|
+
def replace_attributes file
|
238
|
+
doc = File.read file
|
239
|
+
if strict?
|
240
|
+
doc.gsub!(/(<script.+src=['"])([^"']+)(["'])/) do |m|
|
241
|
+
if p = manifest[$2] then $1 << p << $3 else m end
|
242
|
+
end
|
243
|
+
|
244
|
+
doc.gsub!(/(<link[^\>]+href=['"])([^"']+)(["'])/) do |m|
|
245
|
+
if p = manifest[$2] then $1 << p << $3 else m end
|
246
|
+
end
|
247
|
+
else
|
248
|
+
manifest.each do |k, v|
|
249
|
+
doc.gsub!(k, v)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
File.open(file, "w") {|f| f.write doc}
|
253
|
+
end
|
254
|
+
|
255
|
+
def dump_manifest
|
256
|
+
manifest_file = env.revision["manifest"] || "manifest.yml"
|
257
|
+
File.open(File.join(env.public_folder, manifest_file), "w") do |f|
|
258
|
+
YAML.dump(manifest, f)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "rubygems/package"
|
2
|
+
|
3
|
+
module Linner
|
4
|
+
class Archive
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def tar(glob, dest)
|
8
|
+
archived = StringIO.new
|
9
|
+
Gem::Package::TarWriter.new(archived) do |tar|
|
10
|
+
Dir[glob].each do |file|
|
11
|
+
paths = Linner.env.paths
|
12
|
+
mode = File.stat(file).mode
|
13
|
+
relative_file = file.gsub /^#{paths.join("|")}\/?/, ""
|
14
|
+
if File.directory?(file)
|
15
|
+
tar.mkdir relative_file, mode
|
16
|
+
else
|
17
|
+
tar.add_file relative_file, mode do |tf|
|
18
|
+
File.open(file, "rb") { |f| tf.write f.read }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
archived.rewind
|
25
|
+
|
26
|
+
Zlib::GzipWriter.open(dest) do |gz|
|
27
|
+
gz.write archived.string
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def untar(path, dest)
|
32
|
+
extracted = Gem::Package::TarReader.new Zlib::GzipReader.open(path)
|
33
|
+
|
34
|
+
extracted.rewind
|
35
|
+
|
36
|
+
extracted.each do |entry|
|
37
|
+
file = File.join dest, entry.full_name
|
38
|
+
if entry.directory?
|
39
|
+
FileUtils.mkdir_p file
|
40
|
+
else
|
41
|
+
directory = File.dirname(file)
|
42
|
+
FileUtils.mkdir_p directory unless File.directory?(directory)
|
43
|
+
File.open file, "wb" do |f|
|
44
|
+
f.print entry.read
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
extracted.close
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/linner/asset.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
require "digest"
|
2
|
+
|
3
|
+
module Linner
|
4
|
+
class Asset
|
5
|
+
class RenderError < StandardError; end
|
6
|
+
|
7
|
+
attr_accessor :path, :content
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
@mtime = File.mtime(path).to_i if File.exist?(path)
|
12
|
+
end
|
13
|
+
|
14
|
+
def mtime
|
15
|
+
@mtime
|
16
|
+
end
|
17
|
+
|
18
|
+
def extname
|
19
|
+
@extname = File.extname path
|
20
|
+
end
|
21
|
+
|
22
|
+
def digest_path
|
23
|
+
digest = Digest::MD5.hexdigest content
|
24
|
+
path.chomp(extname) << "-#{digest}" << extname
|
25
|
+
end
|
26
|
+
|
27
|
+
def relative_digest_path
|
28
|
+
digest_path.gsub /#{Linner.env.public_folder}/, ""
|
29
|
+
end
|
30
|
+
|
31
|
+
def revable?
|
32
|
+
javascript? or stylesheet?
|
33
|
+
end
|
34
|
+
|
35
|
+
def revision!
|
36
|
+
File.rename path, digest_path
|
37
|
+
end
|
38
|
+
|
39
|
+
def content(context = nil)
|
40
|
+
return @content if @content
|
41
|
+
source = begin
|
42
|
+
File.exist?(path) ? Tilt.new(path, :default_encoding => "UTF-8").render(nil, context) : ""
|
43
|
+
rescue RuntimeError
|
44
|
+
File.read(path, mode: "rb")
|
45
|
+
rescue => e
|
46
|
+
raise RenderError, "#{e.message} in (#{path})"
|
47
|
+
end
|
48
|
+
if wrappable?
|
49
|
+
@content = wrap(source)
|
50
|
+
else
|
51
|
+
@content = source
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def wrap(source)
|
56
|
+
if javascript?
|
57
|
+
Wrapper::Module.wrap(logical_path.chomp(File.extname logical_path), source)
|
58
|
+
elsif template?
|
59
|
+
if File.basename(path).start_with?("_")
|
60
|
+
Wrapper::Template.partial_wrap(logical_path.chomp(File.extname logical_path), source)
|
61
|
+
else
|
62
|
+
Wrapper::Template.wrap(logical_path.chomp(File.extname logical_path), source)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def javascript?
|
68
|
+
Tilt[path] and Tilt[path].default_mime_type == "application/javascript"
|
69
|
+
end
|
70
|
+
|
71
|
+
def stylesheet?
|
72
|
+
Tilt[path] and Tilt[path].default_mime_type == "text/css"
|
73
|
+
end
|
74
|
+
|
75
|
+
def template?
|
76
|
+
Tilt[path] and Tilt[path].default_mime_type == "text/template"
|
77
|
+
end
|
78
|
+
|
79
|
+
def eruby?
|
80
|
+
Tilt[path] and Tilt[path].default_mime_type = "application/x-eruby"
|
81
|
+
end
|
82
|
+
|
83
|
+
def wrappable?
|
84
|
+
!!(self.javascript? and !Linner.env.modules_ignored.include?(@path) or self.template?)
|
85
|
+
end
|
86
|
+
|
87
|
+
def write
|
88
|
+
FileUtils.mkdir_p File.dirname(@path)
|
89
|
+
File.open @path, "w" do |file|
|
90
|
+
file.write @content
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def compress
|
95
|
+
@content = Compressor.compress(self)
|
96
|
+
end
|
97
|
+
|
98
|
+
def logical_path
|
99
|
+
@logical_path ||= @path.gsub(/^(#{Linner.env.paths.join("|")})\/?/, "")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "uri"
|
2
|
+
require "digest"
|
3
|
+
require "fileutils"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module Linner
|
7
|
+
class Bundler
|
8
|
+
REPOSITORY = File.expand_path "~/.linner/bundles"
|
9
|
+
|
10
|
+
Bundle = Struct.new(:name, :version, :url) do
|
11
|
+
def path
|
12
|
+
File.join(REPOSITORY, name, version, File.basename(url))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(env)
|
17
|
+
@bundles = []
|
18
|
+
env.bundles.each do |name, props|
|
19
|
+
@bundles << Bundle.new(name, props["version"], props["url"])
|
20
|
+
end
|
21
|
+
@vendor = Pathname(".").join env.vendor_folder
|
22
|
+
end
|
23
|
+
|
24
|
+
def check
|
25
|
+
return [false, "Bundles didn't exsit!"] unless File.exist? REPOSITORY
|
26
|
+
@bundles.each do |bundle|
|
27
|
+
unless File.exist?(bundle.path) and File.exist?(File.join(@vendor, bundle.name))
|
28
|
+
return [false, "Bundle #{bundle.name} v#{bundle.version} didn't match!"]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
return [true, "Perfect bundled, ready to go!"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def install
|
35
|
+
unless File.exist? REPOSITORY
|
36
|
+
FileUtils.mkdir_p(REPOSITORY)
|
37
|
+
end
|
38
|
+
@bundles.each do |bundle|
|
39
|
+
if bundle.version != "master"
|
40
|
+
next if File.exist?(bundle.path) and File.exist?(File.join(@vendor, bundle.name))
|
41
|
+
end
|
42
|
+
puts "Installing #{bundle.name} #{bundle.version}..."
|
43
|
+
install_to_repository bundle
|
44
|
+
if File.extname(bundle.path) == ".gz"
|
45
|
+
link_and_extract_to_vendor bundle.path, File.join(@vendor, ".pkg", bundle.name, File.basename(bundle.path)), File.join(@vendor, bundle.name)
|
46
|
+
else
|
47
|
+
link_to_vendor bundle.path, File.join(@vendor, bundle.name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def perform
|
53
|
+
check and install
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def install_to_repository(bundle)
|
58
|
+
FileUtils.mkdir_p File.dirname(bundle.path)
|
59
|
+
begin
|
60
|
+
File.open(bundle.path, "wb") do |dest|
|
61
|
+
if bundle.url =~ URI::regexp
|
62
|
+
open(bundle.url, "r:UTF-8") {|file| dest.write file.read}
|
63
|
+
else
|
64
|
+
dest.write(File.read Pathname(bundle.url).expand_path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
rescue
|
68
|
+
Notifier.error("Can't fetch bundle #{bundle.name} from #{bundle.url}")
|
69
|
+
Kernel::exit
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def link_to_vendor(path, dest)
|
74
|
+
return if File.exist?(dest) and Digest::MD5.file(path).hexdigest == Digest::MD5.file(dest).hexdigest
|
75
|
+
FileUtils.mkdir_p File.dirname(dest)
|
76
|
+
FileUtils.cp path, dest
|
77
|
+
end
|
78
|
+
|
79
|
+
def link_and_extract_to_vendor(path, linked_path, dest)
|
80
|
+
link_to_vendor(path, linked_path)
|
81
|
+
FileUtils.rm_rf Dir.glob("#{dest}/*")
|
82
|
+
Archive.untar(path, dest)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|