stasis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +18 -0
  3. data/README.md +287 -0
  4. data/Rakefile +109 -0
  5. data/bin/stasis +30 -0
  6. data/config/gemsets.yml +9 -0
  7. data/config/gemspec.yml +17 -0
  8. data/lib/stasis.rb +291 -0
  9. data/lib/stasis/dev_mode.rb +55 -0
  10. data/lib/stasis/gems.rb +154 -0
  11. data/lib/stasis/plugin.rb +76 -0
  12. data/lib/stasis/plugins/before.rb +50 -0
  13. data/lib/stasis/plugins/helpers.rb +29 -0
  14. data/lib/stasis/plugins/ignore.rb +35 -0
  15. data/lib/stasis/plugins/instead.rb +15 -0
  16. data/lib/stasis/plugins/layout.rb +51 -0
  17. data/lib/stasis/plugins/priority.rb +41 -0
  18. data/lib/stasis/plugins/render.rb +71 -0
  19. data/lib/stasis/scope.rb +54 -0
  20. data/lib/stasis/scope/action.rb +25 -0
  21. data/lib/stasis/scope/controller.rb +62 -0
  22. data/lib/stasis/server.rb +90 -0
  23. data/site/arrow.png +0 -0
  24. data/site/controller.rb +72 -0
  25. data/site/github.png +0 -0
  26. data/site/index.html.haml +24 -0
  27. data/site/jquery-1.6.2.js +8982 -0
  28. data/site/stasis.css.scss +226 -0
  29. data/site/stasis.js.coffee +42 -0
  30. data/site/stasis.png +0 -0
  31. data/spec/fixtures/gemsets.yml +9 -0
  32. data/spec/fixtures/gemspec.yml +15 -0
  33. data/spec/fixtures/project/_partial.html.haml +1 -0
  34. data/spec/fixtures/project/before_render_partial.html.haml +1 -0
  35. data/spec/fixtures/project/before_render_text.html.haml +1 -0
  36. data/spec/fixtures/project/controller.rb +83 -0
  37. data/spec/fixtures/project/index.html.haml +16 -0
  38. data/spec/fixtures/project/layout.html.haml +3 -0
  39. data/spec/fixtures/project/layout_action.html.haml +1 -0
  40. data/spec/fixtures/project/layout_action_from_subdirectory.html.haml +1 -0
  41. data/spec/fixtures/project/layout_controller.html.haml +1 -0
  42. data/spec/fixtures/project/layout_controller_from_subdirectory.html.haml +1 -0
  43. data/spec/fixtures/project/no_controller/index.html.haml +12 -0
  44. data/spec/fixtures/project/not_dynamic.html +1 -0
  45. data/spec/fixtures/project/plugin.rb +16 -0
  46. data/spec/fixtures/project/subdirectory/_partial.html.haml +1 -0
  47. data/spec/fixtures/project/subdirectory/before_render_partial.html.haml +1 -0
  48. data/spec/fixtures/project/subdirectory/before_render_text.html.haml +1 -0
  49. data/spec/fixtures/project/subdirectory/controller.rb +66 -0
  50. data/spec/fixtures/project/subdirectory/ignore.html.haml +0 -0
  51. data/spec/fixtures/project/subdirectory/index.html.haml +14 -0
  52. data/spec/fixtures/project/subdirectory/layout.html.haml +3 -0
  53. data/spec/fixtures/project/subdirectory/layout_action.html.haml +1 -0
  54. data/spec/fixtures/project/subdirectory/layout_action_from_root.html.haml +1 -0
  55. data/spec/fixtures/project/subdirectory/layout_controller.html.haml +1 -0
  56. data/spec/fixtures/project/subdirectory/layout_controller_from_root.html.haml +1 -0
  57. data/spec/fixtures/project/time.html.haml +2 -0
  58. data/spec/spec_helper.rb +28 -0
  59. data/spec/stasis/gems_spec.rb +249 -0
  60. data/spec/stasis/plugins/before_spec.rb +53 -0
  61. data/spec/stasis/plugins/helpers_spec.rb +16 -0
  62. data/spec/stasis/plugins/ignore_spec.rb +17 -0
  63. data/spec/stasis/plugins/layout_spec.rb +22 -0
  64. data/spec/stasis/plugins/priority_spec.rb +22 -0
  65. data/spec/stasis/plugins/render_spec.rb +23 -0
  66. data/spec/stasis/server_spec.rb +29 -0
  67. data/spec/stasis_spec.rb +46 -0
  68. data/stasis.gemspec +32 -0
  69. 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,15 @@
1
+ class Stasis
2
+ class Instead < Plugin
3
+
4
+ action_method :instead
5
+
6
+ def initialize(stasis)
7
+ @stasis = stasis
8
+ end
9
+
10
+ # This method is bound to all actions.
11
+ def instead(string)
12
+ @stasis.action._render = string
13
+ end
14
+ end
15
+ 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
@@ -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