rote 0.2.4.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,74 @@
1
+ #--
2
+ # Rote filter for TOC generation
3
+ # (c)2005 Ross Bamford (and contributors)
4
+ #
5
+ # See 'rote.rb' or LICENSE for licence information.
6
+ # $Id: toc.rb,v 1.2 2005/12/12 02:45:24 roscopeco Exp $
7
+ #++
8
+
9
+ module Rote
10
+ module Filters
11
+
12
+ #####
13
+ ## Page filter that supports easy construction of a Table Of Contents
14
+ ## from your *layout*. This filter searches for tags matching the
15
+ ## specified regular expression(s) (H tags by default), and stores them
16
+ ## to be used for TOC generation in the layout. HTML Named-anchors are
17
+ ## created based on the headings found.
18
+ ##
19
+ ## Additional attributes for the A tags can be passed via the +attrs+
20
+ ## parameter.
21
+ class TOC
22
+
23
+ # An individual Heading in the +links+ array.
24
+ class Heading
25
+ class << self
26
+ alias :[] :new
27
+ end
28
+
29
+ def initialize(tag, title, attrs = {})
30
+ @tag = tag
31
+ @title = title
32
+ @attrs = attrs
33
+ end
34
+
35
+ # The information held by this link
36
+ attr_accessor :tag, :title, :attrs
37
+
38
+ def anchor
39
+ title.downcase.gsub(/<[^>]+>/,'').gsub(/[^a-z]+/,'_')
40
+ end
41
+
42
+ def to_s
43
+ %Q[<a #{"#{(attrs.collect { |k,v| "#{k}='#{v}'" }).join(' ')} " unless attrs.empty?}href='##{anchor}'>#{title}</a>]
44
+ end
45
+ end
46
+
47
+ def initialize(tags_re = /h\d+/, attrs = {})
48
+ @tags_re = tags_re
49
+ @attrs = attrs
50
+ @headings = []
51
+ end
52
+
53
+ # Array of heading links with the
54
+ # heading title as the link text. Suitable
55
+ # for joining and outputting:
56
+ #
57
+ # <%= links.join(" - ") %>
58
+ #
59
+ # *Note* that this isn't populated until after
60
+ # the filter is run.
61
+ attr_reader :headings
62
+ alias :links :headings
63
+
64
+ def filter(text, page)
65
+ # find headings *and insert named anchors*
66
+ text.gsub(%r[<(#{@tags_re})>(.*?)</\1>]) do
67
+ headings << (h = Heading[$1,$2])
68
+ %Q[<a name='#{h.anchor}'></a>#{$&}]
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,9 @@
1
+ #--
2
+ # Require all formats
3
+ # (c)2005 Ross Bamford (and contributors)
4
+ #
5
+ # See 'rote.rb' or LICENSE for licence information.
6
+ # $Id: format.rb,v 1.2 2005/12/12 02:45:24 roscopeco Exp $
7
+ #++
8
+
9
+ Dir[File.join(File.dirname(__FILE__), 'format/*.rb')].each { |f| require f }
@@ -0,0 +1,49 @@
1
+ #--
2
+ # Rote format helper for HTML
3
+ # (c)2005 Ross Bamford (and contributors)
4
+ #
5
+ # See 'rote.rb' or LICENSE for licence information.
6
+ # $Id: html.rb,v 1.2 2005/12/12 02:45:24 roscopeco Exp $
7
+ #++
8
+ require 'erb'
9
+
10
+ module Rote
11
+ module Format
12
+
13
+ # HTML Formatting module for Rote. This module may be mixed in to any Page
14
+ # instance to provide various HTML helpers (including those from ERB::Util).
15
+ #
16
+ # To use this module for a given page, simply place the following code
17
+ # somewhere applicable to that page:
18
+ #
19
+ # extend Format::HTML
20
+ #
21
+ # Note that +include+ cannot be used since the page code is run via
22
+ # +instance_eval+.
23
+ module HTML
24
+ include ERB::Util
25
+
26
+ ###################################################################
27
+ ## HELPERS
28
+
29
+ # Make the given output-root-relative path relative to the
30
+ # current page's path. This is handy when you do both local
31
+ # preview from some deep directory, and remote deployment
32
+ # to a root
33
+ def relative(href)
34
+ thr = href
35
+
36
+ if thr.is_a?(String) && href[0,1] == '/' # only interested in absolute
37
+ dtfn = File.dirname(template_name) + '/'
38
+
39
+ count = dtfn == './' ? 0 : dtfn.split('/').length
40
+ thr = ('../' * count) + href[1..href.length]
41
+ end
42
+
43
+ thr
44
+ end
45
+
46
+ alias :link_rel :relative # Alias 'link_rel' is deprecated, vv0.2.99 v-0.4
47
+ end # HTML
48
+ end # Format
49
+ end # Rote
data/lib/rote/page.rb CHANGED
@@ -1,34 +1,68 @@
1
+ #--
1
2
  # Rote page class
2
3
  # (c)2005 Ross Bamford (and contributors)
3
4
  #
4
5
  # See 'rote.rb' or LICENSE for licence information.
5
- # $Id: page.rb,v 1.9 2005/12/05 14:44:32 roscopeco Exp $
6
+ # $Id: page.rb,v 1.10 2005/12/12 02:45:24 roscopeco Exp $
7
+ #++
8
+
6
9
  require 'erb'
7
- require 'rdoc/markup/simple_markup'
8
- require 'rdoc/markup/simple_markup/to_html'
9
10
 
10
- begin
11
- require 'redcloth'
12
- rescue LoadError
13
- # optional dep
14
- nil
15
- end
11
+ # Don't want user to have to require these in their pagecode.
12
+ require 'rote/format/html'
16
13
 
17
14
  module Rote
18
-
15
+ STRIP_SLASHES = /^\/?(.*?)\/?$/
16
+ FILE_EXT = /\..*$/
17
+
19
18
  #####
20
19
  ## A +Page+ object represents an individual template source file, taking
21
20
  ## input from that file and (optionally) some ruby code, and producing
22
- ## rendered (or 'merged') output as a +String+.
23
- ## When a page is created, ruby source will be found alongside the
21
+ ## rendered (or 'merged') output as a +String+. All user-supplied code
22
+ ## (COMMON.rb or page code, for example) is executed in the binding
23
+ ## of an instance of this class.
24
+ ##
25
+ ## When a page is created, ruby source will be sought alongside the
24
26
  ## file, with same basename and an '.rb' extension. If found it will
25
27
  ## run through +instance_eval+. That source can call methods
26
28
  ## and set any instance variables, for use later in the template.
29
+ ## Such variables or methods may also be defined in a COMMON.rb file
30
+ ## in or above the page's directory, in code associated with the
31
+ ## +layout+ applied to a page, or (less often) in a block supplied to
32
+ ## +new+.
27
33
  ##
28
34
  ## Rendering happens only once for a given page object, when the
29
35
  ## +render+ method is first called. Once a page has been rendered
30
36
  ## it is frozen.
31
37
  class Page
38
+
39
+ class << self
40
+ # Helper that returns a page-code filename given a template filename.
41
+ # This does not check that the source exists - use the +ruby_filename+
42
+ # instance method to get the actual filename (if any) of source
43
+ # associated with a given page instance.
44
+ def page_ruby_filename(template_fn)
45
+ fn = nil
46
+ if (template_fn)
47
+ if (fn = template_fn.dup) =~ FILE_EXT
48
+ fn[FILE_EXT] = '.rb'
49
+ else
50
+ fn << '.rb' unless fn.empty?
51
+ end
52
+ end
53
+ fn
54
+ end
55
+
56
+ # Find all COMMON.rb files from given dir up to FS root.
57
+ def resolve_common_rubys(dir, arr = [])
58
+ # defer to parent dir first
59
+ parent = File.expand_path(File.join(dir, '..'))
60
+ resolve_common_rubys(parent,arr) unless parent == dir # at root
61
+ fn = File.join(dir,'COMMON.rb')
62
+ arr << fn if (File.exists?(fn) && File.readable?(fn))
63
+ end
64
+ end
65
+
32
66
  # The text of the template to use for this page.
33
67
  attr_reader :template_text
34
68
 
@@ -36,24 +70,20 @@ module Rote
36
70
  # when (if) the page source calls layout(basename).
37
71
  attr_reader :layout_text
38
72
 
39
- # Formatting options for this page. This is an array of the
40
- # option symbols, as defined by RedCloth, with a further +:rdoc+
41
- # symbol that selects RDoc formatting. The most common are
42
- # :textile. :markdown, and :rdoc, but additional options are
43
- # supported by RedCloth - see it's documentation for full details
44
- # of supported option symbols and their effect.
45
- #
46
- # The default is [], which means 'No formatting'. This setting
47
- # does not affect ERB rendering (which is always performed, before
48
- # any formatting).
49
- attr_reader :format_opts
50
- def format_opts=(opts)
51
- if !opts.nil? && opts.respond_to?(:to_ary)
52
- @format_opts = opts
53
- else
54
- @format_opts = [opts]
55
- end
56
- end
73
+ # The names from which this page's template and layout (if any)
74
+ # were read, relative to the +base_path+.
75
+ attr_reader :template_name, :layout_name
76
+
77
+ # The base paths for this page's template and layout. These point
78
+ # to the directories configured in the Rake tasks.
79
+ attr_reader :base_path, :layout_path
80
+
81
+ # The array of page filters (applied to this page output *before*
82
+ # layout is applied) and post filters (three gueses).
83
+ # You can use +append_page_filter+ and +append_post_filter+ to add
84
+ # new filters, which gives implicit block => Filters::Proc conversion
85
+ # and checks for nil.
86
+ attr_reader :page_filters, :post_filters
57
87
 
58
88
  # Reads the template, and evaluates the global and page scripts, if
59
89
  # available, using the current binding. You may define any instance
@@ -64,49 +94,80 @@ module Rote
64
94
  # If specified, the layout path will be used to find layouts referenced
65
95
  # from templates.
66
96
  #
67
- # If a block is supplied, it is executed _after_ the global / page
68
- # code, so you can locally override variables and so on.
69
- def initialize(template_fn,
70
- layout_path = File.dirname(template_fn),
71
- default_layout_ext = File.extname(template_fn)) # :yield: self if block_given?
97
+ # If a block is supplied, it is executed _before_ the global / page
98
+ # code. This will be the block supplied by the file-extension mapping.
99
+ def initialize(template_name, pages_dir = '.', layout_dir = pages_dir, &blk)
72
100
  @template_text = nil
101
+ @template_name = nil
73
102
  @layout_text = nil
103
+ @layout_name = nil
74
104
  @content_for_layout = nil
75
105
  @result = nil
76
- @format_opts = []
77
- @layout_defext = default_layout_ext
78
- @layout_path = layout_path
79
- @fixme_dir = File.dirname(template_fn)
106
+ @layout_defext = File.extname(template_name)
107
+ @layout_path = layout_dir[STRIP_SLASHES,1]
108
+ @base_path = pages_dir[STRIP_SLASHES,1]
80
109
 
110
+ @page_filters, @post_filters = [], []
111
+
81
112
  # read in the template. Layout _may_ get configured later in page code
82
- read_template(template_fn)
113
+ # We only add the pages_dir if it's not already there, because it's
114
+ # easier to pass the whole relative fn from rake...
115
+ # template_name always needs with no prefix.
116
+ tfn = template_name
117
+ read_template(tfn)
83
118
 
84
- # get script filenames, and eval them if found
85
- src_rb = template_fn.sub(/\..*$/,'') + '.rb'
86
- section_rb = @fixme_dir + '/COMMON.rb'
87
- instance_eval(File.read(section_rb),section_rb) if File.exists?(section_rb)
88
- instance_eval(File.read(src_rb),src_rb) if File.exists?(src_rb)
119
+ blk[self] if blk
120
+
121
+ # Eval COMMON.rb's
122
+ eval_common_rubys
89
123
 
90
- # Allow block to have the final say
91
- yield self if block_given?
124
+ # get script filenames, and eval them if found
125
+ tfn = ruby_filename # nil if no file
126
+ instance_eval(File.read(tfn),tfn) if tfn
92
127
  end
93
128
 
94
- # Sets the layout from the specified file, or disables layout if
95
- # +nil+ is passed in. The specified basename should be the name
96
- # of the layout file relative to the +layout_dir+, with no extension.
97
- #
98
- # The layout is read by this method. An exception is
99
- # thrown if the layout doesn't exist.
100
- #
101
- # This can only be called before the first call to +render+. After
102
- # that the instance is frozen.
103
- def layout(basename)
104
- if basename
105
- fn = layout_fn(basename)
106
- raise "Layout #{fn} not found" unless File.exists?(fn)
107
- @layout_text = File.read(fn)
129
+ # Returns the full filename of this Page's template. This is obtained by
130
+ # joining the base path with template name.
131
+ def template_filename
132
+ template_name ? File.join(base_path,template_name) : nil
133
+ end
134
+
135
+ # Returns the full filename of this Page's template. This is obtained by
136
+ # joining the base path with template name.
137
+ def layout_filename
138
+ layout_name ? File.join(layout_path,layout_name) : nil
139
+ end
140
+
141
+ # Returns the full filename of this Page's ruby source. If no source is
142
+ # found for this page (not including common source) this returns +nil+.
143
+ def ruby_filename
144
+ fn = Page::page_ruby_filename(template_filename)
145
+ File.exists?(fn) ? fn : nil
146
+ end
147
+
148
+ # Append +filter+ to this page's page-filter chain, or create
149
+ # a new Rote::Filters::TextFilter with the supplied block.
150
+ # This method should be preferred over direct manipulation of
151
+ # the +filters+ array if you are simply building a chain.
152
+ def page_filter(filter = nil, &block)
153
+ if filter
154
+ page_filters << filter
155
+ else
156
+ if block
157
+ page_filters << Filters::Proc.new(block)
158
+ end
159
+ end
160
+ end
161
+
162
+ # Append +filter+ to this page's post-filter chain.
163
+ # Behaviour is much the same as +append_page_filter+.
164
+ def post_filter(filter = nil, &block)
165
+ if filter
166
+ post_filters << filter
108
167
  else
109
- @layout_text = nil
168
+ if block
169
+ post_filters << Filters::Proc.new(block)
170
+ end
110
171
  end
111
172
  end
112
173
 
@@ -119,116 +180,108 @@ module Rote
119
180
 
120
181
  alias to_s render
121
182
 
122
- private
183
+ # Sets the layout from the specified file, or disables layout if
184
+ # +nil+ is passed in. The specified basename should be the name
185
+ # of the layout file relative to the +layout_dir+, with no extension.
186
+ #
187
+ # *The* *layout* *is* *not* *read* *by* *this* *method*. It, and
188
+ # it's source, are loaded only at rendering time. This prevents
189
+ # multiple calls by various scoped COMMON code, for example, from
190
+ # making a mess in the Page binding.
191
+ #
192
+ # This can only be called before the first call to +render+. After
193
+ # that the instance is frozen.
194
+ def layout(basename)
195
+ if basename
196
+ # layout text
197
+ @layout_name = "#{basename}#{@layout_defext if File.extname(basename).empty?}"
198
+ else
199
+ @layout_name = nil
200
+ end
201
+ end
202
+
203
+ private
123
204
 
124
205
  # Sets the template from the specified file, or clears the template if
125
206
  # +nil+ is passed in. The specified basename should be the name
126
207
  # of the layout file relative to the +layout_dir+, with no extension.
127
- def read_template(fn)
208
+ def read_template(fn)
128
209
  if fn
129
- raise "Template #{fn} not found" unless File.exists?(fn)
130
- @template_text = File.read(fn)
210
+ # if it's already a path that includes the pages path, strip
211
+ # that to get the name.
212
+ if fn =~ /#{base_path}/
213
+ @template_name = fn[/^#{base_path}\/(.*)/,1]
214
+ else
215
+ @template_name = fn
216
+ end
217
+
218
+ raise "Template #{fn} not found" unless File.exists?(template_filename)
219
+ @template_text = File.read(template_filename)
131
220
  else
221
+ @template_name = nil
132
222
  @template_text = nil
133
223
  end
134
224
  end
135
225
 
136
- def render_fmt(text)
137
- result = text
138
-
139
- # need to get opts to a known state (array), and copy it
140
- # so we can modify it.
141
- if @format_opts && ((@format_opts.respond_to?(:to_ary) && (!@format_opts.empty?)) || @format_opts.is_a?(Symbol))
142
- opts = @format_opts.respond_to?(:to_ary) ? @format_opts.dup : [@format_opts]
143
-
144
- # Remove :rdoc opts from array (RedCloth doesn't do 'em)
145
- # and remember for after first rendering...
146
- #
147
- # Cope with multiple occurences of :rdoc
148
- unless (rdoc_opt = opts.grep(:rdoc)).empty?
149
- opts -= rdoc_opt
150
- end
151
-
152
- # Render out RedCloth / markdown
153
- unless opts.empty?
154
- if defined?(RedCloth)
155
- rc = RedCloth.new(result)
156
- rc.instance_eval { @lite_mode = false } # hack around a warning
157
- result = rc.to_html(*opts)
158
- else
159
- puts "WARN: RedCloth options specified but no RedCloth installed"
160
- end
161
- end
162
-
163
- # Render out Rdoc
164
- #
165
- # TODO could support alternative output formats by having the user supply
166
- # the formatter class (ToHtml etc).
167
- unless rdoc_opt.empty?
168
- p = SM::SimpleMarkup.new
169
- h = SM::ToHtml.new
170
- result = p.convert(result, h)
171
- end
226
+ def load_layout
227
+ if fn = layout_filename
228
+ raise "Layout #{fn} not found" unless File.exists?(fn)
229
+ @layout_text = File.read(fn)
230
+
231
+ # layout code
232
+ cfn = Page::page_ruby_filename(fn)
233
+ instance_eval(File.read(cfn), cfn) if File.exists?(cfn)
172
234
  end
173
-
174
- result
175
235
  end
176
236
 
177
237
 
238
+ def render_page_filters(text)
239
+ page_filters.inject(text) { |s, f| f.filter(s, self) }
240
+ end
241
+
242
+ def render_post_filters(text)
243
+ post_filters.inject(text) { |s, f| f.filter(s, self) }
244
+ end
245
+
178
246
  # render, set up @result for next time. Return result too.
179
247
  def do_render!
180
248
  # Render the page content into the @content_for_layout
181
249
  unless @template_text.nil?
182
- @content_for_layout = render_fmt( ERB.new(@template_text).result(binding) )
250
+ # default render_fmt does nothing - different page formats may redefine it.
251
+ erb = ERB.new(@template_text)
252
+ erb.filename = template_filename
253
+ @content_for_layout = render_page_filters( erb.result(binding) )
183
254
  end
184
255
 
256
+ # Load layout _after_ page eval, allowing page to override layout.
257
+ load_layout
258
+
185
259
  # render into the layout if supplied.
186
260
  @result = if !@layout_text.nil?
187
- ERB.new(@layout_text).result(binding)
261
+ erb = ERB.new(@layout_text)
262
+ erb.filename = layout_filename
263
+ erb.result(binding)
188
264
  else
189
265
  @content_for_layout
190
266
  end
191
267
 
268
+ @result = render_post_filters(@result)
192
269
  freeze
193
270
 
194
271
  @result
195
272
  end
196
273
 
197
- # Get a full layout filename from a basename. If the basename has no extension,
198
- # the default extension is added.
199
- def layout_fn(basename)
200
- ext = File.extname(basename)
201
- "#{@layout_path}/#{basename}#{@layout_defext if ext.empty?}"
202
- end
203
-
204
- # FIXME NASTY HACK: Allow templates to inherit COMMON.rb. This should be replaced
205
- # with a proper search for inherited in Page.new. Call from your COMMON.rb to
206
- # inherit the COMMON.rb immediately above this. If none exists there, this doesn't go
207
- # looking beyond that - it just returns false
208
- def inherit_common
209
- inh = "#{@fixme_dir}/../COMMON.rb"
210
- if File.exists?(inh)
211
- instance_eval(File.read(inh))
212
- true
213
- else
214
- false
215
- end
216
- end
217
-
218
- # FIXME NASTY HACK II: relative links (kinda works, but simply. Handy when
219
- # you do both local preview from some deep directory, and remote deployment
220
- # to a root)
221
- def link_rel(href)
222
- thr = href
223
- if thr.is_a?(String) && href[0,1] == '/' # only interested in absolute
224
- thr = href[1..href.length]
225
- count = @fixme_dir.split('/').length - 2
226
- if count > 0 then count.times {
227
- thr = '../' + thr
228
- } end
229
- end
230
- thr
274
+ def inherit_common # inherit_common is implicit now vv0.2.99 v-0.5
275
+ warn "Warning: inherit_common is deprecated (inheritance is now implicit)"
231
276
  end
232
-
277
+
278
+ # Find and evaluate all COMMON.rb files from page dir up to FS root.
279
+ def eval_common_rubys
280
+ common_rbs = Page::resolve_common_rubys(File.expand_path(File.dirname(template_filename)))
281
+ common_rbs.each { |fn| instance_eval(File.read(fn),fn) }
282
+
283
+ true
284
+ end # method
285
+
233
286
  end #class
234
287
  end #module