Pimki 1.0.092

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. data/README +158 -0
  2. data/README-PIMKI +87 -0
  3. data/app/controllers/wiki.rb +563 -0
  4. data/app/models/author.rb +4 -0
  5. data/app/models/chunks/category.rb +31 -0
  6. data/app/models/chunks/category_test.rb +21 -0
  7. data/app/models/chunks/chunk.rb +20 -0
  8. data/app/models/chunks/engines.rb +34 -0
  9. data/app/models/chunks/include.rb +29 -0
  10. data/app/models/chunks/literal.rb +19 -0
  11. data/app/models/chunks/match.rb +19 -0
  12. data/app/models/chunks/nowiki.rb +31 -0
  13. data/app/models/chunks/nowiki_test.rb +14 -0
  14. data/app/models/chunks/test.rb +18 -0
  15. data/app/models/chunks/todo.rb +22 -0
  16. data/app/models/chunks/uri.rb +97 -0
  17. data/app/models/chunks/uri_test.rb +92 -0
  18. data/app/models/chunks/wiki.rb +82 -0
  19. data/app/models/chunks/wiki_test.rb +36 -0
  20. data/app/models/page.rb +91 -0
  21. data/app/models/page_lock.rb +24 -0
  22. data/app/models/page_set.rb +73 -0
  23. data/app/models/page_test.rb +76 -0
  24. data/app/models/revision.rb +91 -0
  25. data/app/models/revision_test.rb +252 -0
  26. data/app/models/web.rb +277 -0
  27. data/app/models/web_test.rb +53 -0
  28. data/app/models/wiki_content.rb +113 -0
  29. data/app/models/wiki_service.rb +137 -0
  30. data/app/models/wiki_service_test.rb +15 -0
  31. data/app/models/wiki_words.rb +26 -0
  32. data/app/models/wiki_words_test.rb +12 -0
  33. data/app/views/bottom.rhtml +4 -0
  34. data/app/views/markdown_help.rhtml +16 -0
  35. data/app/views/menu.rhtml +20 -0
  36. data/app/views/navigation.rhtml +26 -0
  37. data/app/views/rdoc_help.rhtml +16 -0
  38. data/app/views/static_style_sheet.rhtml +231 -0
  39. data/app/views/style.rhtml +179 -0
  40. data/app/views/textile_help.rhtml +28 -0
  41. data/app/views/top.rhtml +52 -0
  42. data/app/views/wiki/authors.rhtml +15 -0
  43. data/app/views/wiki/bliki.rhtml +101 -0
  44. data/app/views/wiki/bliki_edit.rhtml +33 -0
  45. data/app/views/wiki/bliki_new.rhtml +61 -0
  46. data/app/views/wiki/bliki_revision.rhtml +51 -0
  47. data/app/views/wiki/edit.rhtml +34 -0
  48. data/app/views/wiki/edit_menu.rhtml +27 -0
  49. data/app/views/wiki/edit_web.rhtml +139 -0
  50. data/app/views/wiki/export.rhtml +14 -0
  51. data/app/views/wiki/feeds.rhtml +10 -0
  52. data/app/views/wiki/list.rhtml +164 -0
  53. data/app/views/wiki/locked.rhtml +14 -0
  54. data/app/views/wiki/login.rhtml +11 -0
  55. data/app/views/wiki/mind.rhtml +39 -0
  56. data/app/views/wiki/new.rhtml +27 -0
  57. data/app/views/wiki/new_system.rhtml +78 -0
  58. data/app/views/wiki/new_web.rhtml +64 -0
  59. data/app/views/wiki/page.rhtml +84 -0
  60. data/app/views/wiki/print.rhtml +16 -0
  61. data/app/views/wiki/published.rhtml +10 -0
  62. data/app/views/wiki/recently_revised.rhtml +31 -0
  63. data/app/views/wiki/revision.rhtml +87 -0
  64. data/app/views/wiki/rss_feed.rhtml +22 -0
  65. data/app/views/wiki/search.rhtml +26 -0
  66. data/app/views/wiki/tex.rhtml +23 -0
  67. data/app/views/wiki/tex_web.rhtml +35 -0
  68. data/app/views/wiki/todo.rhtml +39 -0
  69. data/app/views/wiki/web_list.rhtml +13 -0
  70. data/app/views/wiki_words_help.rhtml +8 -0
  71. data/libraries/action_controller_servlet.rb +177 -0
  72. data/libraries/bluecloth.rb +1127 -0
  73. data/libraries/diff/diff.rb +475 -0
  74. data/libraries/diff/diff_test.rb +80 -0
  75. data/libraries/erb.rb +490 -0
  76. data/libraries/madeleine/automatic.rb +357 -0
  77. data/libraries/madeleine/clock.rb +94 -0
  78. data/libraries/madeleine_service.rb +69 -0
  79. data/libraries/rdocsupport.rb +156 -0
  80. data/libraries/redcloth_for_tex.rb +869 -0
  81. data/libraries/redcloth_for_tex_test.rb +41 -0
  82. data/libraries/view_helper.rb +33 -0
  83. data/libraries/web_controller_server.rb +95 -0
  84. data/pimki.rb +97 -0
  85. metadata +169 -0
