stasis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +10 -0
- data/LICENSE +18 -0
- data/README.md +287 -0
- data/Rakefile +109 -0
- data/bin/stasis +30 -0
- data/config/gemsets.yml +9 -0
- data/config/gemspec.yml +17 -0
- data/lib/stasis.rb +291 -0
- data/lib/stasis/dev_mode.rb +55 -0
- data/lib/stasis/gems.rb +154 -0
- data/lib/stasis/plugin.rb +76 -0
- data/lib/stasis/plugins/before.rb +50 -0
- data/lib/stasis/plugins/helpers.rb +29 -0
- data/lib/stasis/plugins/ignore.rb +35 -0
- data/lib/stasis/plugins/instead.rb +15 -0
- data/lib/stasis/plugins/layout.rb +51 -0
- data/lib/stasis/plugins/priority.rb +41 -0
- data/lib/stasis/plugins/render.rb +71 -0
- data/lib/stasis/scope.rb +54 -0
- data/lib/stasis/scope/action.rb +25 -0
- data/lib/stasis/scope/controller.rb +62 -0
- data/lib/stasis/server.rb +90 -0
- data/site/arrow.png +0 -0
- data/site/controller.rb +72 -0
- data/site/github.png +0 -0
- data/site/index.html.haml +24 -0
- data/site/jquery-1.6.2.js +8982 -0
- data/site/stasis.css.scss +226 -0
- data/site/stasis.js.coffee +42 -0
- data/site/stasis.png +0 -0
- data/spec/fixtures/gemsets.yml +9 -0
- data/spec/fixtures/gemspec.yml +15 -0
- data/spec/fixtures/project/_partial.html.haml +1 -0
- data/spec/fixtures/project/before_render_partial.html.haml +1 -0
- data/spec/fixtures/project/before_render_text.html.haml +1 -0
- data/spec/fixtures/project/controller.rb +83 -0
- data/spec/fixtures/project/index.html.haml +16 -0
- data/spec/fixtures/project/layout.html.haml +3 -0
- data/spec/fixtures/project/layout_action.html.haml +1 -0
- data/spec/fixtures/project/layout_action_from_subdirectory.html.haml +1 -0
- data/spec/fixtures/project/layout_controller.html.haml +1 -0
- data/spec/fixtures/project/layout_controller_from_subdirectory.html.haml +1 -0
- data/spec/fixtures/project/no_controller/index.html.haml +12 -0
- data/spec/fixtures/project/not_dynamic.html +1 -0
- data/spec/fixtures/project/plugin.rb +16 -0
- data/spec/fixtures/project/subdirectory/_partial.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/before_render_partial.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/before_render_text.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/controller.rb +66 -0
- data/spec/fixtures/project/subdirectory/ignore.html.haml +0 -0
- data/spec/fixtures/project/subdirectory/index.html.haml +14 -0
- data/spec/fixtures/project/subdirectory/layout.html.haml +3 -0
- data/spec/fixtures/project/subdirectory/layout_action.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/layout_action_from_root.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/layout_controller.html.haml +1 -0
- data/spec/fixtures/project/subdirectory/layout_controller_from_root.html.haml +1 -0
- data/spec/fixtures/project/time.html.haml +2 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/stasis/gems_spec.rb +249 -0
- data/spec/stasis/plugins/before_spec.rb +53 -0
- data/spec/stasis/plugins/helpers_spec.rb +16 -0
- data/spec/stasis/plugins/ignore_spec.rb +17 -0
- data/spec/stasis/plugins/layout_spec.rb +22 -0
- data/spec/stasis/plugins/priority_spec.rb +22 -0
- data/spec/stasis/plugins/render_spec.rb +23 -0
- data/spec/stasis/server_spec.rb +29 -0
- data/spec/stasis_spec.rb +46 -0
- data/stasis.gemspec +32 -0
- metadata +227 -0
@@ -0,0 +1,76 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Plugin
|
3
|
+
class <<self
|
4
|
+
|
5
|
+
# `Hash` -- Keys are the "bind as" method names and values are the actual method
|
6
|
+
# names on the `Plugin` instance.
|
7
|
+
attr_accessor :_methods
|
8
|
+
|
9
|
+
# `Fixnum` -- The execution priority for this plugin (defaults to 0).
|
10
|
+
def _priority; @priority || 0; end
|
11
|
+
|
12
|
+
# The methods in this `Array` essentially all take the same kind of parameters.
|
13
|
+
# Either a `Hash` or an `Array` of method names. No matter what, the input is
|
14
|
+
# converted to a `Hash` (see `_methods`).
|
15
|
+
%w(
|
16
|
+
action_method
|
17
|
+
after_all
|
18
|
+
after_render
|
19
|
+
before_all
|
20
|
+
before_render
|
21
|
+
controller_method
|
22
|
+
).each do |method|
|
23
|
+
method = method.to_sym
|
24
|
+
# Define method on the `Plugin` class.
|
25
|
+
define_method(method) do |*methods|
|
26
|
+
# Set defaults on the `_` class variable.
|
27
|
+
self._methods ||= {}
|
28
|
+
self._methods[method] ||= {}
|
29
|
+
# If passing a `Hash`...
|
30
|
+
if methods[0].is_a?(::Hash)
|
31
|
+
self._methods[method].merge!(methods[0])
|
32
|
+
# If passing an `Array`...
|
33
|
+
else
|
34
|
+
# Generate `Hash` from `Array`.
|
35
|
+
methods = methods.inject({}) do |hash, m|
|
36
|
+
hash[m] = m
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
self._methods[method].merge!(methods)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Class method to set priority on the `Plugin`.
|
45
|
+
def priority(number)
|
46
|
+
@priority = number
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Helper method provided for built-in Stasis plugins. Returns a boolean value denoting
|
51
|
+
# whether or not a path is within another path.
|
52
|
+
def _within?(within_path, path=@stasis.path)
|
53
|
+
if within_path && path
|
54
|
+
dir = File.dirname(within_path)
|
55
|
+
path[0..dir.length-1] == dir
|
56
|
+
else
|
57
|
+
true
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Helper method provided for built-in Stasis plugins. Returns an `Array` of values
|
62
|
+
# of a `Hash` whose keys are `nil`, a literal match, or a pattern match.
|
63
|
+
def _match_key?(hash, match_key)
|
64
|
+
hash.inject([]) do |array, (key, value)|
|
65
|
+
if key.nil?
|
66
|
+
array << value
|
67
|
+
elsif key.is_a?(::String) && key == match_key
|
68
|
+
array << value
|
69
|
+
elsif key.is_a?(::Regexp) && key =~ match_key
|
70
|
+
array << value
|
71
|
+
end
|
72
|
+
array
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Before < Plugin
|
3
|
+
|
4
|
+
before_all :before_all
|
5
|
+
before_render :before_render
|
6
|
+
controller_method :before
|
7
|
+
priority 1
|
8
|
+
|
9
|
+
def initialize(stasis)
|
10
|
+
@stasis = stasis
|
11
|
+
@blocks = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# This method is bound to all controllers. Stores a block in the `@blocks` `Hash`,
|
15
|
+
# where the key is a path and the value is an `Array` of blocks.
|
16
|
+
def before(*paths, &block)
|
17
|
+
paths = [ nil ] if paths.empty?
|
18
|
+
if block
|
19
|
+
paths.each do |path|
|
20
|
+
path = @stasis.controller._resolve(path, true)
|
21
|
+
@blocks[path] ||= []
|
22
|
+
@blocks[path] << [ @stasis.path, block ]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# This event triggers before all files render. When a `before` call receives a path
|
28
|
+
# that does not exist, we want to create that file dynamically. This method adds
|
29
|
+
# those dynamic paths to the `paths` `Array`.
|
30
|
+
def before_all
|
31
|
+
new_paths = (@blocks || {}).keys.select do |path|
|
32
|
+
path.is_a?(::String)
|
33
|
+
end
|
34
|
+
@stasis.paths = (@stasis.paths + new_paths).uniq
|
35
|
+
end
|
36
|
+
|
37
|
+
# This event triggers before each file renders through Stasis. It finds matching
|
38
|
+
# blocks for the `path` and evaluates those blocks using the `action` as a scope.
|
39
|
+
def before_render
|
40
|
+
matches = _match_key?(@blocks, @stasis.path)
|
41
|
+
matches.each do |group|
|
42
|
+
group.each do |(path, block)|
|
43
|
+
if _within?(path)
|
44
|
+
@stasis.action.instance_eval(&block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Helpers < Plugin
|
3
|
+
|
4
|
+
controller_method :helpers
|
5
|
+
before_render :before_render
|
6
|
+
|
7
|
+
def initialize(stasis)
|
8
|
+
@stasis = stasis
|
9
|
+
@blocks = []
|
10
|
+
end
|
11
|
+
|
12
|
+
# This event triggers before each file renders through Stasis. For each helper
|
13
|
+
# `block`, evaluate the `block` in the scope of the `action` class.
|
14
|
+
def before_render
|
15
|
+
@blocks.each do |(path, block)|
|
16
|
+
if _within?(path)
|
17
|
+
@stasis.action.class.class_eval(&block)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# This method is bound to all controllers. Stores a block in the `@blocks` `Array`.
|
23
|
+
def helpers(&block)
|
24
|
+
if block
|
25
|
+
@blocks << [ @stasis.path, block ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Ignore < Plugin
|
3
|
+
|
4
|
+
before_render :before_render
|
5
|
+
controller_method :ignore
|
6
|
+
|
7
|
+
def initialize(stasis)
|
8
|
+
@stasis = stasis
|
9
|
+
@ignore = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# This event triggers before each file renders. Rejects any `paths` that are included
|
13
|
+
# in the `@ignore` `Array`.
|
14
|
+
def before_render
|
15
|
+
matches = _match_key?(@ignore, @stasis.path)
|
16
|
+
matches.each do |group|
|
17
|
+
group.each do |path|
|
18
|
+
@stasis.path = nil if _within?(path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method is bound to all controllers. Adds an `Array` of paths to the `@ignore`
|
24
|
+
# `Array`.
|
25
|
+
def ignore(*array)
|
26
|
+
array.each do |path|
|
27
|
+
path = @stasis.controller._resolve(path)
|
28
|
+
if path
|
29
|
+
@ignore[path] ||= []
|
30
|
+
@ignore[path] << @stasis.path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Layout < Plugin
|
3
|
+
|
4
|
+
action_method :layout => :layout_action
|
5
|
+
before_render :before_render
|
6
|
+
controller_method :layout => :layout_controller
|
7
|
+
|
8
|
+
def initialize(stasis)
|
9
|
+
@stasis = stasis
|
10
|
+
@layouts = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# This event triggers before each file renders through Stasis. It sets the `action`
|
14
|
+
# layout from the matching layout for `path`.
|
15
|
+
def before_render
|
16
|
+
@stasis.action._layout = nil
|
17
|
+
matches = _match_key?(@layouts, @stasis.path)
|
18
|
+
matches.each do |(within, layout)|
|
19
|
+
@stasis.action._layout = layout if _within?(within)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# This method is bound to all actions. Set the `action` layout.
|
24
|
+
def layout_action(path)
|
25
|
+
if path = @stasis.controller._resolve(path)
|
26
|
+
@stasis.action._layout = path
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method is bound to all controllers. If it receives a `String` as a parameter,
|
31
|
+
# use that layout for all paths. Otherwise, it receives a `Hash` with the key being
|
32
|
+
# the `path` and the value being the layout to use for that `path`.
|
33
|
+
def layout_controller(hash_or_string)
|
34
|
+
if hash_or_string.is_a?(::String)
|
35
|
+
hash = {}
|
36
|
+
hash[/.*/] = hash_or_string
|
37
|
+
else
|
38
|
+
hash = hash_or_string
|
39
|
+
end
|
40
|
+
@layouts.merge! hash.inject({}) { |hash, (path, layout)|
|
41
|
+
path = @stasis.controller._resolve(path)
|
42
|
+
layout = @stasis.controller._resolve(layout)
|
43
|
+
if layout
|
44
|
+
hash[path] = [ @stasis.path, layout ]
|
45
|
+
@stasis.controller.ignore(layout)
|
46
|
+
end
|
47
|
+
hash
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Priority < Plugin
|
3
|
+
|
4
|
+
after_all :after_all
|
5
|
+
before_all :before_all
|
6
|
+
controller_method :priority
|
7
|
+
priority 2
|
8
|
+
|
9
|
+
def initialize(stasis)
|
10
|
+
@stasis = stasis
|
11
|
+
@priorities = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# This event triggers before all files render through Stasis. Collect matching
|
15
|
+
# `paths` and sort those `paths` by priority.
|
16
|
+
def before_all
|
17
|
+
@stasis.paths.collect! do |path|
|
18
|
+
priority = 0
|
19
|
+
matches = _match_key?(@priorities, path)
|
20
|
+
matches.each do |(within, value, force)|
|
21
|
+
priority = value if _within?(within, path) || force
|
22
|
+
end
|
23
|
+
[ path, priority ]
|
24
|
+
end
|
25
|
+
@stasis.paths.sort! { |a, b| b[1] <=> a[1] }
|
26
|
+
@stasis.paths.collect! { |(path, priority)| path }
|
27
|
+
end
|
28
|
+
|
29
|
+
# This method is bound to all controllers. Stores a priority integer in the
|
30
|
+
# `@@priorities` `Hash`, where the key is a path and the value is the priority.
|
31
|
+
def priority(hash)
|
32
|
+
hash = hash.inject({}) do |hash, (key, value)|
|
33
|
+
force = key[0..0] == '/'
|
34
|
+
key = @stasis.controller._resolve(key)
|
35
|
+
hash[key] = [ @stasis.path, value, force ] if key
|
36
|
+
hash
|
37
|
+
end
|
38
|
+
@priorities.merge!(hash)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'tilt'
|
2
|
+
|
3
|
+
class Stasis
|
4
|
+
class Render < Plugin
|
5
|
+
|
6
|
+
action_method :render
|
7
|
+
|
8
|
+
def initialize(stasis)
|
9
|
+
@stasis = stasis
|
10
|
+
end
|
11
|
+
|
12
|
+
# This method is bound to all actions.
|
13
|
+
def render(path_or_options={}, options={}, &block)
|
14
|
+
if path_or_options.is_a?(::String)
|
15
|
+
options[:path] = path_or_options
|
16
|
+
else
|
17
|
+
options.merge!(path_or_options)
|
18
|
+
end
|
19
|
+
|
20
|
+
callback = options[:callback]
|
21
|
+
locals = options[:locals]
|
22
|
+
path = options[:path]
|
23
|
+
scope = options[:scope]
|
24
|
+
text = options[:text]
|
25
|
+
|
26
|
+
if @stasis.controller
|
27
|
+
path = @stasis.controller._resolve(path)
|
28
|
+
end
|
29
|
+
|
30
|
+
output =
|
31
|
+
if text
|
32
|
+
text
|
33
|
+
elsif path && File.file?(path)
|
34
|
+
unless callback == false
|
35
|
+
# Trigger all plugin `before_render` events.
|
36
|
+
temporary_path(path) do
|
37
|
+
@stasis.trigger(:before_render)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
output =
|
42
|
+
if Tilt.mappings.keys.include?(File.extname(path)[1..-1])
|
43
|
+
scope = options[:scope] ||= @stasis.action
|
44
|
+
Tilt.new(path).render(scope, locals, &block)
|
45
|
+
else
|
46
|
+
File.read(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
unless callback == false
|
50
|
+
# Trigger all plugin `after_render` events.
|
51
|
+
temporary_path(path) do
|
52
|
+
@stasis.trigger(:after_render)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
output
|
57
|
+
end
|
58
|
+
|
59
|
+
output
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
# Temporarily set `Stasis#path`.
|
65
|
+
def temporary_path(path, &block)
|
66
|
+
@stasis.path, old_path = path, @stasis.path
|
67
|
+
yield
|
68
|
+
@stasis.path = old_path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/stasis/scope.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
class Stasis
|
2
|
+
class Scope
|
3
|
+
|
4
|
+
# `Stasis`.
|
5
|
+
attr_accessor :_stasis
|
6
|
+
|
7
|
+
# Plugins use the `controller_method` and `action_method` class methods to bind
|
8
|
+
# plugin methods to a scope instance. This method does the binding.
|
9
|
+
def _bind_plugin(plugin, type)
|
10
|
+
_each_plugin_method(plugin, type) do |plugin, method, real_method|
|
11
|
+
self.instance_eval <<-EVAL
|
12
|
+
# Define a method on `self` (the `Scope` instance).
|
13
|
+
def #{method}(*args, &block)
|
14
|
+
# Find the plugin.
|
15
|
+
plugin = self._stasis.plugins.detect do |plugin|
|
16
|
+
plugin.to_s == "#{plugin.to_s}"
|
17
|
+
end
|
18
|
+
# Pass parameters to the method on the plugin.
|
19
|
+
plugin.send(:#{real_method}, *args, &block)
|
20
|
+
end
|
21
|
+
EVAL
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def _each_plugin_method(plugin, type, &block)
|
26
|
+
# Retrieve plugin `methods`: a `Hash` whose keys are the method name to bind to
|
27
|
+
# `self`, and whose values are the method name on the `Plugin` class we are
|
28
|
+
# binding from.
|
29
|
+
methods = plugin.class._methods ? plugin.class._methods[type] : nil
|
30
|
+
methods ||= {}
|
31
|
+
methods.each do |method, real_method|
|
32
|
+
yield(plugin, method, real_method)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def _each_plugins_method(type, &block)
|
37
|
+
# For each plugin...
|
38
|
+
_stasis.plugins.each do |plugin|
|
39
|
+
_each_plugin_method(plugin, type, &block)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Using all `Plugin` instances of a certain priority, call methods of a certain type.
|
44
|
+
def _send_to_plugin(priority, type)
|
45
|
+
_each_plugins_method(type) do |plugin, method, real_method|
|
46
|
+
# If priority matches and plugin responds to method...
|
47
|
+
if plugin.class._priority == priority && plugin.respond_to?(real_method)
|
48
|
+
# Call plugin method.
|
49
|
+
plugin.send(real_method)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# `Action` provides a scope for markup rendered via [Tilt][ti] and `before` blocks within
|
2
|
+
# a controller.
|
3
|
+
#
|
4
|
+
# [ti]: https://github.com/rtomayko/tilt
|
5
|
+
|
6
|
+
class Stasis
|
7
|
+
class Action < Scope
|
8
|
+
|
9
|
+
# `String` -- Path to the layout for this action.
|
10
|
+
attr_accessor :_layout
|
11
|
+
|
12
|
+
# `String` -- If present, render this path instead of the default.
|
13
|
+
attr_accessor :_render
|
14
|
+
|
15
|
+
def initialize(stasis)
|
16
|
+
@_stasis = stasis
|
17
|
+
|
18
|
+
# Some plugins define methods to be made available to action scopes. This call
|
19
|
+
# binds those methods.
|
20
|
+
@_stasis.plugins.each do |plugin|
|
21
|
+
_bind_plugin(plugin, :action_method)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|