Fixed a few bugs in `mnit::mnit_fps`, a typo invalidated the value of `current_fps` and the use of long Ints were broken probably since they are tagged. Using floats should fix this last issue.
Finally, the goal of this PR is to copy the module `mnit::mnit_fps` to `gamnit::limit_fps` and adapt it to the gamnit framework. There is some code duplication, but the mnit version should be deleted with the engine when gamnit is completed.
Pull-Request: #1879
Reviewed-by: Jean Privat <jean@pryen.org>
-all: nitiwiki
+all: nitiwiki bin/nitiwiki_server
nitiwiki:
mkdir -p bin
../../bin/nitc src/nitiwiki.nit -o bin/nitiwiki
+bin/nitiwiki_server: $(shell ../../bin/nitls -M src/wiki_edit.nit)
+ ../../bin/nitc -o $@ src/wiki_edit.nit
+
check: nitiwiki
cd tests; make
* `SUBTITLE`: Wiki description
* `LOGO`: Wiki logo image path
-Additionnal macros can be used in specialized templates.
+Additional macros can be used in specialized templates.
### Main template
Be sure to set `wiki.rsync_dir` in order to correctly push your changes.
When using `--rsync`, keep in mind that the rendered output must be configured
to work on the web server.
+
+### Serve and edit with nitiwiki_server
+
+nitiwiki_server is a lightweight web server to publish the generated files
+and accept modifications from a web form.
+
+The binary available in `bin/nitiwiki_server` is configured for simple usage or demo.
+The source of the server, at `src/wiki_edit`, can be tweaked for more advanced use.
+It is also possible to import the source and add an instance of `EditAction` to a custom nitcorn server.
+
+To launch the server, change directory to the root of the wiki and run `nitiwiki_server`.
+It uses `config.ini` from the local directory and listen on localhost:8080.
+The template should define the macro `%EDIT%` and `config.ini` should define `wiki.edit=/edit/`.
+To limit who can edit the wiki, list the md5 sum of accepted passwords (one per line) in the local file `passwords`.
--- /dev/null
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Web server to server generated files and modify the wiki from a web form
+module wiki_edit
+
+import nitcorn
+import markdown
+import md5
+
+intrude import wiki_html
+
+# Page for editing markdown source
+class WikiEditForm
+ super WikiArticle
+
+ # Part of the title before the name of the page
+ var title_prefix: String
+
+ # Markdown content, for previews
+ redef var md
+
+ # Custom HTML code, for forms and links
+ var html: String
+
+ init do content = (md or else "").md_to_html.to_s + html
+
+ redef fun dir_href do return "edit" / href
+
+ redef fun tpl_article
+ do
+ var s = super
+ s.title = title_prefix + title
+ return s
+ end
+
+ # Fill and return a new `HttpResponse` with this page content
+ fun to_http_response: HttpResponse
+ do
+ var resp = new HttpResponse(200)
+ resp.body = tpl_page.write_to_string
+ return resp
+ end
+end
+
+# Action to serve edit forms, show previews and apply changes
+class EditAction
+ super Action
+
+ # Full public URL for the root of this wiki
+ var root_url: String
+
+ # Path to the wiki config
+ var config_file_path: String
+
+ # Configuration of the Wiki, loaded once
+ var wiki_config = new WikiConfig(config_file_path) is lazy
+
+ # Path to the root of the wiki
+ private var wiki_root: String = config_file_path.dirname is lazy
+
+ # Path to the source files
+ private var source_dir: String = (wiki_root / wiki_config.source_dir).simplify_path + "/" is lazy
+
+ # List of acceptable password to apply modifications
+ #
+ # If `null`, no password checks are applied and all modifications are accepted.
+ var passwords: nullable Collection[String]
+
+ # Reload the wiki instance with the latest changes
+ fun wiki: Nitiwiki
+ do
+ var wiki = new Nitiwiki(wiki_config)
+ wiki.parse
+ return wiki
+ end
+
+ redef fun answer(http_request, turi)
+ do
+ var action = http_request.string_arg("action")
+ var markdown = http_request.post_args.get_or_default("content", "")
+
+ var file_path = turi.strip_leading_slash
+ file_path = wiki_root / file_path
+
+ var abs_file_path = file_path.to_absolute_path
+ var abs_source_dir = source_dir.to_absolute_path
+
+ if not abs_file_path.has_prefix(abs_source_dir) then
+ # Attempting to access a file outside the source directory
+ var entity = new WikiEditForm(wiki, turi.strip_leading_slash,
+ "Access denied: ", "", "<p>Target outside of the source directory</p>")
+ return entity.to_http_response
+ end
+
+ if action == "Submit" then
+ var passwords = passwords
+ var password = http_request.post_args.get_or_null("password")
+ if passwords != null and (password == null or not passwords.has(password.md5)) then
+ # Deny modification
+ var entity = new WikiEditForm(wiki, turi.strip_leading_slash,
+ "Changes rejected: ", "", "<p>Password invalid</p>")
+ return entity.to_http_response
+ end
+
+ # Save markdown source
+ markdown = markdown.replace('\r', "")
+ markdown.write_to_file file_path
+
+ # Update HTML files
+ var wiki = wiki
+ wiki.render
+
+ var link
+ if turi.has_prefix("/pages/") then
+ link = root_url / turi.substring_from(7)
+ else link = root_url / turi
+ link = link.strip_extension(".md") + ".html"
+
+ # Show confirmation
+ var body = """
+<p>Your edits were recorded and the file is updated: <a href="{{{link}}}">{{{link}}}</a></p>
+"""
+ var entity = new WikiEditForm(wiki, turi.strip_leading_slash, "Changes saved: ", "", body)
+ return entity.to_http_response
+ else
+ # Show edit form, and preview when requested
+
+ # When not in a preview, use the local content of the file
+ if action != "Preview" then markdown = file_path.to_path.read_all
+
+ var form = """
+<form method="POST" action="/edit{{{turi}}}">
+ You may edit the file. When you are done, click on "Submit".<br/>
+ <textarea name="content" rows="30" cols="80">{{{markdown.html_escape}}}</textarea><br/>
+"""
+ if passwords != null then form += """
+ Password: <input type="password" name="password"><br/>
+"""
+ form += """
+ <input type="submit" name="action" value="Preview">
+ <input type="submit" name="action" value="Submit">
+</form>
+"""
+
+ # Show processed markdown only on preview
+ if action != "Preview" then markdown = ""
+
+ var entity = new WikiEditForm(wiki, turi.strip_leading_slash, "Edit source: ", markdown, form)
+ return entity.to_http_response
+ end
+ end
+end
+
+redef class String
+ private fun strip_leading_slash: String
+ do
+ if has_prefix("/") then return substring_from(1)
+ return self
+ end
+
+ private fun to_absolute_path: String
+ do
+ return (getcwd / self).simplify_path
+ end
+end
+
+var config_file_path = "config.ini"
+var iface = "localhost:8080"
+var password_file_path = "passwords"
+
+# Load passwords for file
+var passwords = if password_file_path.file_exists then
+ password_file_path.to_path.read_lines
+ else null
+
+var vh = new VirtualHost(iface)
+
+# Serve Markdown editing form
+var action = new EditAction("http://" + iface, config_file_path, passwords)
+vh.routes.add new Route("/edit", action)
+
+# Serve the static (and generated) content
+var path_to_public_files = config_file_path.dirname / action.wiki_config.out_dir
+vh.routes.add new Route(null, new FileServer(path_to_public_files))
+
+var factory = new HttpFactory.and_libevent
+factory.config.virtual_hosts.add vh
+factory.run
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="http://xymus.net/ens/">Enseignement</a></li>
- <li class="active"><a href="http://pep8.xymus.net/">Pep/8 Analysis</a></li>
+ <li><a href="http://xymus.net/opportunity/">Opportunité</a></li>
<li><a href="http://tnitter.xymus.net/">Tnitter</a></li>
+ <li class="active"><a href="http://pep8.xymus.net/">Pep/8 Analysis</a></li>
<li><a href="http://benitlux.xymus.net/">Benitlux</a></li>
- <li><a href="http://xymus.net/opportunity/">Opportunité</a></li>
- <li><a href="http://nitlanguage.org/">Nit</a></li>
</ul>
<ul class="nav navbar-nav pull-right">
+++ /dev/null
-[package]
-name=buffered_ropes
-tags=algo,text,lib
-maintainer=Lucas Bajolet <r4pass@hotmail.com>
-license=Apache-2.0
-[upstream]
-browse=https://github.com/nitlang/nit/tree/master/lib/buffered_ropes.nit
-git=https://github.com/nitlang/nit.git
-git.directory=lib/buffered_ropes.nit
-homepage=http://nitlanguage.org
-issues=https://github.com/nitlang/nit/issues
+++ /dev/null
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# This file is free software, which comes along with NIT. This software is
-# distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
-# without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
-# PARTICULAR PURPOSE. You can modify it is you want, provided this header
-# is kept unaltered, and a notification of the changes is added.
-# You are allowed to redistribute it and sell it, alone or is a part of
-# another product.
-
-# Ropes with a special kind of Leaves that act similar to a `Buffer`
-#
-# When using this module, re-allocations are limited by the introduction
-# of a larger-than-necessary buffered area for the native part of a `String`
-# in an append-only fashion.
-#
-# Concretely, when concatenating two small strings of length `n` + `m` < `maxlen`
-# What happens is that a `maxlen` byte buffer is allocated, ready to receive more
-# bytes a posteriori without necessarily reallocating a new byte array.
-#
-# Theoretically, this should lower the number of concatenations
-# and reallocations when concatenating `String` objects.
-module buffered_ropes
-
-intrude import core::text::ropes
-
-# Hidden buffer, used to simulate a `FlatBuffer` on a short string.
-#
-# This is to be used by low-level APIs because of its lack of
-# safety, if you use it, make sure you know what you are doing !
-#
-# Practically, it is the underlying representation of a `Leaf` in
-# the `Rope` block, its advantage is that it saves a bit more space
-# for future concatenations, without risking to overwrite previously
-# used space, making it suitable for Strings.
-#
-# Note for future use : Should there be parallel capacity in Nit at
-# some point, this is NOT thread safe !
-private class ManualBuffer
- var ns: NativeString is noinit
- # Current position in the `NativeString`
- #
- # It is used by the clients of `ManualBuffer` as a guard
- # to detect if the concatenation in the `ManualBuffer`
- # is safe or not.
- #
- # i.e. :
- # Say we have two strings `x` and `y` referencing the
- # same `ManualBuffer` `b`, `y` is the concatenation of
- # `x` and another string.
- #
- # If we try to concatenate a `String` `z` to `x`, a new
- # `ManualBuffer` will be created since `pos` and `x.length`
- # do not match.
- #
- # However, if we concatenate the same `String` to `y`,
- # the contents of `z` will be copied to the `ManualBuffer`.
- var pos = 0
-
- init do ns = new NativeString(maxlen)
-
- fun [](i: Int): Byte do return ns[i]
-end
-
-# Simple implementation of the iterator on Substrings for `Leaf`
-#
-# Basically just returns `self` encapsulated in a `FlatString`.
-private class LeafSubstrings
- super IndexedIterator[FlatText]
-
- var leaf: Leaf
- var str: FlatString is noinit
- var avail = true
-
- init do
- str = new FlatString.with_infos(leaf.buf.ns, leaf.length, 0, leaf.length - 1)
- end
-
- redef fun is_ok do return avail
-
- redef fun next do avail = false
-
- redef fun index do return 0
-
- redef fun item do return str
-end
-
-# Leaf of a `Rope`, used as a buffered area for speedy concatenation.
-private class Leaf
- super String
- super Rope
-
- var buf: ManualBuffer
- var bns: NativeString is noinit
- redef var length is noinit
-
- # Unsafe, but since it is an experiment, don't mind
- redef fun bytelen do return length
-
- redef fun empty do return new Leaf(new ManualBuffer)
-
- redef fun to_cstring do
- var len = length
- var ns = new NativeString(len + 1)
- ns[len] = 0u8
- buf.ns.copy_to(ns, len, 0, 0)
- return ns
- end
-
- redef fun substrings do return new LeafSubstrings(self)
-
- redef fun [](i) do return buf[i].to_i.code_point
-
- init do
- bns = buf.ns
- length = buf.pos
- end
-
- redef fun output do new FlatString.with_infos(buf.ns, length, 0, length - 1).output
-
- redef fun to_upper do
- var x = new FlatBuffer
- for i in chars do x.add(i.to_upper)
- return x.to_s
- end
-
- redef fun to_lower do
- var x = new FlatBuffer
- for i in chars do x.add(i.to_lower)
- return x.to_s
- end
-
- redef fun reversed do
- var x = new ManualBuffer
- var nns = x.ns
- var ns = bns
- var mlen = length
- var j = mlen - 1
- for i in [0 .. mlen[ do
- nns[j] = ns[i]
- j -= 1
- end
- x.pos = mlen - 1
- return new Leaf(x)
- end
-
- redef fun substring(from, len) do
- return new FlatString.with_infos(buf.ns, len, from, from + len - 1)
- end
-
- redef fun insert_at(s, pos) do
- var l = substring(0, pos)
- var r = substring_from(pos)
- return l + s + r
- end
-
- redef fun +(o) do
- var s = o.to_s
- var slen = s.bytelen
- var mlen = bytelen
- if slen == 0 then return self
- if mlen == 0 then return s
- var nlen = mlen + slen
- if nlen > maxlen then return new Concat(self, s)
- if s isa FlatString then
- var bpos = buf.pos
- var sits = s.items
- if bpos == mlen then
- sits.copy_to(buf.ns, slen, s.first_byte, bpos)
- buf.pos = bpos + slen
- return new Leaf(buf)
- else
- var b = new ManualBuffer
- var nbns = b.ns
- bns.copy_to(nbns, mlen, 0, 0)
- sits.copy_to(nbns, slen, s.first_byte, mlen)
- b.pos = nlen
- return new Leaf(b)
- end
- else if s isa Leaf then
- var bpos = buf.pos
- var sbns = s.bns
- if bpos == mlen then
- sbns.copy_to(bns, slen, 0, bpos)
- buf.pos += slen
- return new Leaf(buf)
- else
- var b = new ManualBuffer
- var nbns = b.ns
- bns.copy_to(nbns, mlen, 0, 0)
- sbns.copy_to(nbns, slen, 0, mlen)
- b.pos = nlen
- return new Leaf(b)
- end
- else if s isa Concat then
- if not s.left isa Concat then
- return new Concat(self + s.left, s.right)
- end
- return new Concat(self, s)
- else
- var bpos = buf.pos
- var b = buf
- if bpos != mlen then
- b = new ManualBuffer
- bns.copy_to(b.ns, mlen, 0, 0)
- end
- for i in s.bytes do
- bns[bpos] = i
- bpos += 1
- end
- return new Leaf(b)
- end
- end
-end
-
-redef class Concat
- redef fun to_cstring do
- var len = length
- var ns = new NativeString(len + 1)
- ns[len] = 0u8
- var off = 0
- for i in substrings do
- var ilen = i.length
- if i isa FlatString then
- i.items.copy_to(ns, ilen, i.first_byte, off)
- else if i isa Leaf then
- i.buf.ns.copy_to(ns, ilen, 0, off)
- else
- abort
- end
- off += ilen
- end
- return ns
- end
-
- redef fun +(o) do
- var s = o.to_s
- var slen = s.length
- if s isa FlatString then
- var r = right
- var rlen = r.length
- if rlen + slen > maxlen then return new Concat(left, new Concat(r, s))
- return new Concat(left, r + s)
- else if s isa Concat then
- return new Concat(self, s)
- else if s isa Leaf then
- var r = right
- var rlen = r.length
- if rlen + slen > maxlen then return new Concat(left, new Concat(r, s))
- return new Concat(left, r + s)
- else
- abort
- end
- end
-end
-
-redef class FlatString
- redef fun +(o) do
- var s = o.to_s
- var slen = s.length
- var mlen = length
- if slen == 0 then return self
- if mlen == 0 then return s
- if s isa FlatString then
- if slen + mlen > maxlen then return new Concat(self, s)
- var mits = items
- var sifrom = s.first_byte
- var mifrom = first_byte
- var sits = s.items
- var b = new ManualBuffer
- var bns = b.ns
- mits.copy_to(bns, mlen, mifrom, 0)
- sits.copy_to(bns, slen, sifrom, mlen)
- b.pos = mlen + slen
- return new Leaf(b)
- else if s isa Concat then
- var sl = s.left
- var sllen = sl.length
- if sllen + mlen > maxlen then return new Concat(self, s)
- return new Concat(sl + self, s.right)
- else if s isa Leaf then
- if slen + mlen > maxlen then return new Concat(self, s)
- var mifrom = first_byte
- var sb = s.buf
- var b = new ManualBuffer
- var bns = b.ns
- items.copy_to(bns, mlen, mifrom, 0)
- sb.ns.copy_to(bns, slen, 0, mlen)
- b.pos = mlen + slen
- return new Leaf(b)
- else
- abort
- end
- end
-end
-
-redef class Array[E]
-
- # Fast implementation
- redef fun to_s do
- var l = length
- if l == 0 then return ""
- if l == 1 then if self[0] == null then return "" else return self[0].to_s
- var its = _items
- var na = new NativeArray[String](l)
- var i = 0
- var sl = 0
- var mypos = 0
- while i < l do
- var itsi = its[i]
- if itsi == null then
- i += 1
- continue
- end
- var tmp = itsi.to_s
- sl += tmp.length
- na[mypos] = tmp
- i += 1
- mypos += 1
- end
- var ns = new NativeString(sl + 1)
- ns[sl] = 0u8
- i = 0
- var off = 0
- while i < mypos do
- var tmp = na[i]
- var tpl = tmp.length
- if tmp isa FlatString then
- tmp.items.copy_to(ns, tpl, tmp.first_byte, off)
- off += tpl
- else
- for j in tmp.substrings do
- var slen = j.length
- if j isa FlatString then
- j.items.copy_to(ns, slen, j.first_byte, off)
- else if j isa Leaf then
- j.buf.ns.copy_to(ns, slen, 0, off)
- end
- off += slen
- end
- end
- i += 1
- end
- return ns.to_s_with_length(sl)
- end
-end
end
# Return the canonicalized absolute pathname (see POSIX function `realpath`)
+ #
+ # Require: `file_exists`
fun realpath: String do
var cs = to_cstring.file_realpath
+ assert file_exists
var res = cs.to_s_with_copy
- # cs.free_malloc # FIXME memory leak
+ cs.free
return res
end
return (self >= 'a' and self <= 'z') or (self >= 'A' and self <= 'Z')
end
+ # Is `self` an hexadecimal digit ?
+ #
+ # assert 'A'.is_hexdigit
+ # assert not 'G'.is_hexdigit
+ # assert 'a'.is_hexdigit
+ # assert not 'g'.is_hexdigit
+ # assert '5'.is_hexdigit
+ fun is_hexdigit: Bool do return (self >= '0' and self <= '9') or (self >= 'A' and self <= 'F') or
+ (self >= 'a' and self <= 'f')
+
# Returns true if the char is an alpha or a numeric digit
#
# assert 'a'.is_alphanumeric
var c = input[i]
if c == '\n' then
eol = true
+ else if c == '\r' then
else if c == '\t' then
var np = pos + (4 - (pos & 3))
while pos < np do
import tnitter
import benitlux::benitlux_controller
import opportunity::opportunity_controller
+import nitiwiki::wiki_edit
# Header for the whole site
class MasterHeader
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li{{{actives.get_or_default("ens", "")}}}><a href="http://xymus.net/ens/">Enseignement</a></li>
- <li><a href="http://pep8.xymus.net/">Pep/8 Analysis</a></li>
+ <li{{{actives.get_or_default("opportunity", "")}}}><a href="http://xymus.net/opportunity/">Opportunité</a></li>
<li{{{actives.get_or_default("tnitter", "")}}}><a href="http://tnitter.xymus.net/">Tnitter</a></li>
+ <li><a href="http://pep8.xymus.net/">Pep/8 Analysis</a></li>
<li{{{actives.get_or_default("benitlux", "")}}}><a href="http://benitlux.xymus.net/">Benitlux</a></li>
- <li{{{actives.get_or_default("opportunity", "")}}}><a href="http://xymus.net/opportunity/">Opportunité</a></li>
- <li><a href="http://nitlanguage.org/">Nit</a></li>
</ul>
<ul class="nav navbar-nav pull-right">
tnitter_vh.routes.add new Route("/push/", new TnitterPush)
tnitter_vh.routes.add new Route(null, tnitter)
-
# Pep/8 Analysis is only a file server. It is available at `pep8.xymus.net`
# and through the global/default file server at `xymus.net/pep8/`
#
benitlux_vh.routes.add new Route("/rest/", benitlux_rest)
benitlux_vh.routes.add new Route(null, benitlux_sub)
+# Opportunity service
var opportunity = new OpportunityWelcome
var opportunity_rest = new OpportunityRESTAction
default_vh.routes.add new Route("/opportunity/rest/", opportunity_rest)
default_vh.routes.add new Route("/opportunity/", opportunity)
+# Nitiwiki modification form
+var passwords = "nitiwiki_passwords".to_path.read_lines
+assert passwords.not_empty
+default_vh.routes.add new Route("/edit", new EditAction("http://xymus.net/", "/home/xymus/projects/wiki/config.ini", passwords))
+
# We use a special file server for the path `xymus.net/ens` only to display
# a different header.
var file_server_ens = new FileServer("/var/www/ens/")
if nexpr != null then
if nexpr isa ANewExpr then
mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
+ else if nexpr isa AAsCastExpr then
+ mtype = modelbuilder.resolve_mtype_unchecked(mmodule, mclassdef, nexpr.n_type, true)
else if nexpr isa AIntegerExpr then
var cla: nullable MClass = null
if nexpr.value isa Int then
# limitations under the License.
#alt1 import core
-#alt1 import buffered_ropes
var st = "quick brown fox over the lazy dog"
# limitations under the License.
#alt2 import core
-#alt2 import buffered_ropes
var str = "Woe to you, oh earth and sea for the Devil sends the beast with wrath because he knows the time is short. Let him who hath understanding reckon the number of the beast, for it is a human number, its number is Six Hundred and Sixty-Six."
var spaces = " "