@@ -0,0 +1,16 @@
1
+ <%
2
+ @title = @page.plain_name
3
+ @hide_navigation = true
4
+ @style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
5
+ @inline_style = true
6
+ %><%= sub_template "top" %>
7
+
8
+ <%= @page.display_content_for_export %>
9
+
10
+ <div class="byline">
11
+ <%= @page.revisions? ? "Revised" : "Created" %> on <%= @page.pretty_created_at %>
12
+ by
13
+ <%= @page.author_link({ :mode => :export }) %>
14
+ </div>
15
+
16
+ <%= sub_template "bottom" %>
@@ -0,0 +1,10 @@
1
+ <%
2
+ @title = @page.plain_name
3
+ @hide_navigation = true
4
+ @style_additions = ".newWikiWord { background-color: white; font-style: italic; }"
5
+ @inline_style = true
6
+ %><%= sub_template "top" %>
7
+
8
+ <%= @page.display_published %>
9
+
10
+ <%= sub_template "bottom" %>
@@ -0,0 +1,31 @@
1
+ <% @title = "Recently Revised" %>
2
+ <%= sub_template "top" %>
3
+
4
+ <% unless @categories.empty? %>
5
+ <div id="categories">
6
+ <strong>Categories</strong>:
7
+ [<a href=".">Any</a>]
8
+ <%= @category_links.join(', ') %>
9
+ </div>
10
+ <% end %>
11
+
12
+ <% revision_date = Date.new(2100) %>
13
+ <ul>
14
+ <% for page in @pages_by_revision %>
15
+ <% if page.revised_on < revision_date %>
16
+ </ul><b><%= page.pretty_revised_on %></b><ul>
17
+ <% end %>
18
+
19
+ <li>
20
+ <a href="../show/<%= page.name %>"><%= page.plain_name %></a>
21
+ <div class="byline" style="margin-bottom: 0px">
22
+ by <%= page.author_link %>
23
+ at <%= page.created_at.strftime "%H:%M" %>
24
+ <%= "from #{page.author.ip}" if page.author.respond_to?(:ip) %>
25
+ </div>
26
+ </li>
27
+
28
+ <% revision_date = page.revised_on %>
29
+ <% end %>
30
+
31
+ <%= sub_template "bottom" %>
@@ -0,0 +1,87 @@
1
+ <% @title = "#{@page.plain_name} (Rev ##{@revision.number})" %><%= sub_template "top" %>
2
+
3
+ <div id="revision">
4
+ <%= @revision.display_content %>
5
+ </div>
6
+
7
+ <div id="changes" style="display: none">
8
+ <p style="background: #eee; padding: 3px; border: 1px solid silver">
9
+ <small>
10
+ Showing changes from revision #<%= @revision.number - 1 %> to #<%= @revision.number %>:
11
+ <ins class="diffins">Added</ins> | <del class="diffdel">Removed</del>
12
+ </small>
13
+ </p>
14
+
15
+ <%= @revision.display_diff %>
16
+ </div>
17
+
18
+
19
+ <div class="byline">
20
+ <%= "Revision from #{@revision.pretty_created_at} by" %>
21
+ <%= @page.web.make_link(@revision.author) %>
22
+ </div>
23
+
24
+ <div class="navigation">
25
+
26
+ <% if @revision.next_revision %>
27
+ <% if @revision.next_revision.number < (@page.revisions.length - 1) %>
28
+ <a href="../revision/<%= @page.name %>?rev=<%= @revision.next_revision.number %>" class="navlink">
29
+ <% else %>
30
+ <a href="../show/<%= @page.name %>" class="navlink">
31
+ <% end %>
32
+ Forward in time</a>
33
+ (<%= @revision.page.revisions.length - @revision.next_revision.number %> more)
34
+ <% end %>
35
+
36
+ <% if @revision.next_revision && @revision.previous_revision %>
37
+ |
38
+ <% end %>
39
+
40
+ <% if @revision.previous_revision %>
41
+ <a href="../revision/<%= @page.name %>?rev=<%= @revision.previous_revision.number %>" class="navlink">Back in time</a>
42
+ (<%= @revision.previous_revision.number + 1 %> more)
43
+ <% end %>
44
+
45
+ | <a href="../show/<%= @page.name %>" class="navlink">See current</a>
46
+
47
+ <% if @revision.previous_revision %>
48
+ <span id="show_changes">
49
+ | <a href="#" onClick="toggleChanges(); return false;">See changes</a>
50
+ </span>
51
+ <span id="hide_changes" style="display: none">
52
+ | <a href="#" onClick="toggleChanges(); return false;">Hide changes</a>
53
+ </span>
54
+ <% end %>
55
+
56
+ | <a href="#" class="navlink" onClick="confirmRollback(); return false;">Rollback</a>
57
+
58
+ <% if @page.references.length > 0 %>
59
+ <small>
60
+ | Linked from: <%= @page.references.collect { |ref| "<a href='#{ref.name}'>#{ref.name}</a>" }.join(", ") %>
61
+ </small>
62
+ <% end %>
63
+ </div>
64
+
65
+ <script language="Javascript">
66
+ function toggleChanges() {
67
+ if (document.getElementById("changes").style.display == "none") {
68
+ document.getElementById("changes").style.display = "block";
69
+ document.getElementById("revision").style.display = "none";
70
+ document.getElementById("show_changes").style.display = "none";
71
+ document.getElementById("hide_changes").style.display = "inline";
72
+ } else {
73
+ document.getElementById("changes").style.display = "none";
74
+ document.getElementById("revision").style.display = "block";
75
+ document.getElementById("show_changes").style.display = "inline";
76
+ document.getElementById("hide_changes").style.display = "none";
77
+ }
78
+ }
79
+
80
+ function confirmRollback() {
81
+ if (confirm('Are you sure you want reset the page to this revision?')) {
82
+ location.href = '../rollback/<%= @page.name %>?rev=<%= @revision.number %>';
83
+ }
84
+ }
85
+ </script>
86
+
87
+ <%= sub_template "bottom" %>
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
3
+ <channel>
4
+ <title><%= @web.name %></title>
5
+ <link><%= "#{@web_url}/show/HomePage" %></link>
6
+ <description>An Instiki wiki</description>
7
+ <language>en-us</language>
8
+ <ttl>40</ttl>
9
+ <% for page in @pages_by_revision %>
10
+ <item>
11
+ <title><%= page.plain_name %></title>
12
+ <% unless @hide_description %>
13
+ <description><%= CGI.escapeHTML(page.display_content) %></description>
14
+ <% end %>
15
+ <pubDate><%= page.created_at.strftime "%a, %e %b %Y %H:%M:%S %Z" %></pubDate>
16
+ <guid><%= "#{@web_url}/show/#{page.name}" %></guid>
17
+ <link><%= "#{@web_url}/show/#{page.name}" %></link>
18
+ <dc:creator><%= WikiWords.separate(page.author) %></dc:creator>
19
+ </item>
20
+ <% end %>
21
+ </channel>
22
+ </rss>
@@ -0,0 +1,26 @@
1
+ <% @title = @results.length > 0 ? "#{@results.length} pages contains \"#{@params["query"]}\"" : "No pages contains \"#{@query}\"" %><%= sub_template "top" %>
2
+
3
+ <% if @results.length > 0 %>
4
+ <ul>
5
+ <% for page in @results %>
6
+ <li><a href="../show/<%= page.name %>"><%= page.plain_name %></a><br />
7
+ <%
8
+ idxs = page.content.scan(/.{0,30}#{@params["query"]}.{0,30}/im)
9
+ idxs.each do |i|
10
+ begin %>
11
+ ...<%= i.to_s %>...<br /><%
12
+ rescue Exception => e
13
+ %><%= e.message %><%
14
+ end
15
+ end
16
+ %>
17
+ </li>
18
+ <% end %>
19
+ </ul>
20
+ <% else %>
21
+ <p>Perhaps you should try expanding your query. Remember that Instiki searches for entire phrases, so if you search for "all that jazz" it will not match pages that contain these words in separation&mdash;only as a sentence fragment.</p>
22
+
23
+ <p>If you're a high-tech computer wizard, you might even want try constructing a regular expression. That's actually what Instiki uses, so go right ahead and flex your "[a-z]*Leet?RegExpSkill(s|z)"</p>
24
+ <% end %>
25
+
26
+ <%= sub_template "bottom" %>
@@ -0,0 +1,23 @@
1
+ \documentclass[12pt,titlepage]{article}
2
+
3
+ \usepackage[danish]{babel} %danske tekster
4
+ \usepackage[OT1]{fontenc} %rigtige danske bogstaver...
5
+ \usepackage{a4}
6
+ \usepackage{graphicx}
7
+ \usepackage{ucs}
8
+ \usepackage[utf8]{inputenc}
9
+ \input epsf
10
+
11
+ %-------------------------------------------------------------------
12
+
13
+ \begin{document}
14
+
15
+ \sloppy
16
+
17
+ %-------------------------------------------------------------------
18
+
19
+ \section*{<%= @page.name %>}
20
+
21
+ <%= @tex_content %>
22
+
23
+ \end{document}
@@ -0,0 +1,35 @@
1
+ \documentclass[12pt,titlepage]{article}
2
+
3
+ \usepackage{fancyhdr}
4
+ \pagestyle{fancy}
5
+
6
+ \fancyhead[LE,RO]{}
7
+ \fancyhead[LO,RE]{\nouppercase{\bfseries \leftmark}}
8
+ \fancyfoot[C]{\thepage}
9
+
10
+ \usepackage[danish]{babel} %danske tekster
11
+ \usepackage{a4}
12
+ \usepackage{graphicx}
13
+ \usepackage{ucs}
14
+ \usepackage[utf8]{inputenc}
15
+ \input epsf
16
+
17
+
18
+ %-------------------------------------------------------------------
19
+
20
+ \title{<%= @web_name %>}
21
+
22
+ \begin{document}
23
+
24
+ \maketitle
25
+
26
+ \tableofcontents
27
+ \pagebreak
28
+
29
+ \sloppy
30
+
31
+ %-------------------------------------------------------------------
32
+
33
+ <%= @tex_content %>
34
+
35
+ \end{document}
@@ -0,0 +1,39 @@
1
+ <% @title = "ToDo" %>
2
+ <%= sub_template "top" %>
3
+
4
+ <% num_items = @todo_items.inject(0) { |n, (p,i)| n += i.size } %>
5
+ <h3>There <%= num_items == 1 ? 'is' : 'are' %> <%= num_items.zero? ? "no" : @todo_items.size %> ToDo item<%= num_items != 1 ? 's' : '' %>.
6
+ </h3>
7
+
8
+ <ul>
9
+ <% today = Date.today
10
+ @todo_items.each do |page, items| %>
11
+ <li><%= page.link %>
12
+ <ul>
13
+ <% items.each do |item|
14
+ # default is the muted 'darkred', to prevent to many bright red
15
+ # items on one page: (See also chunks/todo.rb)
16
+ tclass = "todoFuture"
17
+ # next check is the item is dated:
18
+ d = ParseDate.parsedate(item[0]) rescue nil
19
+ if not d.nil? and not d.all? { |x| x.nil? }
20
+ # there's a date in the todo. display it accordingly
21
+ d[0] ||= today.year # make sure there's a 'year' component
22
+ d = Date.new(*d[0..2]) rescue nil
23
+ tclass = 'todo' if (not d.nil?) and ((today <=> d) > -1)
24
+ end %>
25
+ <li class="<%= tclass %>"><%= item %></li>
26
+ <% end %>
27
+ </ul>
28
+ </li>
29
+ <% end %>
30
+ </ul>
31
+
32
+ <% if num_items == 0 %>
33
+ <p>To make ToDo items appear here insert a 'todo' item in any page. Simply write at the start of any line 'todo' followed by a colon (':'), followed by a space and some text. For example:
34
+ <pre>todo: some text</pre></p>
35
+ <p> This text will be rendered in red on the page itself, and will also appear here in a list of todo items from all the pages.</p>
36
+ <p>This module also uses <a href='http://www.ruby-docs.org/stdlib/'>ParseDate</a>, which is a fairly smart module. If you write a date anywhere in the todo item's text, it will be picked up. If that date is today or in the past, the item will be colored bright red!<p>
37
+ <% end %>
38
+
39
+ <%= sub_template "bottom" %>
@@ -0,0 +1,13 @@
1
+ <% @title = "Wiki webs" %><%= sub_template "top" %>
2
+
3
+ <ul>
4
+ <% for web in @webs %>
5
+ <li>
6
+ <a href="/<%= web.address %>/show/HomePage"><%= web.name %></a>
7
+ (<%= web.pages.length %> pages by <%= web.authors.length %> authors)
8
+ <% if web.published then %>(<a href="/<%= web.address %>/published/HomePage">published</a>)<% end %>
9
+ </li>
10
+ <% end %>
11
+ </ul>
12
+
13
+ <%= sub_template "bottom" %>
@@ -0,0 +1,8 @@
1
+ <h3>Wiki words</h3>
2
+ <p style="border-top: 1px dotted #ccc; margin-top: 0px">
3
+ Two or more uppercase words stuck together (camel case) or any phrase surrounded by doubble brackets is a wiki word. A camel-case wiki word can be escaped by putting \ in front of it.
4
+ </p>
5
+ <p>
6
+ Wiki words: <i>HomePage, ThreeWordsTogether, [[C++]], [[Let's play again!]]</i><br/>
7
+ Not wiki words: <i>IBM, School</i>
8
+ </p>
@@ -0,0 +1,177 @@
1
+ require 'erb'
2
+ require 'cgi'
3
+ require 'webrick'
4
+ include WEBrick
5
+
6
+ require 'view_helper'
7
+
8
+ class ActionControllerServlet < HTTPServlet::AbstractServlet
9
+ @@template_root = "./views"
10
+ def self.template_root() @@template_root end
11
+ def self.template_root=(template_root) @@template_root = template_root end
12
+
13
+ include ViewHelper
14
+
15
+ def do_POST(req, res) do_GET(req, res) end
16
+
17
+ def do_GET(req, res)
18
+ @req, @res = req, res
19
+
20
+ @res['Content-Type'] = "text/html; charset=utf-8"
21
+ @res['Pragma'] = "no-cache"
22
+ @res['Cache-Control'] = "no-cache"
23
+
24
+ @params = @req.query
25
+ @assigns = {}
26
+ @performed_render = @performed_redirect = false
27
+
28
+ @logger.info "Performing #{action_name}"
29
+ @logger.info " Parameters: #{@params.inspect}"
30
+ @logger.info " Cookies: #{@req.cookies.collect { |c| "#{c.name} => #{c.value}" }.join(", ") }"
31
+
32
+ perform_action
33
+ @res
34
+ end
35
+
36
+ protected
37
+ def template_root() self.class.template_root end
38
+
39
+ # Issues a HTTP 302 (location) redirect to the specified <tt>path</tt>. Query string
40
+ # parameters can be specified in the <tt>params</tt> hash and are automatically URL escaped.
41
+ # An <tt>anchor</tt> can also be specified (as a string).
42
+ def redirect_path(path, params = nil, anchor = nil)
43
+ path << build_query_string(params) unless params.nil?
44
+ path << "\##{anchor}" unless anchor.nil?
45
+ @res.set_redirect WEBrick::HTTPStatus::MovedPermanently, path
46
+ @performed_redirect = true
47
+ end
48
+
49
+ # Redirects the browser to another action within the current controller, so redirect_path "pages"
50
+ # within a controller called WikiController would take the user to "http://www.example.com/wiki/pages"
51
+ def redirect_action(action, params = nil, anchor = nil)
52
+ redirect_path "/#{controller_name}/#{action}", params, anchor
53
+ end
54
+
55
+
56
+ # Compiles the template response and adds it to the response. If no template_name has been
57
+ # supplied, the action parameter parsed to the controller is used. So invoking
58
+ # render on a object initialized with myController.new("show_person") will compile the template
59
+ # located at "../views/show_person.rhtml". Notice that the ".rhtml" extension is postfixed
60
+ # to the template_name regardless of one was passed or that the action parameter was used.
61
+ def render(template_name = "#{controller_name}/#{action_name}")
62
+ @performed_render = true
63
+ @logger.info "Rendering: #{template_name}"
64
+ add_instance_variables_to_assigns
65
+ @res.body = template_result "#{template_root}/#{template_name}.rhtml"
66
+ end
67
+
68
+ def render_text(text)
69
+ @performed_render = true
70
+ @logger.info "Rendering in text"
71
+ @res.body = text
72
+ end
73
+
74
+ # Wrapper around render that presumes the current controller is used as a base for the action,
75
+ # so render_action("page") in WikiController will be equal to render("wiki/page")
76
+ def render_action(action_name)
77
+ render "#{controller_name}/#{action_name}"
78
+ end
79
+
80
+ def sub_template(template_name)
81
+ template_result "#{template_root}/#{template_name}.rhtml"
82
+ end
83
+
84
+ # Returns the value of the cookie matching +name+ (or nil if none is found).
85
+ def read_cookie(key)
86
+ cookies = @req.cookies.select { |cookie| cookie.name == key }
87
+ cookies.empty? ? nil : cookies.first.value
88
+ end
89
+
90
+ def write_cookie(key, value, permanent = false)
91
+ @logger.info "Writing cookie: #{key} => #{value}"
92
+
93
+ cookie = WEBrick::Cookie.new(key, value)
94
+ cookie.path = "/"
95
+ cookie.expires = Time.local(2030) if permanent
96
+ @res.cookies << cookie
97
+ end
98
+
99
+
100
+ # Returns the last part of the uri path ("page" in "/wiki/page") by default, but
101
+ # can be overwritten to implement mod_rewrite-like behaviour, such as "page" in "/wiki/page/home"
102
+ def action_name
103
+ @req.path.to_s.split(/\//).last
104
+ end
105
+
106
+ # Returns an array with each of the parts in the request as an element. So /something/cool/dude
107
+ # returns ["something", "cool", "dude"]
108
+ def request_path
109
+ request_path_parts = @req.path.to_s.split(/\//)
110
+ request_path_parts.length > 1 ? request_path_parts[1..-1] : []
111
+ end
112
+
113
+ # Can be overwritten by a controller class to implement shared behaviour for all the actions in
114
+ # the class, such as security measures
115
+ def before_action() end
116
+
117
+
118
+ private
119
+ # Wraps around all action calls to ensure the session is properly updated and closed.
120
+ # Figures out whether an action, such as "list", is implemented as show_list or do_list.
121
+ # The show_* type has precedence and automatically calls render after execution.
122
+ def perform_action
123
+ if before_action == false then return end
124
+
125
+ if action_methods.include?(action_name)
126
+ send(action_name)
127
+ render unless @performed_render || @performed_redirect || !@res.body.empty?
128
+ elsif template_exists_for_action
129
+ render
130
+ else
131
+ raise "No action responded to #{action_name}", caller
132
+ end
133
+ end
134
+
135
+ def add_instance_variables_to_assigns
136
+ instance_variables.each { |var|
137
+ next if protected_instance_variables.include?(var)
138
+ @assigns[var[1..-1]] = instance_variable_get(var)
139
+ }
140
+ end
141
+
142
+ def protected_instance_variables
143
+ [ "@assigns", "@performed_redirect", "@performed_render" ]
144
+ end
145
+
146
+ def action_methods
147
+ methods - Object.instance_methods
148
+ end
149
+
150
+ # Returns true if a template exists in the controller directory for the specified <tt>action_name</tt>,
151
+ # which would mean true if called for WikiController#changes and "/views/wiki/changes.rhtml" existed.
152
+ def template_exists_for_action
153
+ File.exist? action_template_path
154
+ end
155
+
156
+ def action_template_path(action = action_name)
157
+ "#{template_root}/#{controller_name}/#{action}.rhtml"
158
+ end
159
+
160
+ def template_result(template_path)
161
+ @assigns.each { |key, value| instance_variable_set "@#{key}", value }
162
+ ERB.new(IO.readlines(template_path).join).result(binding)
163
+ end
164
+
165
+ # Converts the class name from something like "OneModule::TwoModule::NeatController"
166
+ # to "neat".
167
+ def controller_name
168
+ self.class.to_s.split("::").last.sub(/Controller/, "").downcase
169
+ end
170
+
171
+ # Returns a query string with escaped keys and values from the passed hash.
172
+ def build_query_string(hash)
173
+ elements = []
174
+ hash.each { |key, value| elements << "#{CGI.escape(key)}=#{CGI.escape(value.to_s)}" }
175
+ "?" + elements.join("&")
176
+ end
177
+ end