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