linner-hc 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +3 -0
  5. data/CHANGELOG +129 -0
  6. data/Gemfile +14 -0
  7. data/LICENSE +22 -0
  8. data/README.md +74 -0
  9. data/Rakefile +8 -0
  10. data/bin/linner +11 -0
  11. data/docs/commands.md +29 -0
  12. data/docs/config.md +192 -0
  13. data/lib/linner.rb +261 -0
  14. data/lib/linner/archive.rb +53 -0
  15. data/lib/linner/asset.rb +102 -0
  16. data/lib/linner/bundler.rb +85 -0
  17. data/lib/linner/cache.rb +19 -0
  18. data/lib/linner/command.rb +133 -0
  19. data/lib/linner/compressor.rb +17 -0
  20. data/lib/linner/environment.rb +76 -0
  21. data/lib/linner/helper.rb +39 -0
  22. data/lib/linner/notifier.rb +24 -0
  23. data/lib/linner/reactor.rb +87 -0
  24. data/lib/linner/sprite.rb +127 -0
  25. data/lib/linner/template.rb +77 -0
  26. data/lib/linner/templates/app/images/.gitkeep +0 -0
  27. data/lib/linner/templates/app/images/logo.png +0 -0
  28. data/lib/linner/templates/app/scripts/app.coffee +3 -0
  29. data/lib/linner/templates/app/styles/app.scss +1 -0
  30. data/lib/linner/templates/app/templates/welcome.hbs +1 -0
  31. data/lib/linner/templates/app/views/index.html +21 -0
  32. data/lib/linner/templates/bin/server +3 -0
  33. data/lib/linner/templates/config.yml +54 -0
  34. data/lib/linner/templates/public/.gitkeep +1 -0
  35. data/lib/linner/templates/test/.gitkeep +0 -0
  36. data/lib/linner/templates/vendor/.gitkeep +1 -0
  37. data/lib/linner/version.rb +3 -0
  38. data/lib/linner/wrapper.rb +39 -0
  39. data/linner.gemspec +35 -0
  40. data/linner.gemspec.bak +34 -0
  41. data/spec/fixtures/app.js +1 -0
  42. data/spec/fixtures/config.yml +30 -0
  43. data/spec/linner/asset_spec.rb +33 -0
  44. data/spec/linner/bundler_spec.rb +26 -0
  45. data/spec/linner/environment_spec.rb +22 -0
  46. data/spec/linner/helper_spec.rb +26 -0
  47. data/spec/linner/sprites_spec.rb +23 -0
  48. data/spec/linner/template_spec.rb +16 -0
  49. data/spec/linner/wrapper_spec.rb +20 -0
  50. data/spec/spec_helper.rb +11 -0
  51. data/vendor/config.default.yml +13 -0
  52. data/vendor/livereload.js +1055 -0
  53. data/vendor/require_definition.js +60 -0
  54. metadata +289 -0
@@ -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
@@ -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