Intro a portable client for Tnitter listing the more recent 16 tnits. It is more of a test and example on using `AsyncHttpRequest` to do both simple request to a REST server and implement push notifications using an open request. By design, this client does not support posting Tnits, this feature would require much more code and it may be simplified by future services in the lib.
To support the client the server has a few new REST interfaces. One to list the tnits and the other for push notifications.
This PR also fixes a few bugs on the Tnitter server: losing the session on posting and empty post being accepted by the server.
-----
Note for the reviewers: You may be better to read commits individually. There are 2 bigs commits, one extracts the database logic from the model module and the other introduces code generated by Jwrapper for the native layer of that Android ui implementation.
Pull-Request: #1834
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>
You can link to an anchor inside a page, using something like `[[WikiLink#foo]]`.
+#### Trails of Pages
+
+Wikilinks, with the directive `trail`, will register the target page as an element of a trail.
+Each `trail` are chained together and will display navigational link `prev` for the previous page of the trail, `next` for the next page of the trail and `up` to go to the pages that has used the `trail` wikilink.
+
+For instance, if the page `doc.md` has the following content:
+
+~~~md
+To use nitiwiki, first [[trail: install|install it]],
+then [[trail: simple_wiki|create a first wiki]].
+
+You can also do advanced things like:
+
+* [[trail: github|editing pages with github]]
+* [[trail: templating| adapting the templates]]
+~~~
+
+A trail will be made and will consist of the sequence of pages `install`, `simple_wiki`, `github` and `templating`.
+On each one of these pages, there will be links for the previous page, the next page and the `doc.md` page.
+
+If a page includes trail wikilinks and is also the target for trail wikilinks, then the two trails are merged and pages will be visitable in a depth-first order.
+This nesting of trails can be used to have sections and sub-sections.
+
#### Render the wiki in HTML
Once you have done your changes, use:
%HEADER%
%TOP_MENU%
<div>
+ %TRAIL%
%BODY%
+ %TRAIL%
%FOOTER%
</div>
</body>
* `TOP_MENU`: Wiki top menu (see [Topmenu template](#Topmenu_template))
* `HEADER`: Wiki header (see [Header template](#Header_template))
* `BODY`: Wiki body content
+* `TRAIL`: content of the trail navigational links, if any (see [Trails of Pages](#Trails_of_Pages))
### Header template
%HEADER%
%TOP_MENU%
<div>
+ %TRAIL%
%BODY%
+ %TRAIL%
%FOOTER%
</div>
</body>
.summary li li li { font-size: 12px; font-weight: normal }
.breadcrumb { margin-top: 20px; }
+
+.trail {
+ list-style-type: none;
+ color: #838183;
+ text-align: center;
+}
+
+.trail li{
+ display: inline;
+}
+
+.trail li+li:before{
+ content: " | ";
+}
--- /dev/null
+# a last page
--- /dev/null
+# another page
--- /dev/null
+
+# Example of trail
+
+* [[trail: a_page]]
+* [[trail: another_page]]
+* [[trail: sub_section]]
+* [[trail: a_last_page]]
--- /dev/null
+# A sub-section
+
+* [[trail: foo]]
+* [[trail: bar]]
%TOP_MENU%
<div class="container">
<div class="row">
+ %TRAIL%
%BODY%
+ %TRAIL%
</div>
%FOOTER%
</div>
index.is_dirty = true
add_child index
end
+ # Hack: Force the rendering of `index` first so that trails are collected
+ # TODO: Add first-pass analysis to collect global information before doing the rendering
+ index.render
super
end
if tpl.has_macro("FOOTER") then
tpl.replace("FOOTER", tpl_footer)
end
+ if tpl.has_macro("TRAIL") then
+ tpl.replace("TRAIL", tpl_trail)
+ end
return tpl
end
return tpl
end
+ # Generate navigation links for the trail of this article, if any.
+ #
+ # A trail is generated if the article include or is included in a trail.
+ # See `wiki.trails` for details.
+ fun tpl_trail: Writable do
+ if not wiki.trails.has(self) then return ""
+
+ # Get the position of `self` in the trail
+ var flat = wiki.trails.to_a
+ var pos = flat.index_of(self)
+ assert pos >= 0
+
+ var res = new Template
+ res.add "<ul class=\"trail\">"
+ if pos > 0 then
+ var target = flat[pos-1]
+ res.add "<li>{target.a_from(self, "prev")}</li>"
+ end
+ var parent = wiki.trails.parent(self)
+ if parent != null then
+ res.add "<li>{parent.a_from(self, "up")}</li>"
+ end
+ if pos < flat.length - 1 then
+ var target = flat[pos+1]
+ res.add "<li>{target.a_from(self, "next")}</li>"
+ end
+ res.add "</ul>"
+
+ return res
+ end
+
# Generate the HTML footer for this article.
fun tpl_footer: Writable do
var file = footer_file
import wiki_base
import markdown::wikilinks
+import ordered_tree
redef class Nitiwiki
# Looks up a WikiEntry by its `name`.
end
return entry
end
+
+ # Trails between pages
+ #
+ # Trails are represented as a forest of entries.
+ # This way it is possible to represent a flat-trail as a visit of a tree.
+ var trails = new OrderedTree[WikiEntry]
end
redef class WikiEntry
return res
end
+ # A relative hyperlink <a> to `self` from the page `context`.
+ #
+ # If `text` is not given, `title` will be used instead.
+ fun a_from(context: WikiEntry, text: nullable Text): Writable
+ do
+ var title = title.html_escape
+ if text == null then text = title else text = text.html_escape
+ var href = href_from(context)
+ return """<a href="{{{href}}}" title="{{{title}}}">{{{text}}}</a>"""
+ end
+
redef fun render do
super
if not is_dirty and not wiki.force_render then return
var name = token.name
v.add "<a "
if not link.has_prefix("http://") and not link.has_prefix("https://") then
+ # Extract commands from the link.
+ var command = null
+ var command_split = link.split_once_on(":")
+ if command_split.length > 1 then
+ command = command_split[0].trim
+ link = command_split[1].trim
+ end
+
if link.has("#") then
var parts = link.split_with("#")
link = parts.first
if target != null then
if name == null then name = target.title
link = target.href_from(context)
+
+ if command == "trail" then
+ if target isa WikiSection then target = target.index
+ wiki.trails.add(context, target)
+ end
else
wiki.message("Warning: unknown wikilink `{link}` (in {context.src_path.as(not null)})", 0)
v.add "class=\"broken\" "
Render section pages -> out
-Render article contact -> wiki2/out/contact.html
Warning: unknown wikilink `not found` (in pages/index.md)
Warning: unknown wikilink `Not Found` (in pages/index.md)
Warning: unknown wikilink `/not/found` (in pages/index.md)
Warning: unknown wikilink `not found` (in pages/index.md)
Warning: unknown wikilink `not found` (in pages/index.md)
Render article index -> wiki2/out/index.html
+Render article contact -> wiki2/out/contact.html
Render article other_page -> wiki2/out/other_page.html
Render section sec1 -> out/sec1
Render article index -> wiki2/out/sec1/index.html
Render section pages -> out
-Render article contact -> wiki3/out/contact.html
Render article index -> wiki3/out/index.html
+Render article contact -> wiki3/out/contact.html
Render article other_page -> wiki3/out/other_page.html
Render article sitemap -> wiki3/out/sitemap.html
# Matches are a part of a `Text` found by a `Pattern`.
class Match
# The base string matched
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.string == "hello world"
+ # ~~~
var string: String
# The starting position in the string
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.from == 3
+ # ~~~
var from: Int
# The length of the matching part
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.length == 2
+ # ~~~
var length: Int
# The position of the first character just after the matching part.
# May be out of the base string
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.after == 5
+ # ~~~
fun after: Int do return from + length
# The contents of the matching part
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.to_s == "lo"
+ # ~~~
redef fun to_s do return string.substring(from,length)
+ # The content of `string` before the match
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.text_before == "hel"
+ # ~~~
+ fun text_before: String do return string.substring(0, from)
+
+ # The content of `string` after the match
+ #
+ # ~~~
+ # var m = "hello world".search("lo")
+ # assert m.text_after == " world"
+ # ~~~
+ fun text_after: String do return string.substring_from(after)
+
init
do
assert positive_length: length >= 0
return null
end
+ # Extract a given prefix, if any.
+ #
+ # ~~~
+ # var p = "hello world".prefix("hello")
+ # assert p != null
+ # assert p.text_after == " world"
+ # ~~~
+ fun prefix(t: Text): nullable Match do
+ var len = t.length
+ if substring(0, len) == t then
+ return new Match(self.to_s, 0, len)
+ end
+ return null
+ end
+
+ # Extract a given suffix, if any.
+ #
+ # ~~~
+ # var p = "hello world".suffix("world")
+ # assert p != null
+ # assert p.text_before == "hello "
+ # ~~~
+ fun suffix(t: Text): nullable Match do
+ var len = t.length
+ var from = length - len
+ if substring(from, len) == t then
+ return new Match(self.to_s, from, len)
+ end
+ return null
+ end
+
# Search all occurrences of `pattern` into self.
#
# var a = new Array[Int]
res.add(null_instance)
continue
end
- if param.is_vararg and map.vararg_decl > 0 then
- var vararg = exprs.sub(j, map.vararg_decl)
+ if param.is_vararg and args[i].vararg_decl > 0 then
+ var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype
var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
res.add(arg)
res.add(null_instance)
continue
end
- if param.is_vararg and map.vararg_decl > 0 then
- var vararg = exprs.sub(j, map.vararg_decl)
+ if param.is_vararg and args[i].vararg_decl > 0 then
+ var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype
var arg = self.vararg_instance(mpropdef, recv, vararg, elttype)
res.add(arg)
res.add(null_instance)
continue
end
- if param.is_vararg and map.vararg_decl > 0 then
- var vararg = exprs.sub(j, map.vararg_decl)
+ if param.is_vararg and args[i].vararg_decl > 0 then
+ var vararg = exprs.sub(j, args[i].vararg_decl)
var elttype = param.mtype.anchor_to(self.mainmodule, recv.mtype.as(MClassType))
var arg = self.array_instance(vararg, elttype)
res.add(arg)
for i in [0..mparameters.length[ do
var parameter = mparameters[i]
if parameter.is_vararg then
- assert vararg_rank == -1
+ if vararg_rank >= 0 then
+ # If there is more than one vararg,
+ # consider that additional arguments cannot be mapped.
+ vararg_rank = -1
+ break
+ end
vararg_rank = i
end
end
self.vararg_rank = vararg_rank
end
- # The rank of the ellipsis (`...`) for vararg (starting from 0).
+ # The rank of the main ellipsis (`...`) for vararg (starting from 0).
# value is -1 if there is no vararg.
# Example: for "(a: Int, b: Bool..., c: Char)" #-> vararg_rank=1
+ #
+ # From a model POV, a signature can contain more than one vararg parameter,
+ # the `vararg_rank` just indicates the one that will receive the additional arguments.
+ # However, currently, if there is more that one vararg parameter, no one will be the main one,
+ # and additional arguments will be refused.
var vararg_rank: Int is noinit
# The number of parameters
var sig = mpropdef.msignature
if sig == null then continue # Skip broken method
- for param in sig.mparameters do
- var ret_type = param.mtype
- var mparameter = new MParameter(param.name, ret_type, false)
- mparameters.add(mparameter)
- end
+ mparameters.add_all sig.mparameters
initializers.add(mpropdef.mproperty)
mpropdef.mproperty.is_autoinit = true
end
continue # skip the vararg
end
- var paramtype = param.mtype
- self.visit_expr_subtype(arg, paramtype)
+ if not param.is_vararg then
+ var paramtype = param.mtype
+ self.visit_expr_subtype(arg, paramtype)
+ else
+ check_one_vararg(arg, param)
+ end
end
if min_arity > 0 then
var paramtype = msignature.mparameters[vararg_rank].mtype
var first = args[vararg_rank]
if vararg_decl == 0 then
- var mclass = get_mclass(node, "Array")
- if mclass == null then return null # Forward error
- var array_mtype = mclass.get_mtype([paramtype])
- if first isa AVarargExpr then
- self.visit_expr_subtype(first.n_expr, array_mtype)
- first.mtype = first.n_expr.mtype
- else
- # only one vararg, maybe `...` was forgot, so be gentle!
- var t = visit_expr(first)
- if t == null then return null # Forward error
- if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
- # Not acceptable but could be a `...`
- error(first, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
- return null
- end
- # Standard valid vararg, finish the job
- map.vararg_decl = 1
- self.visit_expr_subtype(first, paramtype)
- end
+ if not check_one_vararg(first, msignature.mparameters[vararg_rank]) then return null
else
- map.vararg_decl = vararg_decl + 1
+ first.vararg_decl = vararg_decl + 1
for i in [vararg_rank..vararg_rank+vararg_decl] do
self.visit_expr_subtype(args[i], paramtype)
end
return map
end
+ # Check an expression as a single vararg.
+ # The main point of the method if to handle the case of reversed vararg (see `AVarargExpr`)
+ fun check_one_vararg(arg: AExpr, param: MParameter): Bool
+ do
+ var paramtype = param.mtype
+ var mclass = get_mclass(arg, "Array")
+ if mclass == null then return false # Forward error
+ var array_mtype = mclass.get_mtype([paramtype])
+ if arg isa AVarargExpr then
+ self.visit_expr_subtype(arg.n_expr, array_mtype)
+ arg.mtype = arg.n_expr.mtype
+ else
+ # only one vararg, maybe `...` was forgot, so be gentle!
+ var t = visit_expr(arg)
+ if t == null then return false # Forward error
+ if not is_subtype(t, paramtype) and is_subtype(t, array_mtype) then
+ # Not acceptable but could be a `...`
+ error(arg, "Type Error: expected `{paramtype}`, got `{t}`. Is an ellipsis `...` missing on the argument?")
+ return false
+ end
+ # Standard valid vararg, finish the job
+ arg.vararg_decl = 1
+ self.visit_expr_subtype(arg, paramtype)
+ end
+ return true
+ end
+
fun error(node: ANode, message: String)
do
self.modelbuilder.error(node, message)
class SignatureMap
# Associate a parameter to an argument
var map = new ArrayMap[Int, Int]
-
- # The length of the vararg sequence
- # 0 if no vararg or if reverse vararg (cf `AVarargExpr`)
- var vararg_decl: Int = 0
end
# A specific method call site with its associated informations.
# The result of the evaluation of `self` must be
# stored inside the designated array (there is an implicit `push`)
var comprehension: nullable AArrayExpr = null
+
+ # It indicates the number of arguments collected as a vararg.
+ #
+ # When 0, the argument is used as is, without transformation.
+ # When 1, the argument is transformed into an singleton array.
+ # Above 1, the arguments and the next ones are transformed into a common array.
+ #
+ # This attribute is meaning less on expressions not used as attributes.
+ var vararg_decl: Int = 0
end
redef class ABlockExpr
--- /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.
+
+import array
+
+class A
+ fun x(ints: Int...) is autoinit do
+ for i in ints do
+ 'X'.output
+ i.output
+ end
+ '\n'.output
+ end
+end
+
+class B
+ super A
+ fun y(objs: Object...) is autoinit do
+ for i in objs do
+ 'Y'.output
+ i.output
+ end
+ '\n'.output
+ end
+end
+
+var x
+
+#alt1#x = new A
+x = new A(1)
+x = new A(10, 20)
+x = new A([100, 200, 300]...)
+
+#aly1#x = new B(1)
+x = new B(1, 2)
+#alt1#x = new B(1, 2, 3)
+#alt1#x = new B([10, 20], 33)
+x = new B([10, 11]..., 20)
+x = new B(10, [20, 21]...)
+x = new B([10, 11]..., [20, 21, 23]...)
--- /dev/null
+X1
+
+X10
+X20
+
+X100
+X200
+X300
+
+X1
+
+Y2
+
+X10
+X11
+
+Y20
+
+X10
+
+Y20
+Y21
+
+X10
+X11
+
+Y20
+Y21
+Y23
+
--- /dev/null
+alt/base_vararg_mult_alt1.nit:40,5--7: Error: expected at least 1 argument(s) for `init(ints: Int...)`; got 0. See introduction at `core::Object::init`.
+alt/base_vararg_mult_alt1.nit:47,5--7: Error: expected 2 argument(s) for `init(ints: Int..., objs: Object...)`; got 3. See introduction at `core::Object::init`.
+alt/base_vararg_mult_alt1.nit:48,11--18: Type Error: expected `Int`, got `Array[Int]`. Is an ellipsis `...` missing on the argument?