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/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
|