Shazburg-webby 0.9.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.
Files changed (172) hide show
  1. data/History.txt +176 -0
  2. data/Manifest.txt +171 -0
  3. data/README.txt +92 -0
  4. data/Rakefile +54 -0
  5. data/bin/webby +8 -0
  6. data/bin/webby-gen +8 -0
  7. data/examples/blog/Sitefile +7 -0
  8. data/examples/blog/tasks/blog.rake +72 -0
  9. data/examples/blog/templates/atom_feed.erb +40 -0
  10. data/examples/blog/templates/blog/month.erb +22 -0
  11. data/examples/blog/templates/blog/post.erb +16 -0
  12. data/examples/blog/templates/blog/year.erb +22 -0
  13. data/examples/presentation/Sitefile +10 -0
  14. data/examples/presentation/content/css/uv/twilight.css +137 -0
  15. data/examples/presentation/content/presentation/_sample_code.txt +10 -0
  16. data/examples/presentation/content/presentation/index.txt +63 -0
  17. data/examples/presentation/content/presentation/s5/blank.gif +0 -0
  18. data/examples/presentation/content/presentation/s5/bodybg.gif +0 -0
  19. data/examples/presentation/content/presentation/s5/framing.css +23 -0
  20. data/examples/presentation/content/presentation/s5/iepngfix.htc +42 -0
  21. data/examples/presentation/content/presentation/s5/opera.css +7 -0
  22. data/examples/presentation/content/presentation/s5/outline.css +15 -0
  23. data/examples/presentation/content/presentation/s5/pretty.css +86 -0
  24. data/examples/presentation/content/presentation/s5/print.css +1 -0
  25. data/examples/presentation/content/presentation/s5/s5-core.css +9 -0
  26. data/examples/presentation/content/presentation/s5/slides.css +3 -0
  27. data/examples/presentation/content/presentation/s5/slides.js +553 -0
  28. data/examples/presentation/layouts/presentation.txt +43 -0
  29. data/examples/presentation/templates/_code_partial.erb +13 -0
  30. data/examples/presentation/templates/presentation.erb +40 -0
  31. data/examples/tumblog/Sitefile +9 -0
  32. data/examples/tumblog/content/css/tumblog.css +308 -0
  33. data/examples/tumblog/content/images/tumblog/permalink.gif +0 -0
  34. data/examples/tumblog/content/images/tumblog/rss.gif +0 -0
  35. data/examples/tumblog/content/tumblog/200806/the-noble-chicken/index.txt +12 -0
  36. data/examples/tumblog/content/tumblog/200807/historical-perspectives-on-the-classic-chicken-joke/index.txt +12 -0
  37. data/examples/tumblog/content/tumblog/200807/mad-city-chickens/index.txt +10 -0
  38. data/examples/tumblog/content/tumblog/200807/the-wisdom-of-the-dutch/index.txt +11 -0
  39. data/examples/tumblog/content/tumblog/200807/up-a-tree/index.txt +13 -0
  40. data/examples/tumblog/content/tumblog/index.txt +37 -0
  41. data/examples/tumblog/content/tumblog/rss.txt +37 -0
  42. data/examples/tumblog/layouts/tumblog/default.txt +44 -0
  43. data/examples/tumblog/layouts/tumblog/post.txt +15 -0
  44. data/examples/tumblog/lib/tumblog_helper.rb +32 -0
  45. data/examples/tumblog/tasks/tumblog.rake +30 -0
  46. data/examples/tumblog/templates/atom_feed.erb +40 -0
  47. data/examples/tumblog/templates/tumblog/conversation.erb +12 -0
  48. data/examples/tumblog/templates/tumblog/link.erb +10 -0
  49. data/examples/tumblog/templates/tumblog/photo.erb +13 -0
  50. data/examples/tumblog/templates/tumblog/post.erb +12 -0
  51. data/examples/tumblog/templates/tumblog/quote.erb +11 -0
  52. data/examples/webby/Sitefile +19 -0
  53. data/examples/webby/content/css/background.gif +0 -0
  54. data/examples/webby/content/css/blueprint/print.css +76 -0
  55. data/examples/webby/content/css/blueprint/screen.css +696 -0
  56. data/examples/webby/content/css/coderay.css +96 -0
  57. data/examples/webby/content/css/site.css +184 -0
  58. data/examples/webby/content/css/uv/twilight.css +137 -0
  59. data/examples/webby/content/index.txt +37 -0
  60. data/examples/webby/content/manual/index.txt +430 -0
  61. data/examples/webby/content/reference/index.txt +202 -0
  62. data/examples/webby/content/release-notes/rel-0-9-0/index.txt +73 -0
  63. data/examples/webby/content/robots.txt +6 -0
  64. data/examples/webby/content/script/jquery.corner.js +152 -0
  65. data/examples/webby/content/script/jquery.js +31 -0
  66. data/examples/webby/content/sitemap.txt +31 -0
  67. data/examples/webby/content/tips_and_tricks/index.txt +96 -0
  68. data/examples/webby/content/tutorial/index.txt +131 -0
  69. data/examples/webby/layouts/default.txt +55 -0
  70. data/examples/webby/templates/page.erb +10 -0
  71. data/examples/website/Sitefile +7 -0
  72. data/examples/website/content/css/blueprint/License.txt +21 -0
  73. data/examples/website/content/css/blueprint/Readme.txt +100 -0
  74. data/examples/website/content/css/blueprint/compressed/print.css +76 -0
  75. data/examples/website/content/css/blueprint/compressed/screen.css +696 -0
  76. data/examples/website/content/css/blueprint/lib/forms.css +45 -0
  77. data/examples/website/content/css/blueprint/lib/grid.css +193 -0
  78. data/examples/website/content/css/blueprint/lib/grid.png +0 -0
  79. data/examples/website/content/css/blueprint/lib/ie.css +30 -0
  80. data/examples/website/content/css/blueprint/lib/reset.css +39 -0
  81. data/examples/website/content/css/blueprint/lib/typography.css +116 -0
  82. data/examples/website/content/css/blueprint/plugins/buttons/Readme +31 -0
  83. data/examples/website/content/css/blueprint/plugins/buttons/buttons.css +97 -0
  84. data/examples/website/content/css/blueprint/plugins/buttons/icons/cross.png +0 -0
  85. data/examples/website/content/css/blueprint/plugins/buttons/icons/key.png +0 -0
  86. data/examples/website/content/css/blueprint/plugins/buttons/icons/tick.png +0 -0
  87. data/examples/website/content/css/blueprint/plugins/css-classes/Readme +14 -0
  88. data/examples/website/content/css/blueprint/plugins/css-classes/css-classes.css +24 -0
  89. data/examples/website/content/css/blueprint/plugins/fancy-type/Readme +22 -0
  90. data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type-compressed.css +5 -0
  91. data/examples/website/content/css/blueprint/plugins/fancy-type/fancy-type.css +74 -0
  92. data/examples/website/content/css/blueprint/print.css +68 -0
  93. data/examples/website/content/css/blueprint/screen.css +22 -0
  94. data/examples/website/content/css/coderay.css +111 -0
  95. data/examples/website/content/css/site.css +67 -0
  96. data/examples/website/content/index.txt +19 -0
  97. data/examples/website/layouts/default.txt +58 -0
  98. data/examples/website/lib/breadcrumbs.rb +28 -0
  99. data/examples/website/templates/_partial.erb +10 -0
  100. data/examples/website/templates/page.erb +18 -0
  101. data/examples/website/templates/presentation.erb +40 -0
  102. data/lib/webby.rb +227 -0
  103. data/lib/webby/apps.rb +12 -0
  104. data/lib/webby/apps/generator.rb +283 -0
  105. data/lib/webby/apps/main.rb +221 -0
  106. data/lib/webby/auto_builder.rb +83 -0
  107. data/lib/webby/builder.rb +183 -0
  108. data/lib/webby/core_ext/enumerable.rb +11 -0
  109. data/lib/webby/core_ext/hash.rb +28 -0
  110. data/lib/webby/core_ext/kernel.rb +21 -0
  111. data/lib/webby/core_ext/string.rb +163 -0
  112. data/lib/webby/core_ext/time.rb +9 -0
  113. data/lib/webby/filters.rb +91 -0
  114. data/lib/webby/filters/basepath.rb +97 -0
  115. data/lib/webby/filters/erb.rb +9 -0
  116. data/lib/webby/filters/haml.rb +18 -0
  117. data/lib/webby/filters/markdown.rb +16 -0
  118. data/lib/webby/filters/outline.rb +308 -0
  119. data/lib/webby/filters/sass.rb +17 -0
  120. data/lib/webby/filters/slides.rb +56 -0
  121. data/lib/webby/filters/textile.rb +16 -0
  122. data/lib/webby/filters/tidy.rb +76 -0
  123. data/lib/webby/helpers.rb +30 -0
  124. data/lib/webby/helpers/capture_helper.rb +141 -0
  125. data/lib/webby/helpers/coderay_helper.rb +69 -0
  126. data/lib/webby/helpers/graphviz_helper.rb +136 -0
  127. data/lib/webby/helpers/tag_helper.rb +65 -0
  128. data/lib/webby/helpers/tex_img_helper.rb +133 -0
  129. data/lib/webby/helpers/ultraviolet_helper.rb +63 -0
  130. data/lib/webby/helpers/url_helper.rb +235 -0
  131. data/lib/webby/link_validator.rb +152 -0
  132. data/lib/webby/renderer.rb +379 -0
  133. data/lib/webby/resources.rb +96 -0
  134. data/lib/webby/resources/db.rb +251 -0
  135. data/lib/webby/resources/file.rb +221 -0
  136. data/lib/webby/resources/layout.rb +63 -0
  137. data/lib/webby/resources/page.rb +118 -0
  138. data/lib/webby/resources/partial.rb +79 -0
  139. data/lib/webby/resources/resource.rb +160 -0
  140. data/lib/webby/resources/static.rb +52 -0
  141. data/lib/webby/stelan/mktemp.rb +135 -0
  142. data/lib/webby/stelan/paginator.rb +150 -0
  143. data/lib/webby/stelan/spawner.rb +339 -0
  144. data/lib/webby/tasks/build.rake +27 -0
  145. data/lib/webby/tasks/create.rake +22 -0
  146. data/lib/webby/tasks/deploy.rake +22 -0
  147. data/lib/webby/tasks/growl.rake +15 -0
  148. data/lib/webby/tasks/heel.rake +28 -0
  149. data/lib/webby/tasks/validate.rake +19 -0
  150. data/spec/core_ext/hash_spec.rb +47 -0
  151. data/spec/core_ext/string_spec.rb +110 -0
  152. data/spec/core_ext/time_spec.rb +19 -0
  153. data/spec/spec.opts +1 -0
  154. data/spec/spec_helper.rb +14 -0
  155. data/spec/webby/apps/generator_spec.rb +111 -0
  156. data/spec/webby/apps/main_spec.rb +75 -0
  157. data/spec/webby/helpers/capture_helper_spec.rb +56 -0
  158. data/spec/webby/resources/file_spec.rb +104 -0
  159. data/spec/webby/resources_spec.rb +17 -0
  160. data/tasks/ann.rake +81 -0
  161. data/tasks/bones.rake +21 -0
  162. data/tasks/gem.rake +126 -0
  163. data/tasks/git.rake +41 -0
  164. data/tasks/manifest.rake +49 -0
  165. data/tasks/notes.rake +28 -0
  166. data/tasks/post_load.rake +39 -0
  167. data/tasks/rdoc.rake +51 -0
  168. data/tasks/rubyforge.rake +57 -0
  169. data/tasks/setup.rb +268 -0
  170. data/tasks/spec.rake +55 -0
  171. data/tasks/website.rake +38 -0
  172. metadata +287 -0
