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