staugaard-comatose 2.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.
Files changed (93) hide show
  1. data/CHANGELOG +176 -0
  2. data/INSTALL +19 -0
  3. data/LICENSE +20 -0
  4. data/MANIFEST +91 -0
  5. data/README.rdoc +148 -0
  6. data/Rakefile +122 -0
  7. data/SPECS +61 -0
  8. data/about.yml +7 -0
  9. data/bin/comatose +109 -0
  10. data/comatose.gemspec +113 -0
  11. data/generators/comatose_migration/USAGE +15 -0
  12. data/generators/comatose_migration/comatose_migration_generator.rb +59 -0
  13. data/generators/comatose_migration/templates/migration.rb +35 -0
  14. data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
  15. data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
  16. data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
  17. data/init.rb +2 -0
  18. data/install.rb +16 -0
  19. data/lib/acts_as_versioned.rb +543 -0
  20. data/lib/comatose.rb +33 -0
  21. data/lib/comatose/comatose_drop.rb +79 -0
  22. data/lib/comatose/configuration.rb +68 -0
  23. data/lib/comatose/page_wrapper.rb +119 -0
  24. data/lib/comatose/processing_context.rb +69 -0
  25. data/lib/comatose/tasks/admin.rb +60 -0
  26. data/lib/comatose/tasks/data.rb +82 -0
  27. data/lib/comatose/tasks/setup.rb +52 -0
  28. data/lib/comatose/version.rb +4 -0
  29. data/lib/comatose_admin_controller.rb +349 -0
  30. data/lib/comatose_admin_helper.rb +37 -0
  31. data/lib/comatose_controller.rb +142 -0
  32. data/lib/comatose_helper.rb +3 -0
  33. data/lib/comatose_page.rb +141 -0
  34. data/lib/liquid.rb +52 -0
  35. data/lib/liquid/block.rb +96 -0
  36. data/lib/liquid/context.rb +190 -0
  37. data/lib/liquid/document.rb +17 -0
  38. data/lib/liquid/drop.rb +48 -0
  39. data/lib/liquid/errors.rb +7 -0
  40. data/lib/liquid/extensions.rb +53 -0
  41. data/lib/liquid/file_system.rb +62 -0
  42. data/lib/liquid/htmltags.rb +64 -0
  43. data/lib/liquid/standardfilters.rb +111 -0
  44. data/lib/liquid/standardtags.rb +399 -0
  45. data/lib/liquid/strainer.rb +42 -0
  46. data/lib/liquid/tag.rb +25 -0
  47. data/lib/liquid/template.rb +88 -0
  48. data/lib/liquid/variable.rb +39 -0
  49. data/lib/redcloth.rb +1129 -0
  50. data/lib/support/class_options.rb +36 -0
  51. data/lib/support/inline_rendering.rb +48 -0
  52. data/lib/support/route_mapper.rb +50 -0
  53. data/lib/text_filters.rb +138 -0
  54. data/lib/text_filters/markdown.rb +14 -0
  55. data/lib/text_filters/markdown_smartypants.rb +15 -0
  56. data/lib/text_filters/none.rb +8 -0
  57. data/lib/text_filters/rdoc.rb +13 -0
  58. data/lib/text_filters/simple.rb +8 -0
  59. data/lib/text_filters/textile.rb +15 -0
  60. data/rails/init.rb +3 -0
  61. data/resources/layouts/comatose_admin_template.html.erb +28 -0
  62. data/resources/public/images/collapsed.gif +0 -0
  63. data/resources/public/images/expanded.gif +0 -0
  64. data/resources/public/images/no-children.gif +0 -0
  65. data/resources/public/images/page.gif +0 -0
  66. data/resources/public/images/spinner.gif +0 -0
  67. data/resources/public/images/title-hover-bg.gif +0 -0
  68. data/resources/public/javascripts/comatose_admin.js +401 -0
  69. data/resources/public/stylesheets/comatose_admin.css +381 -0
  70. data/tasks/comatose.rake +9 -0
  71. data/test/behaviors.rb +106 -0
  72. data/test/fixtures/comatose_pages.yml +96 -0
  73. data/test/functional/comatose_admin_controller_test.rb +112 -0
  74. data/test/functional/comatose_controller_test.rb +44 -0
  75. data/test/javascripts/test.html +26 -0
  76. data/test/javascripts/test_runner.js +307 -0
  77. data/test/test_helper.rb +43 -0
  78. data/test/unit/class_options_test.rb +52 -0
  79. data/test/unit/comatose_page_test.rb +128 -0
  80. data/test/unit/processing_context_test.rb +108 -0
  81. data/test/unit/text_filters_test.rb +52 -0
  82. data/views/comatose_admin/_form.html.erb +96 -0
  83. data/views/comatose_admin/_page_list_item.html.erb +60 -0
  84. data/views/comatose_admin/delete.html.erb +18 -0
  85. data/views/comatose_admin/edit.html.erb +5 -0
  86. data/views/comatose_admin/index.html.erb +18 -0
  87. data/views/comatose_admin/new.html.erb +5 -0
  88. data/views/comatose_admin/reorder.html.erb +30 -0
  89. data/views/comatose_admin/versions.html.erb +40 -0
  90. data/views/layouts/comatose_admin.html.erb +814 -0
  91. data/views/layouts/comatose_admin_customize.html.erb +28 -0
  92. data/views/layouts/comatose_content.html.erb +17 -0
  93. metadata +147 -0
