linner-hc 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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/cache.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Linner
|
2
|
+
class Cache < Hash
|
3
|
+
def miss? ns, path
|
4
|
+
asset = Asset.new path
|
5
|
+
if self["#{ns}:#{path}"] and self["#{ns}:#{path}"].mtime == asset.mtime
|
6
|
+
false
|
7
|
+
else
|
8
|
+
self["#{ns}:#{path}"] = asset
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def expire_by paths
|
13
|
+
is_include_partial_styles = paths.any? do |path|
|
14
|
+
Asset.new(path).stylesheet? and File.basename(path).start_with? "_"
|
15
|
+
end
|
16
|
+
self.reject! {|k, v| v.stylesheet?} if is_include_partial_styles
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require "thor"
|
2
|
+
require "listen"
|
3
|
+
|
4
|
+
module Linner
|
5
|
+
class Command < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
map "-v" => :version
|
8
|
+
|
9
|
+
def self.source_root
|
10
|
+
File.dirname(__FILE__)
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "version", "show version"
|
14
|
+
def version
|
15
|
+
puts Linner::VERSION
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "check", "check dependencies"
|
19
|
+
method_option :environment,
|
20
|
+
type: :string,
|
21
|
+
default: "development",
|
22
|
+
aliases: "-e",
|
23
|
+
desc: "Watch the choosen environment"
|
24
|
+
def check
|
25
|
+
env.merge_with_environment(options[:environment])
|
26
|
+
message = Linner::Bundler.new(env).check
|
27
|
+
puts (message.first ? "🍵 :" : "👻 :") + message.last
|
28
|
+
end
|
29
|
+
|
30
|
+
desc "install", "install dependencies"
|
31
|
+
method_option :environment,
|
32
|
+
type: :string,
|
33
|
+
default: "development",
|
34
|
+
aliases: "-e",
|
35
|
+
desc: "Watch the choosen environment"
|
36
|
+
def install
|
37
|
+
begin
|
38
|
+
env.merge_with_environment(options[:environment])
|
39
|
+
Linner::Bundler.new(env).perform
|
40
|
+
rescue
|
41
|
+
puts "👻 : Install failed!"
|
42
|
+
puts $!
|
43
|
+
return
|
44
|
+
end
|
45
|
+
puts "🍵 : Perfect installed all bundles!"
|
46
|
+
end
|
47
|
+
|
48
|
+
desc "build", "build assets"
|
49
|
+
method_option :strict,
|
50
|
+
type: :boolean,
|
51
|
+
default: false,
|
52
|
+
aliases: "-s",
|
53
|
+
desc: "Use strict mode to replace revisiton."
|
54
|
+
method_option :environment,
|
55
|
+
type: :string,
|
56
|
+
default: "production",
|
57
|
+
aliases: "-e",
|
58
|
+
desc: "Build the choosen environment"
|
59
|
+
def build
|
60
|
+
Linner.compile = true
|
61
|
+
Linner.strict = true if options[:strict]
|
62
|
+
clean
|
63
|
+
env.merge_with_environment(options[:environment])
|
64
|
+
Bundler.new(env).perform
|
65
|
+
perform
|
66
|
+
rescue
|
67
|
+
Notifier.error $!
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "watch", "watch assets"
|
71
|
+
method_option :environment,
|
72
|
+
type: :string,
|
73
|
+
default: "development",
|
74
|
+
aliases: "-e",
|
75
|
+
desc: "Watch the choosen environment"
|
76
|
+
def watch
|
77
|
+
clean
|
78
|
+
env.merge_with_environment(options[:environment])
|
79
|
+
Bundler.new(env).perform
|
80
|
+
perform
|
81
|
+
watch_for_env
|
82
|
+
watch_for_perform
|
83
|
+
watch_for_reload rescue nil
|
84
|
+
sleep
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "clean", "clean assets"
|
88
|
+
def clean
|
89
|
+
FileUtils.rm_rf Dir.glob("#{env.public_folder}/*")
|
90
|
+
end
|
91
|
+
|
92
|
+
desc "new", "create the skeleton of project"
|
93
|
+
def new(name)
|
94
|
+
directory('templates', name)
|
95
|
+
chmod("#{name}/bin/server", 0755)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def env
|
100
|
+
Linner.env
|
101
|
+
end
|
102
|
+
|
103
|
+
def perform
|
104
|
+
Notifier.profile { Linner.perform }
|
105
|
+
end
|
106
|
+
|
107
|
+
def watch_for_perform
|
108
|
+
Listen.to env.watched_paths do |modified, added, removed|
|
109
|
+
Linner.cache.expire_by(modified + added + removed)
|
110
|
+
perform
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def watch_for_reload
|
115
|
+
reactor = Reactor.supervise_as(:reactor).actors.first
|
116
|
+
Listen.to env.public_folder, relative_path: true do |modified, added, removed|
|
117
|
+
reactor.reload_browser(modified + added + removed)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def watch_for_env
|
122
|
+
Listen.to Linner.root, filter: /(config\.yml|Linnerfile)$/ do |modified, added, removed|
|
123
|
+
Linner.env = Environment.new Linner.config_file
|
124
|
+
Bundler.new(env).perform
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def exit!
|
129
|
+
Notifier.exit
|
130
|
+
Kernel::exit
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "uglifier"
|
2
|
+
require "cssminify"
|
3
|
+
|
4
|
+
module Linner
|
5
|
+
class Compressor
|
6
|
+
|
7
|
+
def self.compress(asset)
|
8
|
+
if asset.javascript? or asset.template?
|
9
|
+
Uglifier.compile asset.content, comments: "none"
|
10
|
+
elsif asset.stylesheet?
|
11
|
+
CSSminify.new.compress asset.content
|
12
|
+
else
|
13
|
+
asset.content
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module Linner
|
4
|
+
class Environment
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@env ||= (YAML::load(File.read path) || Hash.new)
|
8
|
+
merge_with_convension
|
9
|
+
end
|
10
|
+
|
11
|
+
%w(app test vendor public).each do |method|
|
12
|
+
define_method("#{method}_folder") do
|
13
|
+
@env["paths"][method]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def paths
|
18
|
+
groups.map { |group| group["paths"] }.flatten.uniq
|
19
|
+
end
|
20
|
+
|
21
|
+
def watched_paths
|
22
|
+
[app_folder, vendor_folder, test_folder].select do |path|
|
23
|
+
File.exist? path
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
%w(revision notification).each do |method|
|
28
|
+
define_method("#{method}") do
|
29
|
+
@env[method]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def manifest
|
34
|
+
revision["manifest"]
|
35
|
+
end
|
36
|
+
|
37
|
+
def bundles
|
38
|
+
@env["bundles"] || []
|
39
|
+
end
|
40
|
+
|
41
|
+
def sprites
|
42
|
+
@env["sprites"] || {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def modules_ignored
|
46
|
+
Dir.glob(@env["modules"]["ignored"])
|
47
|
+
end
|
48
|
+
|
49
|
+
def wrapper
|
50
|
+
@env["modules"]["wrapper"]
|
51
|
+
end
|
52
|
+
|
53
|
+
def definition
|
54
|
+
File.join public_folder, @env["modules"]["definition"]
|
55
|
+
end
|
56
|
+
|
57
|
+
def groups
|
58
|
+
@env["groups"].values
|
59
|
+
end
|
60
|
+
|
61
|
+
def environments
|
62
|
+
@env["environments"] || {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def merge_with_environment(environment)
|
66
|
+
return @env unless picked = environments[environment]
|
67
|
+
@env = @env.rmerge!(picked)
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
def merge_with_convension
|
72
|
+
convension = YAML::load File.read(File.join File.dirname(__FILE__), "../../vendor", "config.default.yml")
|
73
|
+
@env = convension.rmerge!(@env)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Linner
|
2
|
+
module HashRecursiveMerge
|
3
|
+
def rmerge!(other_hash)
|
4
|
+
merge!(other_hash) do |key, oldval, newval|
|
5
|
+
oldval.class == self.class ? oldval.rmerge!(newval) : newval
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Order
|
11
|
+
def order_by(ary)
|
12
|
+
ary << "..." if not ary.include? "..."
|
13
|
+
order_ary = ary.inject([[]]) do |a, x|
|
14
|
+
x != "..." ? a.last << x : a<< []; a
|
15
|
+
end
|
16
|
+
order_by_direction(order_ary.first, :before)
|
17
|
+
order_by_direction(order_ary.last, :after)
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def order_by_direction(ary, direction)
|
23
|
+
ary = ary.reverse if direction == :before
|
24
|
+
ary.each do |f|
|
25
|
+
next unless i = self.index {|x| x =~ /#{f}/i}
|
26
|
+
item = self.delete_at i
|
27
|
+
direction == :before ? self.unshift(item) : self.push(item)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Hash
|
34
|
+
include Linner::HashRecursiveMerge
|
35
|
+
end
|
36
|
+
|
37
|
+
class Array
|
38
|
+
include Linner::Order
|
39
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "terminal-notifier"
|
2
|
+
|
3
|
+
module Linner
|
4
|
+
class Notifier
|
5
|
+
class << self
|
6
|
+
def profile
|
7
|
+
time = Time.now
|
8
|
+
yield
|
9
|
+
puts "🍜 : Done in #{"%.3f" % (Time.now - time)}s."
|
10
|
+
end
|
11
|
+
|
12
|
+
def error(message)
|
13
|
+
abort message = "👻 : #{message}!"
|
14
|
+
if Linner.env.notification && TerminalNotifier.available?
|
15
|
+
TerminalNotifier.notify message, :title => "Linner"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def exit
|
20
|
+
puts "\r🍵 : Let's take a break!"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "reel"
|
2
|
+
require "json"
|
3
|
+
|
4
|
+
module Linner
|
5
|
+
class Reactor < Reel::Server::HTTP
|
6
|
+
include Celluloid
|
7
|
+
|
8
|
+
Celluloid.logger = nil
|
9
|
+
|
10
|
+
attr_accessor :clients
|
11
|
+
|
12
|
+
def initialize(host = "127.0.0.1", port = 35729)
|
13
|
+
@clients = []
|
14
|
+
super(host, port, &method(:on_connection))
|
15
|
+
end
|
16
|
+
|
17
|
+
def on_connection(connection)
|
18
|
+
while request = connection.request
|
19
|
+
if request.websocket?
|
20
|
+
connection.detach
|
21
|
+
route_websocket request.websocket
|
22
|
+
return
|
23
|
+
else
|
24
|
+
route_request connection, request
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def route_request(connection, request)
|
30
|
+
if request.url.start_with? "/livereload.js"
|
31
|
+
return connection.respond :ok, {"Content_Type" => 'application/ecmascript'}, File.read(File.join(File.dirname(__FILE__), "../../vendor", "livereload.js"))
|
32
|
+
end
|
33
|
+
|
34
|
+
path = File.join(Linner.environment.public_folder, request.url[1..-1])
|
35
|
+
if File.exist?(path)
|
36
|
+
content_type = case File.extname(path)
|
37
|
+
when '.css' then 'text/css'
|
38
|
+
when '.js' then 'application/ecmascript'
|
39
|
+
when '.gif' then 'image/gif'
|
40
|
+
when '.jpeg', '.jpg' then 'image/jpeg'
|
41
|
+
when '.png' then 'image/png'
|
42
|
+
else; 'text/plain'
|
43
|
+
end
|
44
|
+
return connection.respond :ok, {"Content_Type" => content_type, "Content_Length" => File.size(path)}, File.read(path)
|
45
|
+
end
|
46
|
+
|
47
|
+
connection.respond :not_found, "Not found"
|
48
|
+
end
|
49
|
+
|
50
|
+
def route_websocket(socket)
|
51
|
+
socket << JSON.generate({
|
52
|
+
:command => 'hello',
|
53
|
+
:protocols => ['http://livereload.com/protocols/official-7'],
|
54
|
+
:serverName => 'reel-livereload'
|
55
|
+
})
|
56
|
+
if socket.url == "/livereload"
|
57
|
+
@clients << Client.new(socket)
|
58
|
+
else
|
59
|
+
socket.close
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def reload_browser(paths = [])
|
64
|
+
paths.each do |path|
|
65
|
+
@clients.each {|c| c.notify_asset_change path }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Client
|
70
|
+
include Celluloid
|
71
|
+
|
72
|
+
def initialize(socket)
|
73
|
+
@socket = socket
|
74
|
+
end
|
75
|
+
|
76
|
+
def notify_asset_change(path)
|
77
|
+
data = {
|
78
|
+
:path => path,
|
79
|
+
:command => 'reload',
|
80
|
+
:liveCSS => true
|
81
|
+
}
|
82
|
+
@socket << JSON.generate(data)
|
83
|
+
rescue
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require "chunky_png"
|
2
|
+
|
3
|
+
module Linner
|
4
|
+
ImageProxy = Struct.new(:path, :image, :top, :left) do
|
5
|
+
def width
|
6
|
+
image.width
|
7
|
+
end
|
8
|
+
|
9
|
+
def height
|
10
|
+
image.height
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Sprite
|
15
|
+
|
16
|
+
attr_accessor :root, :images
|
17
|
+
|
18
|
+
def initialize(images)
|
19
|
+
@images = images.sort do |a, b|
|
20
|
+
diff = [b.width, b.height].max <=> [a.width, a.height].max
|
21
|
+
diff = [b.width, b.height].min <=> [a.width, a.height].min if diff.zero?
|
22
|
+
diff = b.height <=> a.height if diff.zero?
|
23
|
+
diff = b.width <=> a.width if diff.zero?
|
24
|
+
diff
|
25
|
+
end
|
26
|
+
|
27
|
+
@root = { :x => 0, :y => 0, :w => @images.first.width, :h => @images.first.height}
|
28
|
+
end
|
29
|
+
|
30
|
+
def pack!
|
31
|
+
@images.each do |image|
|
32
|
+
if block = find_block(@root, image)
|
33
|
+
place_image(block, image)
|
34
|
+
split_block(block, image)
|
35
|
+
else
|
36
|
+
@root = expand_root(@root, image)
|
37
|
+
redo
|
38
|
+
end
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_style(config, name)
|
44
|
+
selector = config["selector"] || ".icon-"
|
45
|
+
url = config['url'] || config['path']
|
46
|
+
@images.inject("") do |style, image|
|
47
|
+
logical_path = Asset.new(image.path).logical_path
|
48
|
+
selector_with_pseudo_class = logical_path.chomp(File.extname(logical_path))
|
49
|
+
.gsub("/", "-")
|
50
|
+
.gsub("_active", ".active")
|
51
|
+
.gsub("_hover", ":hover")
|
52
|
+
.gsub("_", "-")
|
53
|
+
style <<
|
54
|
+
"#{selector}#{selector_with_pseudo_class} {
|
55
|
+
width: #{image.width}px;
|
56
|
+
height: #{image.height}px;
|
57
|
+
background: url(#{File.join url, name}) -#{image.left}px -#{image.top}px no-repeat;
|
58
|
+
}
|
59
|
+
"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def find_block(root, image)
|
65
|
+
if root[:used]
|
66
|
+
find_block(root[:right], image) || find_block(root[:down], image)
|
67
|
+
elsif (image.width <= root[:w]) && (image.height <= root[:h])
|
68
|
+
root
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def place_image(block, image)
|
73
|
+
image.top = block[:y]
|
74
|
+
image.left = block[:x]
|
75
|
+
end
|
76
|
+
|
77
|
+
def split_block(block, image)
|
78
|
+
block[:used] = true
|
79
|
+
block[:down] = {:x => block[:x], :y => block[:y] + image.height, :w => block[:w], :h => block[:h] - image.height}
|
80
|
+
block[:right] = {:x => block[:x] + image.width, :y => block[:y], :w => block[:w] - image.width, :h => image.height}
|
81
|
+
end
|
82
|
+
|
83
|
+
def expand_root(root, image)
|
84
|
+
can_expand_down = (image.width <= root[:w])
|
85
|
+
can_expand_right = (image.height <= root[:h])
|
86
|
+
|
87
|
+
should_expand_down = can_expand_down && (root[:w] >= (root[:h] + image.height))
|
88
|
+
should_expand_right = can_expand_right && (root[:h] >= (root[:w] + image.width))
|
89
|
+
|
90
|
+
if should_expand_right
|
91
|
+
expand_right(root, image.width)
|
92
|
+
elsif should_expand_down
|
93
|
+
expand_down(root, image.height)
|
94
|
+
elsif can_expand_right
|
95
|
+
expand_right(root, image.width)
|
96
|
+
elsif can_expand_down
|
97
|
+
expand_down(root, image.height)
|
98
|
+
else
|
99
|
+
raise RuntimeError, "Crashed!"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def expand_right(root, width)
|
104
|
+
Hash[
|
105
|
+
:used => true,
|
106
|
+
:x => 0,
|
107
|
+
:y => 0,
|
108
|
+
:w => root[:w] + width,
|
109
|
+
:h => root[:h],
|
110
|
+
:down => root,
|
111
|
+
:right => { :x => root[:w], :y => 0, :w => width, :h => root[:h] }
|
112
|
+
]
|
113
|
+
end
|
114
|
+
|
115
|
+
def expand_down(root, height)
|
116
|
+
Hash[
|
117
|
+
:used => true,
|
118
|
+
:x => 0,
|
119
|
+
:y => 0,
|
120
|
+
:w => root[:w],
|
121
|
+
:h => root[:h] + height,
|
122
|
+
:down => { :x => 0, :y => root[:h], :w => root[:w], :h => height },
|
123
|
+
:right => root
|
124
|
+
]
|
125
|
+
end
|
126
|
+
end # Sprite
|
127
|
+
end # Linner
|