contrib/nitiwiki: use `config` instead of `opts`
[nit.git] / contrib / nitiwiki / src / wiki_edit.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Web server to server generated files and modify the wiki from a web form
16 module wiki_edit
17
18 import nitcorn
19 import markdown
20 import md5
21 import config
22
23 intrude import wiki_html
24
25 # Page for editing markdown source
26 class WikiEditForm
27 super WikiArticle
28
29 # Part of the title before the name of the page
30 var title_prefix: String
31
32 # Markdown content, for previews
33 redef var md
34
35 # Custom HTML code, for forms and links
36 var html: String
37
38 init do content = (md or else "").md_to_html.to_s + html
39
40 redef fun dir_href do return "edit" / href
41
42 redef fun tpl_article
43 do
44 var s = super
45 s.title = title_prefix + title
46 return s
47 end
48
49 # Fill and return a new `HttpResponse` with this page content
50 fun to_http_response: HttpResponse
51 do
52 var resp = new HttpResponse(200)
53 resp.body = tpl_page.write_to_string
54 return resp
55 end
56 end
57
58 # Action to serve edit forms, show previews and apply changes
59 class EditAction
60 super Action
61
62 # Full public URL for the root of this wiki
63 var root_url: String
64
65 # Path to the wiki config
66 var config_file_path: String
67
68 # Configuration of the Wiki, loaded once
69 var wiki_config = new WikiConfig(config_file_path) is lazy
70
71 # Path to the root of the wiki
72 private var wiki_root: String = config_file_path.dirname is lazy
73
74 # Path to the source files
75 private var source_dir: String = (wiki_root / wiki_config.source_dir).simplify_path + "/" is lazy
76
77 # List of acceptable password to apply modifications
78 #
79 # If `null`, no password checks are applied and all modifications are accepted.
80 var passwords: nullable Collection[String]
81
82 # Reload the wiki instance with the latest changes
83 fun wiki: Nitiwiki
84 do
85 var wiki = new Nitiwiki(wiki_config)
86 wiki.parse
87 return wiki
88 end
89
90 redef fun answer(http_request, turi)
91 do
92 var action = http_request.string_arg("action")
93 var markdown = http_request.post_args.get_or_default("content", "")
94
95 var file_path = turi.strip_leading_slash
96 file_path = wiki_root / file_path
97
98 var abs_file_path = file_path.to_absolute_path
99 var abs_source_dir = source_dir.to_absolute_path
100
101 if not abs_file_path.has_prefix(abs_source_dir) then
102 # Attempting to access a file outside the source directory
103 var entity = new WikiEditForm(wiki, turi.strip_leading_slash,
104 "Access denied: ", "", "<p>Target outside of the source directory</p>")
105 return entity.to_http_response
106 end
107
108 if action == "Submit" then
109 var passwords = passwords
110 var password = http_request.post_args.get_or_null("password")
111 if passwords != null and (password == null or not passwords.has(password.md5)) then
112 # Deny modification
113 var entity = new WikiEditForm(wiki, turi.strip_leading_slash,
114 "Changes rejected: ", "", "<p>Password invalid</p>")
115 return entity.to_http_response
116 end
117
118 # Save markdown source
119 markdown = markdown.replace('\r', "")
120 markdown.write_to_file file_path
121
122 # Update HTML files
123 var wiki = wiki
124 wiki.render
125
126 var link
127 if turi.has_prefix("/pages/") then
128 link = root_url / turi.substring_from(7)
129 else link = root_url / turi
130 link = link.strip_extension(".md") + ".html"
131
132 # Show confirmation
133 var body = """
134 <p>Your edits were recorded and the file is updated: <a href="{{{link}}}">{{{link}}}</a></p>
135 """
136 var entity = new WikiEditForm(wiki, turi.strip_leading_slash, "Changes saved: ", "", body)
137 return entity.to_http_response
138 else
139 # Show edit form, and preview when requested
140
141 # When not in a preview, use the local content of the file
142 if action != "Preview" then markdown = file_path.to_path.read_all
143
144 var form = """
145 <form method="POST" action="/edit{{{turi}}}">
146 You may edit the file. When you are done, click on "Submit".<br/>
147 <textarea name="content" rows="30" cols="80">{{{markdown.html_escape}}}</textarea><br/>
148 """
149 if passwords != null then form += """
150 Password: <input type="password" name="password"><br/>
151 """
152 form += """
153 <input type="submit" name="action" value="Preview">
154 <input type="submit" name="action" value="Submit">
155 </form>
156 """
157
158 # Show processed markdown only on preview
159 if action != "Preview" then markdown = ""
160
161 var entity = new WikiEditForm(wiki, turi.strip_leading_slash, "Edit source: ", markdown, form)
162 return entity.to_http_response
163 end
164 end
165 end
166
167 redef class String
168 private fun strip_leading_slash: String
169 do
170 if has_prefix("/") then return substring_from(1)
171 return self
172 end
173
174 private fun to_absolute_path: String
175 do
176 return (getcwd / self).simplify_path
177 end
178 end
179
180 var opt_config = new OptionString("Path to config.ini file", "-c", "--config")
181 var opt_host = new OptionString("Host to bind the server to", "--host")
182 var opt_port = new OptionInt("Port to bind the server to", 8000, "--port")
183 var opt_pass = new OptionString("Password file path", "--pass")
184
185 var config = new Config
186 config.add_option(opt_config, opt_host, opt_port, opt_pass)
187
188 var usage = new Buffer
189 usage.append "Usage: wiki_edit [OPTION]...\n"
190 usage.append "Web server to server generated files and modify the wiki from a web form."
191 config.tool_description = usage.write_to_string
192
193 config.parse_options(args)
194
195 if config.opt_help.value then
196 config.usage
197 exit 0
198 end
199
200 var config_file_path = opt_config.value or else "config.ini"
201 var iface = "{opt_host.value or else "localhost"}:{opt_port.value}"
202 var password_file_path = opt_pass.value or else "passwords"
203
204 # Load passwords for file
205 var passwords = if password_file_path.file_exists then
206 password_file_path.to_path.read_lines
207 else null
208
209 var vh = new VirtualHost(iface)
210
211 # Serve Markdown editing form
212 var action = new EditAction("http://" + iface, config_file_path, passwords)
213 vh.routes.add new Route("/edit", action)
214
215 # Serve the static (and generated) content
216 var path_to_public_files = config_file_path.dirname / action.wiki_config.out_dir
217 vh.routes.add new Route(null, new FileServer(path_to_public_files))
218
219 var factory = new HttpFactory.and_libevent
220 factory.config.virtual_hosts.add vh
221 factory.run