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