instiki 0.9.2 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/CHANGELOG +165 -0
  2. data/README +68 -172
  3. data/app/controllers/admin_controller.rb +94 -0
  4. data/app/controllers/application.rb +131 -0
  5. data/app/controllers/file_controller.rb +129 -0
  6. data/app/controllers/wiki_controller.rb +354 -0
  7. data/{libraries/view_helper.rb → app/helpers/application_helper.rb} +68 -33
  8. data/app/models/author.rb +3 -3
  9. data/app/models/chunks/category.rb +33 -31
  10. data/app/models/chunks/chunk.rb +86 -20
  11. data/app/models/chunks/engines.rb +54 -38
  12. data/app/models/chunks/include.rb +41 -29
  13. data/app/models/chunks/literal.rb +31 -19
  14. data/app/models/chunks/nowiki.rb +28 -31
  15. data/app/models/chunks/test.rb +18 -18
  16. data/app/models/chunks/uri.rb +182 -97
  17. data/app/models/chunks/wiki.rb +141 -82
  18. data/app/models/file_yard.rb +58 -0
  19. data/app/models/page.rb +112 -86
  20. data/app/models/page_lock.rb +22 -23
  21. data/app/models/page_set.rb +89 -64
  22. data/app/models/revision.rb +123 -90
  23. data/app/models/web.rb +176 -89
  24. data/app/models/wiki_content.rb +207 -105
  25. data/app/models/wiki_service.rb +233 -83
  26. data/app/models/wiki_words.rb +23 -25
  27. data/app/views/{wiki/new_system.rhtml → admin/create_system.rhtml} +83 -78
  28. data/app/views/{wiki/new_web.rhtml → admin/create_web.rhtml} +69 -64
  29. data/app/views/admin/edit_web.rhtml +136 -0
  30. data/app/views/file/file.rhtml +19 -0
  31. data/app/views/file/import.rhtml +23 -0
  32. data/app/views/layouts/default.rhtml +85 -0
  33. data/app/views/markdown_help.rhtml +12 -16
  34. data/app/views/mixed_help.rhtml +7 -0
  35. data/app/views/navigation.rhtml +30 -19
  36. data/app/views/rdoc_help.rhtml +12 -16
  37. data/app/views/textile_help.rhtml +24 -28
  38. data/app/views/wiki/authors.rhtml +11 -13
  39. data/app/views/wiki/edit.rhtml +39 -31
  40. data/app/views/wiki/export.rhtml +12 -14
  41. data/app/views/wiki/feeds.rhtml +14 -10
  42. data/app/views/wiki/list.rhtml +64 -57
  43. data/app/views/wiki/locked.rhtml +23 -14
  44. data/app/views/wiki/login.rhtml +14 -11
  45. data/app/views/wiki/new.rhtml +31 -27
  46. data/app/views/wiki/page.rhtml +115 -81
  47. data/app/views/wiki/print.rhtml +14 -16
  48. data/app/views/wiki/published.rhtml +9 -10
  49. data/app/views/wiki/recently_revised.rhtml +27 -30
  50. data/app/views/wiki/revision.rhtml +103 -81
  51. data/app/views/wiki/rollback.rhtml +14 -9
  52. data/app/views/wiki/rss_feed.rhtml +22 -22
  53. data/app/views/wiki/search.rhtml +38 -15
  54. data/app/views/wiki/tex.rhtml +22 -22
  55. data/app/views/wiki/tex_web.rhtml +34 -34
  56. data/app/views/wiki/web_list.rhtml +18 -13
  57. data/app/views/wiki_words_help.rhtml +9 -8
  58. data/config/environment.rb +82 -0
  59. data/config/environments/development.rb +5 -0
  60. data/config/environments/production.rb +4 -0
  61. data/config/environments/test.rb +17 -0
  62. data/config/routes.rb +18 -0
  63. data/instiki +6 -67
  64. data/instiki.rb +3 -0
  65. data/lib/active_record_stub.rb +31 -0
  66. data/{libraries/diff → lib}/diff.rb +444 -475
  67. data/lib/instiki_errors.rb +15 -0
  68. data/{libraries → lib}/rdocsupport.rb +151 -155
  69. data/lib/redcloth_for_tex.rb +736 -0
  70. data/natives/osx/desktop_launcher/AppDelegate.h +18 -0
  71. data/natives/osx/desktop_launcher/AppDelegate.mm +109 -0
  72. data/natives/osx/desktop_launcher/Credits.html +16 -0
  73. data/natives/osx/desktop_launcher/English.lproj/InfoPlist.strings +0 -0
  74. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/classes.nib +13 -0
  75. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/info.nib +24 -0
  76. data/natives/osx/desktop_launcher/English.lproj/MainMenu.nib/objects.nib +0 -0
  77. data/natives/osx/desktop_launcher/Info.plist +13 -0
  78. data/natives/osx/desktop_launcher/Instiki.xcode/project.pbxproj +592 -0
  79. data/natives/osx/desktop_launcher/Instiki_Prefix.pch +7 -0
  80. data/natives/osx/desktop_launcher/MakeDMG.sh +9 -0
  81. data/natives/osx/desktop_launcher/main.mm +14 -0
  82. data/natives/osx/desktop_launcher/version.plist +16 -0
  83. data/public/404.html +6 -0
  84. data/public/500.html +6 -0
  85. data/public/dispatch.rb +10 -0
  86. data/public/favicon.ico +0 -0
  87. data/public/javascripts/edit_web.js +52 -0
  88. data/public/javascripts/prototype.js +336 -0
  89. data/{app/views/static_style_sheet.rhtml → public/stylesheets/instiki.css} +221 -198
  90. data/script/breakpointer +4 -0
  91. data/script/server +93 -0
  92. metadata +59 -32
  93. data/app/controllers/wiki.rb +0 -389
  94. data/app/models/chunks/match.rb +0 -19
  95. data/app/views/bottom.rhtml +0 -4
  96. data/app/views/top.rhtml +0 -49
  97. data/app/views/wiki/edit_web.rhtml +0 -138
  98. data/libraries/action_controller_servlet.rb +0 -177
  99. data/libraries/erb.rb +0 -490
  100. data/libraries/madeleine_service.rb +0 -68
  101. data/libraries/redcloth_for_tex.rb +0 -869
  102. data/libraries/web_controller_server.rb +0 -81