@@ -0,0 +1,9 @@
1
+
2
+ class Time
3
+
4
+ def to_y
5
+ self.to_yaml.slice(4..-1).strip
6
+ end
7
+ end # class Time
8
+
9
+ # EOF
@@ -0,0 +1,91 @@
1
+ module Webby
2
+ module Filters
3
+
4
+ class << self
5
+
6
+ # Register a handler for a filter
7
+ def register( filter, &block )
8
+ handlers[filter.to_s] = block
9
+ end
10
+
11
+ # Process input through filters
12
+ def process( renderer, page, input )
13
+ # Start a new cursor for this page
14
+ Cursor.new(renderer, page).start_for(input)
15
+ end
16
+
17
+ # Access a filter handler
18
+ def []( name )
19
+ handlers[name]
20
+ end
21
+
22
+ #######
23
+ private
24
+ #######
25
+
26
+ # The registered filter handlers
27
+ def handlers
28
+ @handlers ||= {}
29
+ end
30
+
31
+ # Instances of this class handle processing a set of filters
32
+ # for a given renderer and page.
33
+ # Note: The instance is passed as the second argument to filters
34
+ # that require two parameters and can be used to access
35
+ # information on the renderer, page, or filters being
36
+ # processed.
37
+ class Cursor
38
+
39
+ attr_reader :renderer, :page, :filters
40
+ def initialize(renderer, page)
41
+ @renderer, @page = renderer, page
42
+ @filters = Array(page.filter)
43
+ @log = Logging::Logger[Webby::Renderer]
44
+ @processed = 0
45
+ @prev_cursor = nil
46
+ end
47
+
48
+ def start_for(input)
49
+ @prev_cursor = @renderer.instance_variable_get(:@_cursor)
50
+ @renderer.instance_variable_set(:@_cursor, self)
51
+ filters.inject(input) do |result, filter|
52
+ handler = Filters[filter]
53
+ raise ::Webby::Error, "unknown filter: #{filter.inspect}" if handler.nil?
54
+
55
+ args = [result, self][0, handler.arity]
56
+ handle(filter, handler, *args)
57
+ end
58
+ ensure
59
+ @renderer.instance_variable_set(:@_cursor, @prev_cursor)
60
+ end
61
+
62
+ # The list of filters yet to be processed
63
+ def remaining_filters
64
+ filters[@processed..-1]
65
+ end
66
+
67
+ # The name of the current filter
68
+ def current_filter
69
+ filters[@processed]
70
+ end
71
+
72
+ #######
73
+ private
74
+ #######
75
+
76
+ # Process arguments through a single filter
77
+ def handle(filter, handler, *args)
78
+ result = handler.call(*args)
79
+ @processed += 1
80
+ result
81
+ end
82
+
83
+ end # class Cursor
84
+ end # class << self
85
+
86
+ end # module Filters
87
+ end # module Webby
88
+
89
+ Webby.require_all_libs_relative_to(__FILE__)
90
+
91
+ # EOF
@@ -0,0 +1,97 @@
1
+ require 'hpricot'
2
+
3
+ module Webby
4
+ module Filters
5
+
6
+ # The BasePath filter is used to rewrite URI paths in HTML documents. This
7
+ # is useful when the server location of the website is not located at the
8
+ # root of the webserver (e.g. http://my.site.com/foo/bar).
9
+ #
10
+ # The BasePath filter will adjust the URI paths in a given HTML document by
11
+ # prepending a base path to the URI. This only works for URIs that start
12
+ # with a leading slash "/". Any other character will exclude the URI from
13
+ # being modified.
14
+ #
15
+ # Assume the user specifies a new URI base in the <tt>Webby.site.base</tt>
16
+ # property:
17
+ #
18
+ # Webby.site.base = '/foo/bar'
19
+ #
20
+ # Here is a snippet from some HTML document.
21
+ #
22
+ # <a href="/some/other/page.html">Page</a>
23
+ # <img src="fractal.jpg" alt="a fractal" />
24
+ #
25
+ # When run through the BasePath filter, the resulting snippet would look
26
+ # like this.
27
+ #
28
+ # <a href="/foo/bar/some/other/page.html">Page</a>
29
+ # <img src="fractal.jpg" alt="a fractal" />
30
+ #
31
+ # The +href+ attribute of the anchor tag is modified because it started
32
+ # with a leading slash. The +src+ attribute of the image tag is not
33
+ # modified because it lacks the leading slash.
34
+ #
35
+ class BasePath
36
+
37
+ # call-seq:
38
+ # BasePath.new( html, mode )
39
+ #
40
+ # Creates a new BasePath filter that will operate on the given _html_
41
+ # string. The _mode_ is either 'xml' or 'html' and determines how Hpricot
42
+ # will handle the parsing of the input string.
43
+ #
44
+ def initialize( str, mode )
45
+ @str = str
46
+ @mode = mode.downcase.to_sym
47
+ end
48
+
49
+ # call-seq:
50
+ # filter => html
51
+ #
52
+ # Process the original html document passed to the filter when it was
53
+ # created. The document will be scanned and the basepath for certain
54
+ # elements will be modified.
55
+ #
56
+ # For example, if a document contains the following line:
57
+ #
58
+ # <a href="/link/to/another/page.html">Page</a>
59
+ #
60
+ # and the user has requested for the base path to be some other directory
61
+ # on the webserver -- <tt>/some/other/directory</tt>. The result of the
62
+ # BasePath filter would be:
63
+ #
64
+ # <a href="/some/other/directory/link/to/another/page.html">Page</a>
65
+ #
66
+ def filter
67
+ doc = @mode == :xml ? Hpricot.XML(@str) : Hpricot(@str)
68
+ base_path = ::Webby.site.base
69
+ attr_rgxp = %r/\[@(\w+)\]$/o
70
+ sub_rgxp = %r/\A(?=\/)/o
71
+
72
+ ::Webby.site.xpaths.each do |xpath|
73
+ @attr_name = nil
74
+
75
+ doc.search(xpath).each do |element|
76
+ @attr_name ||= attr_rgxp.match(xpath)[1]
77
+ a = element.get_attribute(@attr_name)
78
+ element.set_attribute(@attr_name, a) if a.sub!(sub_rgxp, base_path)
79
+ end
80
+ end
81
+
82
+ doc.to_html
83
+ end
84
+
85
+ end # class BasePath
86
+
87
+ # Rewrite base URIs in the input HTML text.
88
+ #
89
+ register :basepath do |input, cursor|
90
+ if ::Webby.site.base then BasePath.new(input, cursor.page.extension).filter
91
+ else input end
92
+ end
93
+
94
+ end # module Filters
95
+ end # module Webby
96
+
97
+ # EOF
@@ -0,0 +1,9 @@
1
+ require 'erb'
2
+
3
+ # Render text via ERB using the built in ERB library.
4
+ Webby::Filters.register :erb do |input, cursor|
5
+ b = cursor.renderer.get_binding
6
+ ERB.new(input, nil, '-').result(b)
7
+ end
8
+
9
+ # EOF
@@ -0,0 +1,18 @@
1
+
2
+ # Render text via the Haml library
3
+ if try_require('haml', 'haml')
4
+
5
+ Webby::Filters.register :haml do |input, cursor|
6
+ opts = ::Webby.site.haml_options.merge(cursor.page.haml_options || {})
7
+ b = cursor.renderer.get_binding
8
+ Haml::Engine.new(input, opts).to_html(b)
9
+ end
10
+
11
+ # Otherwise raise an error if the user tries to use haml
12
+ else
13
+ Webby::Filters.register :haml do |input, cursor|
14
+ raise Webby::Error, "'haml' must be installed to use the haml filter"
15
+ end
16
+ end
17
+
18
+ # EOF
@@ -0,0 +1,16 @@
1
+
2
+ # Render text via markdown using the RDiscount library.
3
+ if try_require('rdiscount', 'rdiscount')
4
+
5
+ Webby::Filters.register :markdown do |input|
6
+ RDiscount.new(input).to_html
7
+ end
8
+
9
+ # Otherwise raise an error if the user tries to use markdown
10
+ else
11
+ Webby::Filters.register :markdown do |input|
12
+ raise Webby::Error, "'rdiscount' must be installed to use the markdown filter"
13
+ end
14
+ end
15
+
16
+ # EOF
@@ -0,0 +1,308 @@
1
+ require 'hpricot'
2
+
3
+ module Webby
4
+ module Filters
5
+
6
+ # The Outline filter is used to insert outline numbering into HTML heading
7
+ # tags (h1, h2, h3, etc.) and to generate a table of contents based on the
8
+ # heading tags. The table of contents is inserted into the page at the
9
+ # location of the <toc /> tag. If there is no <toc /> tag, then a table of
10
+ # contents will not be created but outline numbering will still take place.
11
+ #
12
+ # If a table of contents is desired without outline number being inserted
13
+ # into the heading tags, this can be specified in the attibutes of the
14
+ # <toc /> tag itself.
15
+ #
16
+ # <toc numbering="off" />
17
+ #
18
+ # This will generate a table of contents, but not insert outline numbering
19
+ # into the heading tags.
20
+ #
21
+ # The Outline filter will only work on valid HTML or XHTML pages. Therefore
22
+ # it should be used after any markup langauge filters (textile, markdown,
23
+ # etc.).
24
+ #
25
+ # The following attributes can be specified in the <toc /> tag itself to
26
+ # control how outline numbering is performed by the filter. The attributes
27
+ # can be used in combination with one another.
28
+ #
29
+ # === numbering
30
+ #
31
+ # If set to "off", this will prevent numbers from being inserted into the
32
+ # page. The default is "on".
33
+ #
34
+ # <toc numbering="off" />
35
+ #
36
+ # === numbering_start
37
+ #
38
+ # This is the number to start with when inserting outline numbers into a
39
+ # page. The default is 1.
40
+ #
41
+ # <toc numbering_start="3" />
42
+ #
43
+ # === toc_style
44
+ #
45
+ # The style of the Table of Contents list to generated. This will be
46
+ # either "ol" for an ordered list or "ul" for an unordered list. The
47
+ # default is an ordered list.
48
+ #
49
+ # <toc toc_style="ul" />
50
+ #
51
+ # === toc_range
52
+ #
53
+ # This limits the numbering to only a subset of the HTML heading tags. The
54
+ # defaul is to number all the heading tags.
55
+ #
56
+ # <toc toc_range="h1-h3" />
57
+ #
58
+ # In this example, only the heading tags h1, h2, and h3 will be numbered
59
+ # and included in the table of contents listing.
60
+ #
61
+ # ==== Example
62
+ #
63
+ # Generate a table of contents using an unordered list, starting with the
64
+ # number 2, and only numbering heading levels 2, 3, and 4.
65
+ #
66
+ # <toc numbering_start="2" toc_style="ul" toc_range="h2-h4" />
67
+ #
68
+ class Outline
69
+ include ERB::Util
70
+
71
+ # call-seq:
72
+ # Outline.new( html )
73
+ #
74
+ # Creates a new outline filter that will operate on the given
75
+ # _html_ string.
76
+ #
77
+ def initialize( str )
78
+ @str = str
79
+
80
+ @cur_level, @base_level, @cur_depth = nil
81
+ @level = [0] * 6
82
+ @h_rgxp = %r/^h(\d)$/o
83
+
84
+ @numbering = true
85
+ @numbering_start = 1
86
+
87
+ @toc = []
88
+ @toc_style = 'ol'
89
+ @toc_range = 'h1-h6'
90
+ @list_opening = nil
91
+ end
92
+
93
+ # call-seq:
94
+ # filter => html
95
+ #
96
+ # Process the original html document passed to the filter when it was
97
+ # created. The document will be scanned for heading tags (h1, h2, etc.)
98
+ # and outline numbering and id attributes will be inserted. A table of
99
+ # contents will also be created and inserted into the page if a <toc />
100
+ # tag is found.
101
+ #
102
+ # For example, if there is a heading tag
103
+ #
104
+ # <h3>Get Fuzzy</h3>
105
+ #
106
+ # somewhere in a page about comic strips, the tag might be altered as such
107
+ #
108
+ # <h3 id="h2_2_1"><span class="heading-num">2.2.1</span>Get Fuzzy</h3>
109
+ #
110
+ # The id attribute is used to generate a linke from the table of contents
111
+ # to this particular heading tag. The original text of the tag is used in
112
+ # the table of contents -- "Get Fuzzy" in this example.
113
+ #
114
+ def filter
115
+ doc = Hpricot.XML(@str)
116
+
117
+ # extract directives from the "toc" tag
118
+ toc_elem = doc.search('toc').first
119
+
120
+ unless toc_elem.nil?
121
+ @numbering = toc_elem['numbering'] !~ %r/off/i
122
+ @numbering_start = Integer(toc_elem['numbering_start']) if toc_elem.has_attribute? 'numbering_start'
123
+ @toc_style = toc_elem['toc_style'] if toc_elem.has_attribute? 'toc_style'
124
+ @toc_range = toc_elem['toc_range'] if toc_elem.has_attribute? 'toc_range'
125
+ end
126
+
127
+ unless %w[ul ol].include? @toc_style
128
+ raise ArgumentError, "unknown ToC list type '#{@toc_style}'"
129
+ end
130
+
131
+ m = %r/h(\d)\s*-\s*h(\d)/i.match @toc_range
132
+ @toc_range = Integer(m[1])..Integer(m[2])
133
+ @list_opening = build_list_opening(toc_elem)
134
+
135
+ doc.traverse_element(*%w[h1 h2 h3 h4 h5 h6]) do |elem|
136
+ text, id = heading_info(elem)
137
+ add_to_toc(text, id) if @toc_range.include? current_level
138
+ end
139
+
140
+ toc_elem.swap(toc) unless toc_elem.nil?
141
+ doc.to_html
142
+ end
143
+
144
+
145
+ private
146
+
147
+ def build_list_opening( elem )
148
+ lo = "<#{@toc_style}"
149
+ unless elem.nil?
150
+ %w[class style id].each do |atr|
151
+ next unless elem.has_attribute? atr
152
+ lo << " %s=\"%s\"" % [atr, elem[atr]]
153
+ end
154
+ end
155
+ if @toc_style == 'ol' and @numbering_start != 1
156
+ lo << " start=\"#{@numbering_start}\""
157
+ end
158
+ lo << ">"
159
+ end
160
+
161
+ # Returns information for the given heading element. The information is
162
+ # returned as a two element array: [text, id].
163
+ #
164
+ # This method will also insert outline numbering and an id attribute. The
165
+ # outline numbering can be disabled, but the id attribute must be present
166
+ # for TOC generation.
167
+ #
168
+ def heading_info( elem )
169
+ m = @h_rgxp.match(elem.name)
170
+ level = Integer(m[1])
171
+
172
+ self.current_level = level
173
+ text = elem.inner_text
174
+
175
+ lbl = label
176
+ if numbering?
177
+ elem.children.first.before {tag!(:span, lbl, :class => 'heading-num')}
178
+ end
179
+ elem['id'] = "h#{lbl.tr('.','_')}" if elem['id'].nil?
180
+
181
+ return [text, elem['id']]
182
+ end
183
+
184
+ # Set the current heading level. This will set the label and depth as
185
+ # well. An error will be raised if the _level_ is less than the base
186
+ # heading level.
187
+ #
188
+ # The base heading level will be set to the _level_ if it has not already
189
+ # been set. Therefore, the first heading tag encountered defines the base
190
+ # heading level.
191
+ #
192
+ def current_level=( level )
193
+ if @base_level.nil?
194
+ @base_level = @cur_level = level
195
+ @level[@base_level-1] = @numbering_start-1
196
+ end
197
+
198
+ if level < @base_level
199
+ raise ::Webby::Error, "heading tags are not in order, cannot outline"
200
+ end
201
+
202
+ if level == @cur_level
203
+ @level[level-1] += 1
204
+ elsif level > @cur_level
205
+ @cur_level.upto(level-1) {|ii| @level[ii] += 1}
206
+ else
207
+ @cur_level.downto(level+1) {|ii| @level[ii-1] = 0}
208
+ @level[level-1] += 1
209
+ end
210
+
211
+ @cur_level = level
212
+ end
213
+
214
+ # Returns the current heading level number.
215
+ #
216
+ def current_level
217
+ @cur_level
218
+ end
219
+
220
+ # Return the label string for the current heading level.
221
+ #
222
+ def label
223
+ rv = @level.dup
224
+ rv.delete(0)
225
+ rv.join('.')
226
+ end
227
+
228
+ # Return the nesting depth of the current heading level with respect to the
229
+ # base heading level. This is a one-based number.
230
+ #
231
+ def depth
232
+ @cur_level - @base_level + 1
233
+ end
234
+
235
+ # Add the given text and id reference to the table of contents.
236
+ #
237
+ def add_to_toc( text, id )
238
+ a = "<a href=\"##{id}\">#{h(text)}</a>"
239
+ @toc << [depth, a]
240
+ end
241
+
242
+ # Returns the table of contents as a collection of nested ordered lists.
243
+ # This is fully formatted HTML.
244
+ #
245
+ def toc
246
+ ary = []
247
+
248
+ lopen = "<#@toc_style>"
249
+ lclose = "</#@toc_style>"
250
+ prev_depth = open = 0
251
+
252
+ @toc.each do |a|
253
+ cur = a.first
254
+
255
+ # close out the previous list item if we're at the same level
256
+ if cur == prev_depth
257
+ ary << "</li>"
258
+
259
+ # if we are increasing the level, then start a new list
260
+ elsif cur > prev_depth
261
+ ary << if ary.empty? then @list_opening else lopen end
262
+ open += 1
263
+
264
+ # we are decreasing the level; close out tags but ensure we don't
265
+ # close out all the tags (leave one open)
266
+ else
267
+ (prev_depth - cur).times {
268
+ ary << "</li>" << lclose
269
+ open -= 1
270
+ break if open <= 0
271
+ }
272
+ if open > 0
273
+ ary << "</li>"
274
+ else
275
+ ary << lopen
276
+ open += 1
277
+ end
278
+ end
279
+
280
+ # add the current element
281
+ ary << "<li>" << a.last
282
+ prev_depth = cur
283
+ end
284
+
285
+ # close out the remaingling tags
286
+ ary << "</li>" << lclose
287
+ ary.join("\n")
288
+ end
289
+
290
+ # Returns +true+ if outline numbering should be inserted into the heading
291
+ # tags. Returns +false+ otherwise.
292
+ #
293
+ def numbering?
294
+ @numbering
295
+ end
296
+ end # class Outline
297
+
298
+ # Generate a outline numbering and/or a table of contents in the input HTML
299
+ # text.
300
+ #
301
+ register :outline do |input|
302
+ Outline.new(input).filter
303
+ end
304
+
305
+ end # module Filters
306
+ end # module Webby
307
+
308
+ # EOF