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