@@ -1,19 +0,0 @@
1
- # This module is to be included in unit tests that involve matching chunks.
2
- # It provides a easy way to test whether a chunk matches a particular string
3
- # and any the values of any fields that should be set after a match.
4
- module ChunkMatch
5
-
6
- # Asserts a number of tests for the given type and text.
7
- def match(type, test_text, expected)
8
- pattern = type.pattern
9
- assert_match(pattern, test_text)
10
- pattern =~ test_text # Previous assertion guarantees match
11
- chunk = type.new($~)
12
-
13
- # Test if requested parts are correct.
14
- for method_sym, value in expected do
15
- assert_respond_to(chunk, method_sym)
16
- assert_equal(value, chunk.method(method_sym).call, "Checking value of '#{method_sym}'")
17
- end
18
- end
19
- end
@@ -1,4 +0,0 @@
1
- </div>
2
- </div>
3
- </body>
4
- </html>
@@ -1,49 +0,0 @@
1
- <?xml version="1.0" encoding="UTF-8"?>
2
- <html xmlns="http://www.w3.org/1999/xhtml">
3
- <head>
4
- <title>
5
- <% if @web && @page && @page.name == "HomePage" && %( show published print ).include?(@action_name) %>
6
- <%= @web.name %>
7
- <% elsif @web %>
8
- <%= @title %> in <%= @web.name %>
9
- <% else %>
10
- <%= @title %>
11
- <% end %>
12
- </title>
13
-
14
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
15
-
16
- <style type="text/css">
17
- h1#pageName, .newWikiWord a, a.existingWikiWord, .newWikiWord a:hover, #TextileHelp h3 {
18
- color: #<%= @web ? @web.color : "393" %>;
19
- }
20
-
21
- #Container, #Content {
22
- width: <%= @content_width || "600" %>px;
23
- }
24
- <%= sub_template("static_style_sheet") if @inline_style %>
25
- </style>
26
-
27
- <link rel="Stylesheet" href="../static_style_sheet/" type="text/css" media="screen" />
28
-
29
- <style type="text/css">
30
- <%= @style_additions %>
31
- <%= @web ? web.additional_style : "" %>
32
- </style>
33
- </head>
34
- <body>
35
- <div id="Container">
36
- <div id="Content">
37
-
38
- <h1 id="pageName">
39
- <% if @web && @page && @page.name == "HomePage" && %( show published print ).include?(@action_name) %>
40
- <%= @web.name %>
41
- <% elsif @web %>
42
- <small><%= @web.name %></small><br />
43
- <%= @title %>
44
- <% else %>
45
- <%= @title %>
46
- <% end %>
47
- </h1>
48
-
49
- <%= sub_template "navigation" unless @web.nil? || @hide_navigation %>
@@ -1,138 +0,0 @@
1
- <% @title = "Edit Web" %><%= sub_template "top" %>
2
-
3
- <form action="../update_web" id="setup" method="post" onSubmit="cleanAddress(); return validateSetup()">
4
- <h2 style="margin-bottom: 3px">Name and address</h2>
5
- <div class="help">
6
- The name of the web is included in the title on all pages. The address is the base path that all pages within the web live beneath.
7
- Ex: the address "rails" gives URLs like <i>/rails/show/HomePage</i>.
8
- </div>
9
-
10
- <div class="inputBox">
11
- Name: <input type="text" id="name" name="name" value="<%= @web.name %>" onChange="proposeAddress();"> &nbsp;&nbsp;
12
- Address: <input type="text" id="address" name="address" value="<%= @web.address %>" onChange="cleanAddress();">
13
- <i>(Letters & digits only)</i>
14
- </div>
15
-
16
- <h2 style="margin-bottom: 3px">Specialize</h2>
17
- <div class="help">
18
- Turning safe mode on will strip HTML tags and stylesheet options from the content of all pages.
19
- Turning on "brackets only" will require all wiki words to be as [[wiki word]] and WikiWord won't work.
20
- Additions to the stylesheet take precedence over the existing styles. <i>Hint:</i> View source on a page you want to
21
- style to find ID names on individual tags. <a href="#" onClick="document.getElementById('additionalStyle').style.display='block';return false;">See styles >></a>
22
- </div>
23
- <div class="inputBox">
24
- Markup:
25
- <select name="markup">
26
- <%= html_options({ "Textile" => :textile, "Markdown" => :markdown, "RDoc" => :rdoc }, @web.markup) %>
27
- </select>
28
-
29
- &nbsp;&nbsp;
30
-
31
- Color:
32
- <select name="color">
33
- <%= html_options({ "Green" => "008B26", "Purple" => "504685", "Red" => "DA0006", "Orange" => "FA6F00", "Grey" => "8BA2B0" },
34
- @web.color) %>
35
- </select>
36
-
37
- &nbsp;&nbsp;
38
-
39
- <small>
40
-
41
- <input type="checkbox" name="safe_mode"<%= " CHECKED" if @web.safe_mode %>> Safe mode
42
-
43
- &nbsp;&nbsp;
44
-
45
- <input type="checkbox" name="brackets_only"<%= " CHECKED" if @web.brackets_only %>> Brackets only
46
-
47
- &nbsp;&nbsp;
48
-
49
- <input type="checkbox" name="count_pages"<%= " CHECKED" if @web.count_pages %>> Count pages
50
-
51
- </small>
52
-
53
- <textarea id="additionalStyle" style="display: none; margin-top: 10px; margin-bottom: 5px; width: 560px; height: 200px" name="additional_style"><%= @web.additional_style %></textarea>
54
- </div>
55
-
56
- <h2 style="margin-bottom: 3px">Password protection for this web (<%= @web.name %>)</h2>
57
- <div class="help">
58
- This is the password that visitors need to view and edit this web. Setting the password to nothing will remove the password protection.
59
- </div>
60
- <div class="inputBox">
61
- Password: <input type="password" id="password" name="password" value="<%= @web.password %>"> &nbsp;&nbsp;
62
- Verify: <input type="password" id="password_check" value="<%= @web.password %>" name="password_check">
63
- </div>
64
-
65
- <h2 style="margin-bottom: 3px">Publish read-only version of this web (<%= @web.name %>)</h2>
66
- <div class="help">
67
- You can turn on a read-only version of this web that's accessible even when the regular web is password protected.
68
- The published version is accessible through URLs like /wiki/published/HomePage.
69
- </div>
70
- <div class="inputBox">
71
- <input type="checkbox" name="published"<%= " CHECKED" if @web.published %>> Publish this web
72
- </div>
73
-
74
- <p align="right">
75
- <small>
76
- Enter system password
77
- <input type="password" id="system_password" name="system_password">
78
- and
79
- <input type="submit" value="Update Web">
80
- <br/><br/>
81
- ...or forget changes and <a href="/new_web/">create a new web</a>
82
- </small>
83
- </p>
84
-
85
- </form>
86
-
87
- <br/>
88
- <h1>Other administrative tasks</h1>
89
-
90
- <form action="../remove_orphaned_pages" id="remove_orphaned_pages" method="post">
91
-
92
- <p align="right">
93
- <small>
94
- Clean up by entering system password
95
- <input type="password" id="system_password" name="system_password">
96
- and
97
- <input type="submit" value="Delete Orphan Pages">
98
- </small>
99
- </p>
100
-
101
- <script>
102
- function proposeAddress() {
103
- document.getElementById('address').value =
104
- document.getElementById('name').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
105
- }
106
-
107
- function cleanAddress() {
108
- document.getElementById('address').value =
109
- document.getElementById('address').value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
110
- }
111
-
112
- function validateSetup() {
113
- if (document.getElementById('system_password').value == "") {
114
- alert("You must enter the system password");
115
- return false;
116
- }
117
-
118
- if (document.getElementById('name').value == "") {
119
- alert("You must pick a name for the web");
120
- return false;
121
- }
122
-
123
- if (document.getElementById('address').value == "") {
124
- alert("You must pick an address for the web");
125
- return false;
126
- }
127
-
128
- if (document.getElementById('password').value != "" &&
129
- document.getElementById('password').value != document.getElementById('password_check').value) {
130
- alert("The password and its verification doesn't match");
131
- return false;
132
- }
133
-
134
- return true;
135
- }
136
- </script>
137
-
138
- <%= sub_template "bottom" %>
@@ -1,177 +0,0 @@
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
@@ -1,490 +0,0 @@
1
- # Tiny eRuby --- ERB2
2
- # Copyright (c) 1999-2000,2002,2003 Masatoshi SEKI
3
- # You can redistribute it and/or modify it under the same terms as Ruby.
4
-
5
- class ERB
6
- Revision = '$Date: 2004/02/23 23:40:09 $' #'
7
-
8
- def self.version
9
- "erb.rb [2.0.4 #{ERB::Revision.split[1]}]"
10
- end
11
- end
12
-
13
- # ERB::Compiler
14
- class ERB
15
- class Compiler
16
- class PercentLine
17
- def initialize(str)
18
- @value = str
19
- end
20
- attr_reader :value
21
- alias :to_s :value
22
- end
23
-
24
- class Scanner
25
- SplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/
26
-
27
- @scanner_map = {}
28
- def self.regist_scanner(klass, trim_mode, percent)
29
- @scanner_map[[trim_mode, percent]] = klass
30
- end
31
-
32
- def self.default_scanner=(klass)
33
- @default_scanner = klass
34
- end
35
-
36
- def self.make_scanner(src, trim_mode, percent)
37
- klass = @scanner_map.fetch([trim_mode, percent], @default_scanner)
38
- klass.new(src, trim_mode, percent)
39
- end
40
-
41
- def initialize(src, trim_mode, percent)
42
- @src = src
43
- @stag = nil
44
- end
45
- attr_accessor :stag
46
-
47
- def scan; end
48
- end
49
-
50
- class TrimScanner < Scanner
51
- TrimSplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>\n)|(%>)|(\n)/
52
-
53
- def initialize(src, trim_mode, percent)
54
- super
55
- @trim_mode = trim_mode
56
- @percent = percent
57
- if @trim_mode == '>'
58
- @scan_line = self.method(:trim_line1)
59
- elsif @trim_mode == '<>'
60
- @scan_line = self.method(:trim_line2)
61
- elsif @trim_mode == '-'
62
- @scan_line = self.method(:explicit_trim_line)
63
- else
64
- @scan_line = self.method(:scan_line)
65
- end
66
- end
67
- attr_accessor :stag
68
-
69
- def scan(&block)
70
- @stag = nil
71
- if @percent
72
- @src.each do |line|
73
- percent_line(line, &block)
74
- end
75
- else
76
- @src.each do |line|
77
- @scan_line.call(line, &block)
78
- end
79
- end
80
- nil
81
- end
82
-
83
- def percent_line(line, &block)
84
- if @stag || line[0] != ?%
85
- return @scan_line.call(line, &block)
86
- end
87
-
88
- line[0] = ''
89
- if line[0] == ?%
90
- @scan_line.call(line, &block)
91
- else
92
- yield(PercentLine.new(line.chomp))
93
- end
94
- end
95
-
96
- def scan_line(line)
97
- line.split(SplitRegexp).each do |token|
98
- next if token.empty?
99
- yield(token)
100
- end
101
- end
102
-
103
- def trim_line1(line)
104
- line.split(TrimSplitRegexp).each do |token|
105
- next if token.empty?
106
- if token == "%>\n"
107
- yield('%>')
108
- yield(:cr)
109
- break
110
- end
111
- yield(token)
112
- end
113
- end
114
-
115
- def trim_line2(line)
116
- head = nil
117
- line.split(TrimSplitRegexp).each do |token|
118
- next if token.empty?
119
- head = token unless head
120
- if token == "%>\n"
121
- yield('%>')
122
- if is_erb_stag?(head)
123
- yield(:cr)
124
- else
125
- yield("\n")
126
- end
127
- break
128
- end
129
- yield(token)
130
- end
131
- end
132
-
133
- ExplicitTrimRegexp = /(^[ \t]*<%-)|(-%>\n?\z)|(<%-)|(-%>)|(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/
134
- def explicit_trim_line(line)
135
- line.split(ExplicitTrimRegexp).each do |token|
136
- next if token.empty?
137
- if @stag.nil? && /[ \t]*<%-/ =~ token
138
- yield('<%')
139
- elsif @stag && /-%>\n/ =~ token
140
- yield('%>')
141
- yield(:cr)
142
- elsif @stag && token == '-%>'
143
- yield('%>')
144
- else
145
- yield(token)
146
- end
147
- end
148
- end
149
-
150
- ERB_STAG = %w(<%= <%# <%)
151
- def is_erb_stag?(s)
152
- ERB_STAG.member?(s)
153
- end
154
- end
155
-
156
- Scanner.default_scanner = TrimScanner
157
-
158
- class SimpleScanner < Scanner
159
- def scan
160
- @src.each do |line|
161
- line.split(SplitRegexp).each do |token|
162
- next if token.empty?
163
- yield(token)
164
- end
165
- end
166
- end
167
- end
168
-
169
- Scanner.regist_scanner(SimpleScanner, nil, false)
170
-
171
- begin
172
- require 'strscan'
173
- class SimpleScanner2 < Scanner
174
- def scan
175
- stag_reg = /(.*?)(<%%|<%=|<%#|<%|\n|\z)/
176
- etag_reg = /(.*?)(%%>|%>|\n|\z)/
177
- scanner = StringScanner.new(@src)
178
- while ! scanner.eos?
179
- scanner.scan(@stag ? etag_reg : stag_reg)
180
- text = scanner[1]
181
- elem = scanner[2]
182
- yield(text) unless text.empty?
183
- yield(elem) unless elem.empty?
184
- end
185
- end
186
- end
187
- Scanner.regist_scanner(SimpleScanner2, nil, false)
188
-
189
- class PercentScanner < Scanner
190
- def scan
191
- new_line = true
192
- stag_reg = /(.*?)(<%%|<%=|<%#|<%|\n|\z)/
193
- etag_reg = /(.*?)(%%>|%>|\n|\z)/
194
- scanner = StringScanner.new(@src)
195
- while ! scanner.eos?
196
- if new_line && @stag.nil?
197
- if scanner.scan(/%%/)
198
- yield('%')
199
- new_line = false
200
- next
201
- elsif scanner.scan(/%/)
202
- yield(PercentLine.new(scanner.scan(/.*?(\n|\z)/).chomp))
203
- next
204
- end
205
- end
206
- scanner.scan(@stag ? etag_reg : stag_reg)
207
- text = scanner[1]
208
- elem = scanner[2]
209
- yield(text) unless text.empty?
210
- yield(elem) unless elem.empty?
211
- new_line = (elem == "\n")
212
- end
213
- end
214
- end
215
- Scanner.regist_scanner(PercentScanner, nil, true)
216
-
217
- class ExplicitScanner < Scanner
218
- def scan
219
- new_line = true
220
- stag_reg = /(.*?)(<%%|<%=|<%#|<%-|<%|\n|\z)/
221
- etag_reg = /(.*?)(%%>|-%>|%>|\n|\z)/
222
- scanner = StringScanner.new(@src)
223
- while ! scanner.eos?
224
- if new_line && @stag.nil? && scanner.scan(/[ \t]*<%-/)
225
- yield('<%')
226
- new_line = false
227
- next
228
- end
229
- scanner.scan(@stag ? etag_reg : stag_reg)
230
- text = scanner[1]
231
- elem = scanner[2]
232
- new_line = (elem == "\n")
233
- yield(text) unless text.empty?
234
- if elem == '-%>'
235
- yield('%>')
236
- if scanner.scan(/(\n|\z)/)
237
- yield(:cr)
238
- new_line = true
239
- end
240
- elsif elem == '<%-'
241
- yield('<%')
242
- else
243
- yield(elem) unless elem.empty?
244
- end
245
- end
246
- end
247
- end
248
- Scanner.regist_scanner(ExplicitScanner, '-', false)
249
-
250
- rescue LoadError
251
- end
252
-
253
- class Buffer
254
- def initialize(compiler)
255
- @compiler = compiler
256
- @line = []
257
- @script = ''
258
- @compiler.pre_cmd.each do |x|
259
- push(x)
260
- end
261
- end
262
- attr_reader :script
263
-
264
- def push(cmd)
265
- @line << cmd
266
- end
267
-
268
- def cr
269
- @script << (@line.join('; '))
270
- @line = []
271
- @script << "\n"
272
- end
273
-
274
- def close
275
- return unless @line
276
- @compiler.post_cmd.each do |x|
277
- push(x)
278
- end
279
- @script << (@line.join('; '))
280
- @line = nil
281
- end
282
- end
283
-
284
- class Buffer16 < Buffer
285
- def initialize(compiler)
286
- super(compiler)
287
- @script = []
288
- end
289
-
290
- def script
291
- @script.join('')
292
- end
293
- end
294
-
295
- def make_buffer
296
- if RUBY_VERSION > '1.8'
297
- Buffer.new(self)
298
- else
299
- Buffer16.new(self)
300
- end
301
- end
302
-
303
- def compile(s)
304
- out = make_buffer
305
-
306
- content = ''
307
- scanner = make_scanner(s)
308
- scanner.scan do |token|
309
- if scanner.stag.nil?
310
- case token
311
- when PercentLine
312
- out.push("#{@put_cmd} #{content.dump}") if content.size > 0
313
- content = ''
314
- out.push(token.to_s)
315
- out.cr
316
- when :cr
317
- out.cr
318
- when '<%', '<%=', '<%#'
319
- scanner.stag = token
320
- out.push("#{@put_cmd} #{content.dump}") if content.size > 0
321
- content = ''
322
- when "\n"
323
- content << "\n"
324
- out.push("#{@put_cmd} #{content.dump}")
325
- out.cr
326
- content = ''
327
- when '<%%'
328
- content << '<%'
329
- else
330
- content << token
331
- end
332
- else
333
- case token
334
- when '%>'
335
- case scanner.stag
336
- when '<%'
337
- if content[-1] == ?\n
338
- content.chop!
339
- out.push(content)
340
- out.cr
341
- else
342
- out.push(content)
343
- end
344
- when '<%='
345
- out.push("#{@put_cmd}((#{content}).to_s)")
346
- when '<%#'
347
- # out.push("# #{content.dump}")
348
- end
349
- scanner.stag = nil
350
- content = ''
351
- when '%%>'
352
- content << '%>'
353
- else
354
- content << token
355
- end
356
- end
357
- end
358
- out.push("#{@put_cmd} #{content.dump}") if content.size > 0
359
- out.close
360
- out.script
361
- end
362
-
363
- def prepare_trim_mode(mode)
364
- case mode
365
- when 1
366
- return [false, '>']
367
- when 2
368
- return [false, '<>']
369
- when 0
370
- return [false, nil]
371
- when String
372
- perc = mode.include?('%')
373
- if mode.include?('-')
374
- return [perc, '-']
375
- elsif mode.include?('<>')
376
- return [perc, '<>']
377
- elsif mode.include?('>')
378
- return [perc, '>']
379
- else
380
- [perc, nil]
381
- end
382
- else
383
- return [false, nil]
384
- end
385
- end
386
-
387
- def make_scanner(src)
388
- Scanner.make_scanner(src, @trim_mode, @percent)
389
- end
390
-
391
- def initialize(trim_mode)
392
- @percent, @trim_mode = prepare_trim_mode(trim_mode)
393
- @put_cmd = 'print'
394
- @pre_cmd = []
395
- @post_cmd = []
396
- end
397
- attr_reader :percent, :trim_mode
398
- attr_accessor :put_cmd, :pre_cmd, :post_cmd
399
- end
400
- end
401
-
402
- # ERB
403
- class ERB
404
- def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout')
405
- @safe_level = safe_level
406
- compiler = ERB::Compiler.new(trim_mode)
407
- set_eoutvar(compiler, eoutvar)
408
- @src = compiler.compile(str)
409
- end
410
- attr :src
411
-
412
- def set_eoutvar(compiler, eoutvar = '_erbout')
413
- compiler.put_cmd = "#{eoutvar}.concat"
414
-
415
- cmd = []
416
- cmd.push "#{eoutvar} = ''"
417
-
418
- compiler.pre_cmd = cmd
419
-
420
- cmd = []
421
- cmd.push(eoutvar)
422
-
423
- compiler.post_cmd = cmd
424
- end
425
-
426
- def run(b=TOPLEVEL_BINDING)
427
- print self.result(b)
428
- end
429
-
430
- def result(b=TOPLEVEL_BINDING)
431
- if @safe_level
432
- th = Thread.start {
433
- $SAFE = @safe_level
434
- eval(@src, b)
435
- }
436
- return th.value
437
- else
438
- return eval(@src, b)
439
- end
440
- end
441
-
442
- def def_method(mod, methodname, fname='(ERB)')
443
- mod.module_eval("def #{methodname}\n" + self.src + "\nend\n", fname, 0)
444
- end
445
-
446
- def def_module(methodname='erb')
447
- mod = Module.new
448
- def_method(mod, methodname)
449
- mod
450
- end
451
-
452
- def def_class(superklass=Object, methodname='result')
453
- cls = Class.new(superklass)
454
- def_method(cls, methodname)
455
- cls
456
- end
457
- end
458
-
459
- # ERB::Util
460
- class ERB
461
- module Util
462
- public
463
- def html_escape(s)
464
- s.to_s.gsub(/&/, "&amp;").gsub(/\"/, "&quot;").gsub(/>/, "&gt;").gsub(/</, "&lt;")
465
- end
466
- alias h html_escape
467
-
468
- def url_encode(s)
469
- s.to_s.gsub(/[^a-zA-Z0-9_\-.]/n){ sprintf("%%%02X", $&.unpack("C")[0]) }
470
- end
471
- alias u url_encode
472
- end
473
- end
474
-
475
- # ERB::DefMethod
476
- class ERB
477
- module DefMethod
478
- public
479
- def def_erb_method(methodname, erb)
480
- if erb.kind_of? String
481
- fname = erb
482
- File.open(fname) {|f| erb = ERB.new(f.read) }
483
- erb.def_method(self, methodname, fname)
484
- else
485
- erb.def_method(self, methodname)
486
- end
487
- end
488
- module_function :def_erb_method
489
- end
490
- end