merb_comatose 0.0.2

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 YOUR NAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,4 @@
1
+ merb_comatose
2
+ =============
3
+
4
+ A plugin for the Merb framework that provides ...
data/Rakefile ADDED
@@ -0,0 +1,52 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ require 'merb-core'
5
+ require 'merb-core/tasks/merb'
6
+
7
+ GEM_NAME = "merb_comatose"
8
+ GEM_VERSION = "0.0.2"
9
+ AUTHOR = "Jacques Crocker"
10
+ EMAIL = "merbjedi@gmail.com"
11
+ HOMEPAGE = "http://merbjedi.com/"
12
+ SUMMARY = "Partial Port of Comatose with focus on rendering from Comatose Pages (no admin yet)"
13
+
14
+ spec = Gem::Specification.new do |s|
15
+ s.rubyforge_project = 'merb'
16
+ s.name = GEM_NAME
17
+ s.version = GEM_VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+ s.has_rdoc = true
20
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
21
+ s.summary = SUMMARY
22
+ s.description = s.summary
23
+ s.author = AUTHOR
24
+ s.email = EMAIL
25
+ s.homepage = HOMEPAGE
26
+
27
+ # s.add_dependency('merb', '>= 1.0')
28
+
29
+ s.require_path = 'lib'
30
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec}/**/*")
31
+ end
32
+
33
+ Rake::GemPackageTask.new(spec) do |pkg|
34
+ pkg.gem_spec = spec
35
+ end
36
+
37
+ desc "install the plugin as a gem"
38
+ task :install do
39
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
40
+ end
41
+
42
+ desc "Uninstall the gem"
43
+ task :uninstall do
44
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
45
+ end
46
+
47
+ desc "Create a gemspec file"
48
+ task :gemspec do
49
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
50
+ file.puts spec.to_ruby
51
+ end
52
+ end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ TODO:
2
+ Fix LICENSE with your name
3
+ Fix Rakefile with your name and contact info
4
+ Add your code to lib/merb_comatose.rb
5
+ Add your Merb rake tasks to lib/merb_comatose/merbtasks.rb
@@ -0,0 +1,36 @@
1
+ class Class
2
+ def define_option(name, default=nil)
3
+ sym = name.to_sym
4
+ cattr_reader(sym)
5
+ cattr_writer(sym)
6
+ send("#{name.to_s}=", default)
7
+ end
8
+
9
+ def blockable_attr_accessor(sym)
10
+ module_eval(<<-EVAL, __FILE__, __LINE__)
11
+ def #{sym}(&block)
12
+ if block_given?
13
+ @#{sym} = block
14
+ else
15
+ @#{sym}
16
+ end
17
+ end
18
+ def #{sym}=(value)
19
+ @#{sym} = value
20
+ end
21
+ EVAL
22
+ end
23
+ end
24
+
25
+ class Module
26
+ def attr_accessor_with_default(sym, default = nil, &block)
27
+ raise 'Default value or block required' unless !default.nil? || block
28
+ define_method(sym, block_given? ? block : Proc.new { default })
29
+ module_eval(<<-EVAL, __FILE__, __LINE__)
30
+ def #{sym}=(value)
31
+ class << self; attr_reader :#{sym} end
32
+ @#{sym} = value
33
+ end
34
+ EVAL
35
+ end
36
+ end
@@ -0,0 +1,64 @@
1
+ module Comatose
2
+
3
+ class ComatoseDrop < ::Liquid::Drop
4
+
5
+ private :initialize
6
+
7
+ def before_method(method)
8
+ self.send(method.to_sym)
9
+ rescue
10
+ ComatoseController.logger.debug "Error calling #{method}: #{$!}"
11
+ raise $!
12
+ end
13
+
14
+ class << self
15
+ # Define a new ComatoseDrop by name
16
+ def define( name, &block )
17
+ d = ComatoseDrop.new
18
+ d.instance_eval(&block)
19
+ Comatose.registered_drops[name] = d
20
+ rescue
21
+ ComatoseController.logger.debug "Drop '#{name}' was not included: #{$!}"
22
+ end
23
+ end
24
+ end
25
+
26
+ class << self
27
+
28
+ # Returns/initializes a hash for storing ComatoseDrops
29
+ def registered_drops
30
+ @registered_drops ||= {}
31
+ end
32
+
33
+ # Simple wrapper around the ProcessingContext.define method
34
+ # I think Comatose.define_drop is probably simpler to remember too
35
+ def define_drop(name, &block)
36
+ ComatoseDrop.define(name, &block)
37
+ end
38
+
39
+ # Registers a 'filter' for Liquid use
40
+ def register_filter(module_name)
41
+ Liquid::Template.register_filter(module_name)
42
+ end
43
+
44
+ end
45
+ end
46
+
47
+
48
+ #
49
+ # Some Default Filters/Drops
50
+ #
51
+
52
+ module IncludeFilter
53
+ def include(input)
54
+ page = ComatosePage.find_by_path(input)
55
+ params = @context['params']
56
+ # TODO: Add more of the context into the included page's context...
57
+ page.to_html( { 'params' => params } )
58
+ rescue
59
+ "Page at <tt>#{input}</tt> could not be found. <pre>#{$!}</pre>"
60
+ end
61
+ end
62
+
63
+ Comatose.register_filter IncludeFilter
64
+
@@ -0,0 +1,69 @@
1
+ module Comatose
2
+
3
+ def self.config
4
+ @@config ||= Configuration.new
5
+ end
6
+
7
+ def self.configure(&block)
8
+ raise "#configure must be sent a block" unless block_given?
9
+ yield config
10
+ config.validate!
11
+ end
12
+
13
+ # All of the 'mount' points for Comatose
14
+ def self.mount_points
15
+ @@mount_points ||= []
16
+ end
17
+
18
+ # Adds a 'mount' point to the list of known roots...
19
+ def self.add_mount_point(path, info={:index=>''})
20
+ path = "/#{path}" unless path.starts_with? '/'
21
+ info[:root]=path
22
+ mount_points << info
23
+ end
24
+
25
+ class Configuration
26
+
27
+ attr_accessor_with_default :admin_title, 'Comatose'
28
+ attr_accessor_with_default :admin_includes, []
29
+ attr_accessor_with_default :admin_helpers, []
30
+ attr_accessor_with_default :admin_sub_title, 'The Micro CMS'
31
+ attr_accessor_with_default :content_type, 'utf-8'
32
+ attr_accessor_with_default :default_filter, '[No Filter]'
33
+ attr_accessor_with_default :default_processor, :liquid
34
+ attr_accessor_with_default :default_tree_level, 2
35
+ attr_accessor_with_default :disable_caching, false
36
+ attr_accessor_with_default :hidden_meta_fields, []
37
+ attr_accessor_with_default :helpers, []
38
+ attr_accessor_with_default :includes, []
39
+ attr_accessor_with_default :allow_import_export, true
40
+
41
+ # 'Blockable' setters
42
+ blockable_attr_accessor :authorization
43
+ blockable_attr_accessor :admin_authorization
44
+ blockable_attr_accessor :admin_get_author
45
+ blockable_attr_accessor :admin_get_root_page
46
+ blockable_attr_accessor :after_setup
47
+
48
+ def initialize
49
+ # Default procs for blockable attrs....
50
+ @authorization = Proc.new { true }
51
+ @admin_authorization = Proc.new { true }
52
+ @admin_get_author = Proc.new { request.env['REMOTE_ADDR'] }
53
+ @admin_get_root_page = Proc.new { ComatosePage.root }
54
+ @after_setup = Proc.new { true }
55
+ end
56
+
57
+ def validate!
58
+ # Rips through the config and validates it's, er, valid
59
+ raise ConfigurationError.new( "admin_get_author must be a Proc or Symbol" ) unless @admin_get_author.is_a? Proc or @admin_get_author.is_a? Symbol
60
+ raise ConfigurationError.new( "admin_authorization must be a Proc or Symbol" ) unless @admin_authorization.is_a? Proc or @admin_authorization.is_a? Symbol
61
+ raise ConfigurationError.new( "authorization must be a Proc or Symbol" ) unless @authorization.is_a? Proc or @authorization.is_a? Symbol
62
+ true
63
+ end
64
+
65
+ class ConfigurationError < StandardError; end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,119 @@
1
+ # Giving direct access to the AR model is disconcerting, so this wraps the AR
2
+ # Model to prevent access to certain, destructive behavior
3
+ class Comatose::PageWrapper
4
+
5
+ @@allowed_methods = %w(id full_path uri slug keywords title to_html filter_type author updated_on created_on)
6
+ @@custom_methods = %w(link content parent next previous children rchildren first_child last_child has_keyword)
7
+
8
+ attr_accessor :page
9
+ private :page
10
+
11
+ def initialize( page, locals={} )
12
+ @page = page
13
+ @keyword_lst = []
14
+ @keyword_hsh = {}
15
+ unless page.nil?
16
+ @keyword_lst = (page.keywords || '').downcase.split(',').map {|k| k.strip }
17
+ @keyword_lst.each {|kw| @keyword_hsh[kw] = true}
18
+ end
19
+ @locals = locals
20
+ end
21
+
22
+ def has_keyword?(keyword)
23
+ @page.has_keyword?(keyword)
24
+ end
25
+
26
+ def has_keyword
27
+ @keyword_hsh
28
+ end
29
+
30
+ def next
31
+ @next_page ||= begin
32
+ if @page.lower_item.nil?
33
+ nil
34
+ else
35
+ Comatose::PageWrapper.new( @page.lower_item )
36
+ end
37
+ end
38
+ end
39
+
40
+ def previous
41
+ @prev_page ||= begin
42
+ if @page.higher_item.nil?
43
+ nil
44
+ else
45
+ Comatose::PageWrapper.new( @page.higher_item )
46
+ end
47
+ end
48
+ end
49
+
50
+ def first_child
51
+ children.first
52
+ end
53
+
54
+ def def last_child
55
+ children.last
56
+ end
57
+
58
+ # Generates a link to this page... You can pass in the link text,
59
+ # otherwise it will default to the page title.
60
+ def link( title=nil )
61
+ title = @page.title if title.nil?
62
+ TextFilters[@page.filter_type].create_link(title, @page.uri)
63
+ end
64
+
65
+ def content
66
+ @page.to_html( @locals )
67
+ end
68
+
69
+ def parent
70
+ @parent ||= begin
71
+ if @page.parent.nil?
72
+ nil
73
+ else
74
+ Comatose::PageWrapper.new( @page.parent )
75
+ end
76
+ end
77
+ end
78
+
79
+ def children
80
+ # Cache the results so that you can have multiple calls to #children without a penalty
81
+ @children ||= @page.children.to_a.collect {|child| Comatose::PageWrapper.new( child, @locals )}
82
+ end
83
+
84
+ # Children, in reverse order
85
+ def rchildren
86
+ children.reverse
87
+ end
88
+
89
+ def [](key)
90
+ if @@allowed_methods.include? key.to_s #or page.attributes.has_key? key.to_s
91
+ page.send( key )
92
+ elsif @@custom_methods.include? key
93
+ self.send(key.to_sym)
94
+ end
95
+ end
96
+
97
+ def has_key?(key)
98
+ @@allowed_methods.include? key or @@custom_methods.include? key
99
+ end
100
+
101
+ def to_s
102
+ @page.title
103
+ end
104
+
105
+ def to_liquid
106
+ self
107
+ end
108
+
109
+ def method_missing(method_id, *args)
110
+ #STDERR.puts "Looking for method: #{method_id} (#{method_id.class.to_s}) in [#{@@allowed_methods.join(', ')}] or [#{page.attributes.keys.join(', ')}]"
111
+ if @@allowed_methods.include? method_id.to_s or page.attributes.has_key? method_id.to_s
112
+ page.send( method_id, *args)
113
+ else
114
+ # Access nazi says: NO ACCESS FOR YOU!!
115
+ # but he says it silently...
116
+ end
117
+ end
118
+
119
+ end
@@ -0,0 +1,69 @@
1
+ # This is the rendering context object you have access to in text processing...
2
+ class Comatose::ProcessingContext
3
+ @@supported_methods = %w(page include)
4
+
5
+ def initialize( page, locals={} )
6
+ @locals = locals.stringify_keys if locals.respond_to? :stringify_keys
7
+ @page = Comatose::PageWrapper.new(page, @locals)
8
+ end
9
+
10
+ def page
11
+ @page
12
+ end
13
+
14
+ def include(path, locals={})
15
+ begin
16
+ page = ComatosePage.find_by_path(path)
17
+ page.to_html( @locals.merge(locals) )
18
+ rescue
19
+ "<p>Page at <tt>#{path}</tt> could not be found.</p>"
20
+ end
21
+ end
22
+
23
+ def find_by_path(path)
24
+ begin
25
+ page = ComatosePage.find_by_path(path)
26
+ Comatose::PageWrapper.new(page, @locals)
27
+ rescue
28
+ "<p>Page at <tt>#{path}</tt> could not be found.</p>"
29
+ end
30
+ end
31
+
32
+ def [](key)
33
+ if key.to_s.downcase == 'page'
34
+ @page
35
+ elsif @locals.has_key? key
36
+ @locals[key]
37
+ elsif Comatose.registered_drops.has_key? key
38
+ Comatose.registered_drops[key]
39
+ end
40
+ end
41
+
42
+ def has_key?(key)
43
+ @@supported_methods.include?(key) or @locals.has_key?(key) or Comatose.registered_drops.has_key?(key)
44
+ end
45
+
46
+ def to_liquid
47
+ self
48
+ end
49
+
50
+ def get_binding
51
+ binding
52
+ end
53
+
54
+ def method_missing(method_id, *args)
55
+ method_name = method_id.to_s
56
+ if @locals.has_key? method_name
57
+ @locals[method_name]
58
+ elsif Comatose.registered_drops.has_key? method_name
59
+ Comatose.registered_drops[method_name].context = self
60
+ Comatose.registered_drops[method_name]
61
+ else
62
+ "<!-- ProcessingContext: Couldn't find #{method_id} -->"
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+
69
+
@@ -0,0 +1,4 @@
1
+ module Comatose
2
+ VERSION = "2.0.5"
3
+ VERSION_STRING = "#{VERSION} (beta)"
4
+ end
@@ -0,0 +1,96 @@
1
+ module Liquid
2
+
3
+ class Block < Tag
4
+ def parse(tokens)
5
+ @nodelist ||= []
6
+ @nodelist.clear
7
+
8
+ while token = tokens.shift
9
+
10
+ case token
11
+ when /^#{TagStart}/
12
+ if token =~ /^#{TagStart}\s*(\w+)\s*(.*)?#{TagEnd}$/
13
+
14
+ # if we found the proper block delimitor just end parsing here and let the outer block
15
+ # proceed
16
+ if block_delimiter == $1
17
+ end_tag
18
+ return
19
+ end
20
+
21
+ # fetch the tag from registered blocks
22
+ if tag = Template.tags[$1]
23
+ @nodelist << tag.new($2, tokens)
24
+ else
25
+ # this tag is not registered with the system
26
+ # pass it to the current block for special handling or error reporting
27
+ unknown_tag($1, $2, tokens)
28
+ end
29
+ else
30
+ raise SyntaxError, "Tag '#{token}' was not properly terminated with regexp: #{TagEnd.inspect} "
31
+ end
32
+ when /^#{VariableStart}/
33
+ @nodelist << create_variable(token)
34
+ when ''
35
+ # pass
36
+ else
37
+ @nodelist << token
38
+ end
39
+ end
40
+
41
+ # Make sure that its ok to end parsing in the current block.
42
+ # Effectively this method will throw and exception unless the current block is
43
+ # of type Document
44
+ assert_missing_delimitation!
45
+ end
46
+
47
+ def end_tag
48
+ end
49
+
50
+ def unknown_tag(tag, params, tokens)
51
+ case tag
52
+ when 'else'
53
+ raise SyntaxError, "#{block_name} tag does not expect else tag"
54
+ when 'end'
55
+ raise SyntaxError, "'end' is not a valid delimiter for #{block_name} tags. use #{block_delimiter}"
56
+ else
57
+ raise SyntaxError, "Unknown tag '#{tag}'"
58
+ end
59
+ end
60
+
61
+ def block_delimiter
62
+ "end#{block_name}"
63
+ end
64
+
65
+ def block_name
66
+ self.class.name.scan(/\w+$/).first.downcase
67
+ end
68
+
69
+ def create_variable(token)
70
+ token.scan(/^#{VariableStart}(.*)#{VariableEnd}$/) do |content|
71
+ return Variable.new(content.first)
72
+ end
73
+ raise SyntaxError.new("Variable '#{token}' was not properly terminated with regexp: #{VariableEnd.inspect} ")
74
+ end
75
+
76
+ def render(context)
77
+ render_all(@nodelist, context)
78
+ end
79
+
80
+ protected
81
+
82
+ def assert_missing_delimitation!
83
+ raise SyntaxError.new("#{block_name} tag was never closed")
84
+ end
85
+
86
+ def render_all(list, context)
87
+ list.collect do |token|
88
+ if token.respond_to?(:render)
89
+ token.render(context)
90
+ else
91
+ token.to_s
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end