@@ -0,0 +1,3 @@
1
+ module ComatoseHelper
2
+
3
+ end
@@ -0,0 +1,141 @@
1
+ # ComatosePage attributes
2
+ # - parent_id
3
+ # - title
4
+ # - full_path
5
+ # - slug
6
+ # - keywords
7
+ # - body
8
+ # - author
9
+ # - filter_type
10
+ # - position
11
+ # - version
12
+ # - updated_on
13
+ # - created_on
14
+ class ComatosePage < ActiveRecord::Base
15
+
16
+ set_table_name 'comatose_pages'
17
+
18
+ # Only versions the content... Not all of the meta data or position
19
+ acts_as_versioned :table_name=>'comatose_page_versions', :if_changed => [:title, :slug, :keywords, :body]
20
+
21
+ define_option :active_mount_info, {:root=>'', :index=>''}
22
+
23
+ acts_as_tree :order => "position, title"
24
+ acts_as_list :scope => :parent_id
25
+
26
+ #before_create :create_full_path
27
+ before_save :cache_full_path, :create_full_path
28
+ after_save :update_children_full_path
29
+
30
+ # Using before_validation so we can default the slug from the title
31
+ before_validation do |record|
32
+ # Create slug from title
33
+ if record.slug.blank? and !record.title.blank?
34
+ record.slug = record.title.downcase.lstrip.rstrip.gsub( /[^-a-z0-9~\s\.:;+=_]/, '').gsub(/[\s\.:;=_+]+/, '-').gsub(/[\-]{2,}/, '-').to_s
35
+ end
36
+ end
37
+
38
+ # Manually set these, because record_timestamps = false
39
+ before_create do |record|
40
+ record.created_on = record.updated_on = Time.now
41
+ end
42
+
43
+ validates_presence_of :title, :on => :save, :message => "must be present"
44
+ validates_uniqueness_of :slug, :on => :save, :scope=>'parent_id', :message => "is already in use"
45
+ validates_presence_of :parent_id, :on=>:create, :message=>"must be present"
46
+
47
+ # Tests ERB/Liquid content...
48
+ validates_each :body, :allow_nil=>true, :allow_blank=>true do |record, attr, value|
49
+ begin
50
+ body_html = record.to_html
51
+ rescue SyntaxError
52
+ record.errors.add :body, "syntax error: #{$!.to_s.gsub('<', '&lt;')}"
53
+ rescue
54
+ record.errors.add :body, "content error: #{$!.to_s.gsub('<', '&lt;')}"
55
+ end
56
+ end
57
+
58
+ # Returns a pages URI dynamically, based on the active mount point
59
+ def uri
60
+ if full_path == ''
61
+ active_mount_info[:root]
62
+ else
63
+ page_path = (full_path || '').split('/')
64
+ idx_path = active_mount_info[:index].split('/')
65
+ uri_root = active_mount_info[:root].split('/')
66
+ uri_path = ( uri_root + (page_path - idx_path) ).flatten.delete_if {|i| i == "" }
67
+ ['',uri_path].join('/')
68
+ end
69
+ end
70
+
71
+ # Check if a page has a selected keyword... NOT case sensitive.
72
+ # So the keyword McCray is the same as mccray
73
+ def has_keyword?(keyword)
74
+ @key_list ||= (self.keywords || '').downcase.split(',').map {|k| k.strip }
75
+ @key_list.include? keyword.to_s.downcase
76
+ rescue
77
+ false
78
+ end
79
+
80
+ # Returns the page's content, transformed and filtered...
81
+ def to_html(options={})
82
+ #version = options.delete(:version)
83
+ text = self.body
84
+ binding = Comatose::ProcessingContext.new(self, options)
85
+ filter_type = self.filter_type || '[No Filter]'
86
+ TextFilters.transform(text, binding, filter_type, Comatose.config.default_processor)
87
+ end
88
+
89
+ # Static helpers...
90
+
91
+ # Returns a Page with a matching path.
92
+ def self.find_by_path( path )
93
+ path = path.split('.')[0] unless path.empty? # Will ignore file extension...
94
+ path = path[1..-1] if path.starts_with? "/"
95
+ find( :first, :conditions=>[ 'full_path = ?', path ] )
96
+ end
97
+
98
+ # Overrides...
99
+
100
+ # I don't want the AR magic timestamping support for this class...
101
+ def record_timestamps
102
+ false
103
+ end
104
+ def self.record_timestamps
105
+ false
106
+ end
107
+
108
+ protected
109
+
110
+ # Creates a URI path based on the Page tree
111
+ def create_full_path
112
+ if parent_node = self.parent
113
+ # Build URI Path
114
+ path = "#{parent_node.full_path}/#{self.slug}"
115
+ # strip leading space, if there is one...
116
+ path = path[1..-1] if path.starts_with? "/"
117
+ self.full_path = path || ""
118
+ else
119
+ # I'm the root -- My path is blank
120
+ self.full_path = ""
121
+ end
122
+ end
123
+ def create_full_path!
124
+ create_full_path
125
+ save
126
+ end
127
+
128
+ # Caches old path (before save) for comparison later
129
+ def cache_full_path
130
+ @old_full_path = self.full_path
131
+ end
132
+
133
+ # Updates all this content's child URI paths
134
+ def update_children_full_path(should_save=true)
135
+ # OPTIMIZE: Only update all the children if the :slug/:fullpath is different
136
+ for child in self.children
137
+ child.create_full_path! unless child.frozen?
138
+ child.update_children_full_path(should_save)
139
+ end
140
+ end
141
+ end
data/lib/liquid.rb ADDED
@@ -0,0 +1,52 @@
1
+ # Copyright (c) 2005 Tobias Luetke
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 PURPOa AND
17
+ # NONINFRINGEMENT. IN NO EVENT SaALL 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.
21
+
22
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
23
+
24
+ module Liquid
25
+ FilterSperator = /\|/
26
+ ArgumentSeparator = ','
27
+ FilterArgumentSeparator = ':'
28
+ VariableAttributeSeparator = '.'
29
+ TagStart = /\{\%/
30
+ TagEnd = /\%\}/
31
+ VariableStart = /\{\{/
32
+ VariableEnd = /\}\}/
33
+ AllowedVariableCharacters = /[a-zA-Z_.-]/
34
+ QuotedFragment = /"[^"]+"|'[^']+'|[^\s,|]+/
35
+ TagAttributes = /(\w+)\s*\:\s*(#{QuotedFragment})/
36
+ TokenizationRegexp = /(#{TagStart}.*?#{TagEnd}|#{VariableStart}.*?#{VariableEnd})/
37
+ end
38
+
39
+ require 'liquid/drop'
40
+ require 'liquid/extensions'
41
+ require 'liquid/errors'
42
+ require 'liquid/strainer'
43
+ require 'liquid/context'
44
+ require 'liquid/tag'
45
+ require 'liquid/block'
46
+ require 'liquid/document'
47
+ require 'liquid/variable'
48
+ require 'liquid/file_system'
49
+ require 'liquid/template'
50
+ require 'liquid/standardtags'
51
+ require 'liquid/htmltags'
52
+ require 'liquid/standardfilters'
@@ -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
@@ -0,0 +1,190 @@
1
+ module Liquid
2
+
3
+ class ContextError < StandardError
4
+ end
5
+
6
+ # Context keeps the variable stack and resolves variables, as well as keywords
7
+ #
8
+ # context['variable'] = 'testing'
9
+ # context['variable'] #=> 'testing'
10
+ # context['true'] #=> true
11
+ # context['10.2232'] #=> 10.2232
12
+ #
13
+ # context.stack do
14
+ # context['bob'] = 'bobsen'
15
+ # end
16
+ #
17
+ # context['bob'] #=> nil class Context
18
+ class Context
19
+ attr_reader :assigns
20
+ attr_accessor :registers
21
+
22
+ def initialize(assigns = {}, registers = nil)
23
+ @assigns = [assigns]
24
+ @registers = registers || {}
25
+ end
26
+
27
+ def strainer
28
+ @strainer ||= Strainer.create(self)
29
+ end
30
+
31
+ # adds filters to this context.
32
+ # this does not register the filters with the main Template object. see <tt>Template.register_filter</tt>
33
+ # for that
34
+ def add_filters(filter)
35
+ return unless filter.is_a?(Module)
36
+ strainer.extend(filter)
37
+ end
38
+
39
+ def invoke(method, *args)
40
+ if strainer.respond_to?(method)
41
+ strainer.__send__(method, *args)
42
+ else
43
+ return args[0]
44
+ end
45
+ end
46
+
47
+ # push new local scope on the stack. use <tt>Context#stack</tt> instead
48
+ def push
49
+ @assigns.unshift({})
50
+ end
51
+
52
+ # merge a hash of variables in the current local scope
53
+ def merge(new_assigns)
54
+ @assigns[0].merge!(new_assigns)
55
+ end
56
+
57
+ # pop from the stack. use <tt>Context#stack</tt> instead
58
+ def pop
59
+ raise ContextError if @assigns.size == 1
60
+ @assigns.shift
61
+ end
62
+
63
+ # pushes a new local scope on the stack, pops it at the end of the block
64
+ #
65
+ # Example:
66
+ #
67
+ # context.stack do
68
+ # context['var'] = 'hi'
69
+ # end
70
+ # context['var] #=> nil
71
+ #
72
+ def stack(&block)
73
+ push
74
+ begin
75
+ result = yield
76
+ ensure
77
+ pop
78
+ end
79
+ result
80
+ end
81
+
82
+ # Only allow String, Numeric, Hash, Array or <tt>Liquid::Drop</tt>
83
+ def []=(key, value)
84
+ @assigns[0][key] = value
85
+ end
86
+
87
+ def [](key)
88
+ resolve(key)
89
+ end
90
+
91
+ def has_key?(key)
92
+ resolve(key) != nil
93
+ end
94
+
95
+ private
96
+
97
+ # Look up variable, either resolve directly after considering the name. We can directly handle
98
+ # Strings, digits, floats and booleans (true,false). If no match is made we lookup the variable in the current scope and
99
+ # later move up to the parent blocks to see if we can resolve the variable somewhere up the tree.
100
+ # Some special keywords return symbols. Those symbols are to be called on the rhs object in expressions
101
+ #
102
+ # Example:
103
+ #
104
+ # products == empty #=> products.empty?
105
+ #
106
+ def resolve(key)
107
+ case key
108
+ when nil
109
+ nil
110
+ when 'true'
111
+ true
112
+ when 'false'
113
+ false
114
+ when 'empty'
115
+ :empty?
116
+ when 'nil', 'null'
117
+ nil
118
+ # Single quoted strings
119
+ when /^'(.*)'$/
120
+ $1.to_s
121
+ # Double quoted strings
122
+ when /^"(.*)"$/
123
+ $1.to_s
124
+ # Integer and floats
125
+ when /^(\d+)$/
126
+ $1.to_i
127
+ when /^(\d[\d\.]+)$/
128
+ $1.to_f
129
+ else
130
+ variable(key)
131
+ end
132
+ end
133
+
134
+ # fetches an object starting at the local scope and then moving up
135
+ # the hierachy
136
+ def fetch(key)
137
+ begin
138
+ for scope in @assigns
139
+ if scope.has_key?(key)
140
+ obj = scope[key]
141
+ if obj.is_a?(Liquid::Drop)
142
+ obj.context = self
143
+ end
144
+ return obj
145
+ end
146
+ end
147
+ rescue => e
148
+ raise ContextError, "Could not fetch key #{key} from context: " + e.message
149
+ end
150
+ nil
151
+ end
152
+
153
+ # resolves namespaced queries gracefully.
154
+ #
155
+ # Example
156
+ #
157
+ # @context['hash'] = {"name" => 'tobi'}
158
+ # assert_equal 'tobi', @context['hash.name']
159
+ def variable(key)
160
+ parts = key.split(VariableAttributeSeparator)
161
+
162
+
163
+ if object = fetch(parts.shift).to_liquid
164
+ object.context = self if object.is_a?(Liquid::Drop)
165
+
166
+ while not parts.size.zero?
167
+ next_part_name = parts.shift
168
+
169
+ # If the last part of the context variable is .size we just
170
+ # return the count of the objects in this object
171
+ if next_part_name == 'size' and parts.empty?
172
+ return object.size if object.is_a?(Array)
173
+ return object.size if object.is_a?(Hash) && !object.has_key?(next_part_name)
174
+ end
175
+
176
+ return nil if not object.respond_to?(:has_key?)
177
+ return nil if not object.has_key?(next_part_name)
178
+
179
+ object = object[next_part_name].to_liquid
180
+ object.context = self if object.is_a?(Liquid::Drop)
181
+ end
182
+
183
+ object
184
+ else
185
+ nil
186
+ end
187
+ end
188
+
189
+ end
190
+ end