rote 0.2.4.1 → 0.3.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.
@@ -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