darthapo-comatose 2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. data/CHANGELOG +171 -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 +20 -0
  10. data/generators/comatose_migration/USAGE +15 -0
  11. data/generators/comatose_migration/comatose_migration_generator.rb +59 -0
  12. data/generators/comatose_migration/templates/migration.rb +35 -0
  13. data/generators/comatose_migration/templates/v4_upgrade.rb +15 -0
  14. data/generators/comatose_migration/templates/v6_upgrade.rb +23 -0
  15. data/generators/comatose_migration/templates/v7_upgrade.rb +22 -0
  16. data/init.rb +10 -0
  17. data/install.rb +16 -0
  18. data/lib/acts_as_versioned.rb +543 -0
  19. data/lib/comatose.rb +23 -0
  20. data/lib/comatose/comatose_drop.rb +79 -0
  21. data/lib/comatose/configuration.rb +68 -0
  22. data/lib/comatose/page_wrapper.rb +119 -0
  23. data/lib/comatose/processing_context.rb +69 -0
  24. data/lib/comatose/tasks/admin.rb +60 -0
  25. data/lib/comatose/tasks/data.rb +82 -0
  26. data/lib/comatose/tasks/setup.rb +52 -0
  27. data/lib/comatose/version.rb +4 -0
  28. data/lib/comatose_admin_controller.rb +348 -0
  29. data/lib/comatose_admin_helper.rb +37 -0
  30. data/lib/comatose_controller.rb +141 -0
  31. data/lib/comatose_helper.rb +3 -0
  32. data/lib/comatose_page.rb +141 -0
  33. data/lib/liquid.rb +52 -0
  34. data/lib/liquid/block.rb +96 -0
  35. data/lib/liquid/context.rb +190 -0
  36. data/lib/liquid/document.rb +17 -0
  37. data/lib/liquid/drop.rb +48 -0
  38. data/lib/liquid/errors.rb +7 -0
  39. data/lib/liquid/extensions.rb +53 -0
  40. data/lib/liquid/file_system.rb +62 -0
  41. data/lib/liquid/htmltags.rb +64 -0
  42. data/lib/liquid/standardfilters.rb +111 -0
  43. data/lib/liquid/standardtags.rb +399 -0
  44. data/lib/liquid/strainer.rb +42 -0
  45. data/lib/liquid/tag.rb +25 -0
  46. data/lib/liquid/template.rb +88 -0
  47. data/lib/liquid/variable.rb +39 -0
  48. data/lib/redcloth.rb +1129 -0
  49. data/lib/support/class_options.rb +36 -0
  50. data/lib/support/inline_rendering.rb +48 -0
  51. data/lib/support/route_mapper.rb +50 -0
  52. data/lib/text_filters.rb +138 -0
  53. data/lib/text_filters/markdown.rb +14 -0
  54. data/lib/text_filters/markdown_smartypants.rb +15 -0
  55. data/lib/text_filters/none.rb +8 -0
  56. data/lib/text_filters/rdoc.rb +13 -0
  57. data/lib/text_filters/simple.rb +8 -0
  58. data/lib/text_filters/textile.rb +15 -0
  59. data/rails/init.rb +12 -0
  60. data/resources/layouts/comatose_admin_template.html.erb +28 -0
  61. data/resources/public/images/collapsed.gif +0 -0
  62. data/resources/public/images/expanded.gif +0 -0
  63. data/resources/public/images/no-children.gif +0 -0
  64. data/resources/public/images/page.gif +0 -0
  65. data/resources/public/images/spinner.gif +0 -0
  66. data/resources/public/images/title-hover-bg.gif +0 -0
  67. data/resources/public/javascripts/comatose_admin.js +401 -0
  68. data/resources/public/stylesheets/comatose_admin.css +381 -0
  69. data/tasks/comatose.rake +9 -0
  70. data/test/behaviors.rb +106 -0
  71. data/test/fixtures/comatose_pages.yml +96 -0
  72. data/test/functional/comatose_admin_controller_test.rb +112 -0
  73. data/test/functional/comatose_controller_test.rb +44 -0
  74. data/test/javascripts/test.html +26 -0
  75. data/test/javascripts/test_runner.js +307 -0
  76. data/test/test_helper.rb +43 -0
  77. data/test/unit/class_options_test.rb +52 -0
  78. data/test/unit/comatose_page_test.rb +128 -0
  79. data/test/unit/processing_context_test.rb +108 -0
  80. data/test/unit/text_filters_test.rb +52 -0
  81. data/views/comatose_admin/_form.html.erb +96 -0
  82. data/views/comatose_admin/_page_list_item.html.erb +60 -0
  83. data/views/comatose_admin/delete.html.erb +18 -0
  84. data/views/comatose_admin/edit.html.erb +5 -0
  85. data/views/comatose_admin/index.html.erb +18 -0
  86. data/views/comatose_admin/new.html.erb +5 -0
  87. data/views/comatose_admin/reorder.html.erb +30 -0
  88. data/views/comatose_admin/versions.html.erb +40 -0
  89. data/views/layouts/comatose_admin.html.erb +814 -0
  90. data/views/layouts/comatose_admin_customize.html.erb +28 -0
  91. data/views/layouts/comatose_content.html.erb +17 -0
  92. metadata +157 -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