* Introduce a search field in the top navbar connected to the `/search` service.
* Added a stub implementation of a search service depending on APIList.
Demo: http://nitweb.moz-code.org/
Pull-Request: #2157
Reviewed-by: Jean Privat <jean@pryen.org>
app.user = null
return true
else if res isa BenitluxError then
- app.feedback((res.user_message or else res.message).t)
+ feedback((res.user_message or else res.message).t)
return true
else if res isa Error then
- app.feedback res.message.t
+ feedback res.message.t
return true
end
return false
end
+
+ # Show feedback pertinent to the user, defaults to a platform specific popup
+ fun feedback(text: String) do app.feedback text
end
# Async request with services to act on the windows of the app
map["Welcome %0!"] = "Bienvenue %0!"
map["Logged in as %0"] = "Connecté en tant que %0"
map["Username"] = "Nom d'utilisateur"
- map["Invalid name"] = "Nom d'utilisateur invalide"
map["Password"] = "Mot de passe"
- map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+ map["Repeat password"] = "Répéter le mot de passe"
map["Email"] = "Courriel"
map["Login"] = "Se connecter"
+ map["Loging in..."] = "Authentification..."
map["Logout"] = "Se déconnecter"
map["Signup"] = "Créer un compte"
+ map["Signing up..."] = "Création du compte..."
+
+ map["Passwords must be composed of at least 6 characters."] = "Le mot de passe doit avoir au moins 6 charactères."
+ map["Fill the following fields to sign up."] = "Remplissez les champs suivants pour créer un compte."
+
+ map["Passwords do not match."] = "Les mots de passe ne correspondent pas."
+ map["Invalid username."] = "Nom d'utilisateur invalide."
+ map["Invalid password."] = "Mot de passe invalide."
+ map["Username already in use."] = "Le nom d'utilisateur est déjà réservé."
+ map["Invalid username and password combination."] = "La combinaison de nom et mot de passe n'est pas reconnue."
# Social views
map["Follow"] = "Suivre"
class SignupWindow
super Window
- # Main window layout
- var layout = new ListLayout(parent=self)
+ private var list = new ListLayout(parent=self)
+ private var lbl_feedback = new Label(parent=list, text="Welcome")
- private var lbl_welcome = new Label(parent=layout, text="Welcome")
+ private var layout_login = new VerticalLayout(parent=list)
+
+ # ---
+ # First the login options
# Name
- private var name_line = new HorizontalLayout(parent=layout)
+ private var name_line = new HorizontalLayout(parent=layout_login)
private var lbl_name = new Label(parent=name_line, text="Username".t)
private var txt_name = new TextInput(parent=name_line, text=app.user)
- # Pass
- private var pass_line = new HorizontalLayout(parent=layout)
+ # Password
+ private var pass_line = new HorizontalLayout(parent=layout_login)
private var lbl_pass = new Label(parent=pass_line, text="Password".t)
private var txt_pass = new TextInput(parent=pass_line, is_password=true)
- private var lbl_pass_desc = new Label(parent=layout,
+ private var lbl_pass_desc = new Label(parent=layout_login, size = 0.5,
text="Passwords must be composed of at least 6 characters.".t)
- private var but_login = new Button(parent=layout, text="Login".t)
+ private var but_login = new Button(parent=layout_login, text="Login".t)
+
+ # ---
+ # Then, the signup options
+
+ private var layout_register = new VerticalLayout(parent=list)
+
+ private var lbl_signup_desc = new Label(parent=layout_register, size = 0.5,
+ text="Fill the following fields to sign up.".t)
+
+ # Repeat password
+ private var pass_line2 = new HorizontalLayout(parent=layout_register)
+ private var lbl_pass2 = new Label(parent=pass_line2, text="Repeat password".t)
+ private var txt_pass2 = new TextInput(parent=pass_line2, is_password=true)
# Email
- private var email_line = new HorizontalLayout(parent=layout)
+ private var email_line = new HorizontalLayout(parent=layout_register)
private var lbl_email = new Label(parent=email_line, text="Email".t)
private var txt_email = new TextInput(parent=email_line)
- private var but_signup = new Button(parent=layout, text="Signup".t)
-
- private var lbl_feedback = new Label(parent=layout, text="")
+ private var but_signup = new Button(parent=layout_register, text="Signup".t)
init
do
- lbl_pass_desc.size = 0.5
-
for c in [but_login, but_signup] do
c.observers.add self
end
var name = txt_name.text
if name == null or not name.name_is_ok then
- feedback "Invalid name".t
+ feedback "Invalid username.".t
return
end
var pass = txt_pass.text
if pass == null or not pass.pass_is_ok then
- feedback "Invalid password".t
+ feedback "Invalid password.".t
return
end
if sender == but_login then
+ feedback "Logging in...".t
(new LoginOrSignupAction(self, "rest/login?name={name}&pass={pass.pass_hash}")).start
else if sender == but_signup then
+ if pass != txt_pass2.text then
+ feedback "Passwords do not match.".t
+ return
+ end
+
var email = txt_email.text
if email == null or email.is_empty then
feedback "Invalid email".t
return
end
+ feedback "Signing up...".t
(new LoginOrSignupAction(self, "rest/signup?name={name}&pass={pass.pass_hash}&email={email}")).start
end
end
app.on_log_in
end
-end
-
-# Async request for signing up
-class SignupAction
- super WindowHttpRequest
- redef type W: SignupWindow
-
- init do affected_views.add_all([window.but_signup])
-
- redef fun on_load(res)
- do
- if intercept_error(res) then return
-
- if not res isa LoginResult then
- on_fail new Error("Server sent unexpected data {res or else "null"}")
- return
- end
-
- app.token = res.token
- app.user = res.user.name
- app.on_log_in
- end
+ redef fun feedback(text) do window.feedback text
end
# Check if already in user
var stmt = select("ROWID FROM users WHERE lower({user.to_sql_string}) = lower(name)")
assert stmt != null else print_error "Select 'sign_up' failed with: {error or else "?"}"
- if not stmt.iterator.to_a.is_empty then return "Username already in use"
+ if not stmt.iterator.to_a.is_empty then return "Username already in use."
# Check email use
stmt = select("ROWID FROM users WHERE lower({email.to_sql_string}) = lower(email)")
var entry = entries[path]
if not entry.is_dirty then continue
var name = entry.name
- if entry.has_source then name = entry.src_path.to_s
+ if entry.has_source then name = entry.src_path.as(not null)
if entry.is_new then
print " + {name}"
else
fun need_render(src, target: String): Bool do
if force_render then return true
if not target.file_exists then return true
- return src.file_stat.mtime >= target.file_stat.mtime
+ return src.file_stat.as(not null).mtime >= target.file_stat.as(not null).mtime
end
# Create a new `WikiSection`.
# Used to translate ids in beautiful page names.
fun pretty_name(name: String): String do
name = name.replace("_", " ")
- name = name.capitalized
+ name = name.capitalized(keep_upper=true)
return name
end
end
# Returns `-1` if not `has_source`.
fun create_time: Int do
if not has_source then return -1
- return src_full_path.file_stat.ctime
+ return src_full_path.as(not null).file_stat.as(not null).ctime
end
# Entry last modification time.
# Returns `-1` if not `has_source`.
fun last_edit_time: Int do
if not has_source then return -1
- return src_full_path.file_stat.mtime
+ return src_full_path.as(not null).file_stat.as(not null).mtime
end
# Entry list rendering time.
# Returns `-1` if `is_new`.
fun last_render_time: Int do
if is_new then return -1
- return out_full_path.file_stat.mtime
+ return out_full_path.file_stat.as(not null).mtime
end
# Entries hierarchy
# then returns the main wiki template file.
fun template_file: String do
if is_root then return wiki.config.template_file
- return parent.template_file
+ return parent.as(not null).template_file
end
# Header template file for `self`.
# Behave like `template_file`.
fun header_file: String do
if is_root then return wiki.config.header_file
- return parent.header_file
+ return parent.as(not null).header_file
end
# Footer template file for `self`.
# Behave like `template_file`.
fun footer_file: String do
if is_root then return wiki.config.footer_file
- return parent.footer_file
+ return parent.as(not null).footer_file
end
# Menu template file for `self`.
# Behave like `template_file`.
fun menu_file: String do
if is_root then return wiki.config.menu_file
- return parent.menu_file
+ return parent.as(not null).menu_file
end
# Display the entry `name`.
redef fun title do
if has_config then
- var title = config.title
+ var title = config.as(not null).title
if title != null then return title
end
return super
#
# Hidden section are rendered but not linked in menus.
fun is_hidden: Bool do
- if has_config then return config.is_hidden
+ if has_config then return config.as(not null).is_hidden
return false
end
if parent == null then
return wiki.config.source_dir
else
- return wiki.expand_path(parent.src_path, name)
+ return wiki.expand_path(parent.as(not null).src_path, name)
end
end
# Also check custom config.
redef fun template_file do
if has_config then
- var tpl = config.template_file
+ var tpl = config.as(not null).template_file
if tpl != null then return tpl
end
if is_root then return wiki.config.template_file
- return parent.template_file
+ return parent.as(not null).template_file
end
# Also check custom config.
redef fun header_file do
if has_config then
- var tpl = config.header_file
+ var tpl = config.as(not null).header_file
if tpl != null then return tpl
end
if is_root then return wiki.config.header_file
- return parent.header_file
+ return parent.as(not null).header_file
end
# Also check custom config.
redef fun footer_file do
if has_config then
- var tpl = config.footer_file
+ var tpl = config.as(not null).footer_file
if tpl != null then return tpl
end
if is_root then return wiki.config.footer_file
- return parent.footer_file
+ return parent.as(not null).footer_file
end
# Also check custom config.
redef fun menu_file do
if has_config then
- var tpl = config.menu_file
+ var tpl = config.as(not null).menu_file
if tpl != null then return tpl
end
if is_root then return wiki.config.menu_file
- return parent.menu_file
+ return parent.as(not null).menu_file
end
end
# Articles can only have `WikiSection` as parents.
redef type PARENT: WikiSection
- redef fun title: String do
+ redef fun title do
+ var parent = self.parent
if name == "index" and parent != null then return parent.title
return super
end
content = md
end
- redef var src_full_path: nullable String = null
+ redef var src_full_path = null
redef fun src_path do
var src_full_path = self.src_full_path
# REQUIRE: `has_source`.
var md: nullable String is lazy do
if not has_source then return null
- var file = new FileReader.open(src_full_path.to_s)
+ var file = new FileReader.open(src_full_path.as(not null))
var md = file.read_all
file.close
return md
redef fun is_dirty do
if super then return true
if has_source then
- return wiki.need_render(src_full_path.to_s, out_full_path)
+ return wiki.need_render(src_full_path.as(not null), out_full_path)
end
return false
end
var sidebar_blocks: Array[String] is lazy do
var res = new Array[String]
if not has_key("wiki.sidebar.blocks") then return res
- for val in at("wiki.sidebar.blocks").values do
+ for val in at("wiki.sidebar.blocks").as(not null).values do
res.add val
end
return res
redef class WikiSection
# Output directory (where to ouput the HTML pages for this section).
- redef fun out_path: String do
+ redef fun out_path do
+ var parent = self.parent
if parent == null then
return wiki.config.out_dir
else
# Copy attached files from `src_path` to `out_path`.
private fun copy_files do
assert has_source
- var dir = src_full_path.to_s
+ var dir = src_full_path.as(not null).to_s
for name in dir.files do
if name == wiki.config_filename then continue
if name.has_suffix(".md") then continue
redef class WikiArticle
- redef fun out_path: String do
+ redef fun out_path do
+ var parent = self.parent
if parent == null then
return wiki.expand_path(wiki.config.out_dir, "{name}.html")
else
# Returns `null` if no article can be found.
fun lookup_entry_by_name(context: WikiEntry, name: String): nullable WikiEntry do
var section: nullable WikiEntry = context.parent or else context
- var res = section.lookup_entry_by_name(name)
+ var res = section.as(not null).lookup_entry_by_name(name)
if res != null then return res
while section != null do
if section.name == name then return section
# Returns `null` if no article can be found.
fun lookup_entry_by_title(context: WikiEntry, title: String): nullable WikiEntry do
var section: nullable WikiEntry = context.parent or else context
- var res = section.lookup_entry_by_title(title)
+ var res = section.as(not null).lookup_entry_by_title(title)
if res != null then return res
while section != null do
if section.title.to_lower == title.to_lower then return section
fun is_index: Bool do return name == "index"
redef fun href do
+ var parent = self.parent
if parent == null then
return "{name}.html"
else
if json isa JsonParseError then
die("Wrong input file ({json.message})")
abort
+ else if json == null then
+ die("Unable to parse input file as json (got null)")
+ abort
else if not json isa JsonObject then
die("Wrong input type (expected JsonObject got {json.class_name})")
abort
return new RefundStats.from_json(content)
end
- redef fun save_stats(stats: RefundStats) do
+ redef fun save_stats(stats) do
write_output(stats.to_json.to_pretty_json, stats_file)
end
end
proc.check_key(json, "reclamations")
var res = new Array[Reclamation]
var recls = json["reclamations"]
- if not recls isa JsonArray then
+ if recls == null then
+ proc.die("Wrong type for `number` (expected JsonArray got null)")
+ abort
+ else if not recls isa JsonArray then
proc.die("Wrong type for `number` (expected JsonArray got {recls.class_name})")
abort
end
var i = 0
for obj in recls do
- if not obj isa JsonObject then
+ if obj == null then
+ proc.die("Wrong type for `reclamations#{i}` (expected JsonObject got null)")
+ abort
+ else if not obj isa JsonObject then
proc.die("Wrong type for `reclamations#{i}` " +
"(expected JsonObject got {obj.class_name})")
abort
init from_json(proc: RefundProcessor, json: JsonObject) do
proc.check_key(json, "dossier")
var id = json["dossier"]
- if not id isa String then
+ if id == null then
+ proc.die("Wrong type for `dossier` (expected String got null)")
+ abort
+ else if not id isa String then
proc.die("Wrong type for `dossier` (expected String got {id.class_name})")
abort
end
init from_json(proc: RefundProcessor, json: JsonObject) do
proc.check_key(json, "mois")
var month = json["mois"]
- if not month isa String then
+ if month == null then
+ proc.die("Wrong type for `mois` (expected String got null)")
+ return
+ else if not month isa String then
proc.die("Wrong type for `mois` (expected String got {month.class_name})")
return
end
init from_json(proc: RefundProcessor, json: JsonObject) do
proc.check_key(json, "date")
var date = json["date"]
- if not date isa String then
+ if date == null then
+ proc.die("Wrong type for `date` (expected String got null)")
+ abort
+ else if not date isa String then
proc.die("Wrong type for `date` (expected String got {date.class_name})")
abort
end
private fun parse_care_id(proc: RefundProcessor, json: JsonObject): Int do
proc.check_key(json, "soin")
var id = json["soin"]
- if not id isa Int then
+ if id == null then
+ proc.die("Wrong type for `soin` (expected Int got null)")
+ abort
+ else if not id isa Int then
proc.die("Wrong type for `soin` (expected Int got {id.class_name})")
abort
end
private fun parse_fees(proc: RefundProcessor, json: JsonObject): Dollar do
proc.check_key(json, "montant")
var fees = json["montant"]
- if not fees isa String then
+ if fees == null then
+ proc.die("Wrong type for `fees` (expected String got null)")
+ abort
+ else if not fees isa String then
proc.die("Wrong type for `fees` (expected String got {fees.class_name})")
abort
end
print n.message
exit 1
end
- n = n.children.first.children.first.as(not null)
+ n = n.children.first.as(not null).children.first.as(not null)
if n isa Nplan then
print "Error: expected a problem, got a plan."
exit 1
assert n isa Nproblem
# Load all locations
- for n2 in n.n_locations.n_list.children do
+ for n2 in n.n_locations.n_list.as(not null).children do
var e = new Location(locations.length, n2.n_name.text, n2.n_x.text.to_f, n2.n_y.text.to_f)
assert not locations.has_key(e.name)
locations[e.name] = e
# Load all roads
var nbr = 0
- for n2 in n.n_roads.n_list.children do
+ for n2 in n.n_roads.n_list.as(not null).children do
var o = locations.get_or_null(n2.n_orig.text)
var d = locations.get_or_null(n2.n_dest.text)
assert o != null and d != null
# Load the robot
var robot = null
- for n2 in n.n_robots.n_list.children do
+ for n2 in n.n_robots.n_list.as(not null).children do
var name = n2.n_name.text
robot = locations.get_or_null(n2.n_emplacement.text)
assert name == robot_name and robot != null
# Load the parcels
var parcel_locations = new Array[nullable Location]
- for n2 in n.n_parcels.n_list.children do
+ for n2 in n.n_parcels.n_list.as(not null).children do
var name = n2.n_name.text
var e = locations.get_or_null(n2.n_emplacement.text)
assert e != null
print "# {parcels.length} parcels"
# Load the goal of parcels
- for n2 in n.n_goal.n_list.children do
+ for n2 in n.n_goal.n_list.as(not null).children do
var parcel = parcel_by_name.get_or_null(n2.n_name.text)
var e = locations.get_or_null(n2.n_emplacement.text)
assert parcel != null and e != null
print n.message
exit 1
end
- n = n.children.first.children.first.as(not null)
+ n = n.children.first.as(not null).children.first.as(not null)
if n isa Nproblem then
print "Error: expected a plan, got a problem."
exit 1
var res = new Plan(self)
var e = initial_state
var cost = 0.0
- for n2 in n.n_actions.children do
+ for n2 in n.n_actions.as(not null).children do
if n2 isa Naction_load then
var parcel = parcel_by_name.get_or_null(n2.n_parcel.text)
assert parcel != null
* Life-cycle
* User interface
* Persistence
+* Async HTTP requests
* Package metadata
* Compilation and packaging
The features offered by _app.nit_ are common to all platforms, but
may not be available on all devices.
-## Application Life-Cycle
+# Application Life-Cycle
The _app.nit_ application life-cycle is compatible with all target platforms.
It relies on the following sequence of events, represented here by their callback method name:
Other UI elements, from the `ui` submodule, are notified of the same events using a simple depth first visit.
So all UI elements can react separately to live-cycle events.
-## User Interface
+# User Interface
The `app::ui` module defines an abstract API to build a portable graphical application.
The API is composed of interactive `Control`s, visible `View`s and an active `Window`.
* Add an observer to a `Button` instance, and implement `on_event` in the observer.
-### Usage Example
+## Usage Example
The calculator example (at `../../examples/calculator/src/calculator.nit`) is a concrete,
simple and complete use of the _app.nit_ portable UI.
-### Platform-specific UI
+## Platform-specific UI
You can go beyond the portable UI API of _app.nit_ by using the natives services of a platform.
The suggested approach is to use platform specific modules to customize the application on a precise platform.
-This module redefine `Window::on_start` to call the native language of the platform and setup a native UI.
+See the calculator example for an adaptation of the UI on Android,
+the interesting module is in this repository at ../../examples/calculator/src/android_calculator.nit
-_TODO complete description and add concrete examples_
-
-## Persistent State with data\_store
+# Persistent State with data\_store
_app.nit_ offers the submodule `app::data_store` to easily save the application state and user preferences.
The service is accessible by the method `App::data_store`. The `DataStore` itself defines 2 methods:
* `DataStore::[]` returns the object associated to a `String` key.
It returns `null` if nothing is associated to the key.
-### Usage Example
+## Usage Example
~~~
import app::data_store
end
~~~
-## Metadata annotations
+# Async HTTP request
+
+The module `app::http_request` provides services to execute asynchronous HTTP request.
+The class `AsyncHttpRequest` hides the complex parallel logic and
+lets the user implement methods acting only on the UI thread.
+See the documentation of `AsyncHttpRequest` for more information.
+
+# Metadata annotations
The _app.nit_ framework defines three annotations to customize the application package.
The special function `git_revision` will use the prefix of the hash of the latest git commit.
By default, the version is 0.1.
-### Usage Example
+## Usage Example
~~~
module my_module is
end
~~~
-## Compiling and Packaging an Application
+# Compiling and Packaging an Application
The Nit compiler detects the target platform from the importations and generates the appropriate application format and package.
Applications using only the portable services of _app.nit_ require some special care at compilation.
Such an application, let's say `calculator.nit`, does not depend on a specific platform and use the portable UI.
-The target platform must be specifed to the compiler for it to produce the correct application package.
+The target platform must be specified to the compiler for it to produce the correct application package.
There is two main ways to achieve this goal:
-* The the mixin option (`-m path`) loads an additionnal module before compiling.
+* The mixin option (`-m module`) imports an additional module before compiling.
It can be used to load platform specific implementations of the _app.nit_ portable UI.
~~~
# GNU/Linux version, using GTK
- nitc calculator.nit -m NIT_DIR/lib/linux/ui.nit
+ nitc calculator.nit -m linux
# Android version
- nitc calculator.nit -m NIT_DIR/lib/android/ui/
+ nitc calculator.nit -m android
+
+ # iOS version
+ nitc calculator.nit -m ios
~~~
* A common alternative for larger projects is to use platform specific modules.
- Continuing with the `calculator.nit` example, it can be accompagnied by the module `calculator_linux.nit`.
- This module imports both `calculator` and `linux::ui`, and can also use other GNU/Linux specific code.
+ Continuing with the calculator example, it is adapted for Android by the module `android_calculator.nit`.
+ This module imports both `calculator` and `android`, it can then use Android specific code.
~~~
- module calculator_linux
+ module android_calculator
import calculator
- import linux::ui
+ import android
+
+ # ...
~~~
fun run_on_ui_thread(task: Task) is abstract
end
-# Thread executing an HTTP request and deserializing JSON asynchronously
+# Thread executing an HTTP request asynchronously
#
-# This class defines four methods acting on the main/UI thread,
-# they should be implemented as needed:
-# * before
-# * on_load
-# * on_fail
-# * after
+# The request is sent to `rest_server_uri / rest_action`.
+#
+# If `deserialize_json`, the default behavior, the response is deserialized from JSON
+#
+# If `delay > 0.0`, sending the reqest is delayed by the given `delay` in seconds.
+# It can be used to delay resending a request on error.
+#
+# Four callback methods act on the main/UI thread,
+# they should be implemented as needed in subclasses:
+# * `before`
+# * `on_load`
+# * `on_fail`
+# * `after`
class AsyncHttpRequest
super Thread
[package]
name=app
-tags=lib
+tags=lib,mobile
maintainer=Alexis Laferrière <alexis.laf@xymus.net>
license=Apache-2.0
[upstream]
# Letters that follow a letter are lowercased
# Letters that follow a non-letter are upcased.
#
+ # If `keep_upper = true`, already uppercase letters are not lowercased.
+ #
# SEE : `Char::is_letter` for the definition of letter.
#
# assert "jAVASCRIPT".capitalized == "Javascript"
# assert "i am root".capitalized == "I Am Root"
# assert "ab_c -ab0c ab\nc".capitalized == "Ab_C -Ab0C Ab\nC"
- fun capitalized: SELFTYPE do
+ # assert "preserve my ACRONYMS".capitalized(keep_upper=true) == "Preserve My ACRONYMS"
+ fun capitalized(keep_upper: nullable Bool): SELFTYPE do
if length == 0 then return self
var buf = new Buffer.with_cap(length)
-
- var curr = chars[0].to_upper
- var prev = curr
- buf[0] = curr
-
- for i in [1 .. length[ do
- prev = curr
- curr = self[i]
- if prev.is_letter then
- buf[i] = curr.to_lower
- else
- buf[i] = curr.to_upper
- end
- end
-
+ buf.capitalize(keep_upper=keep_upper, src=self)
return buf.to_s
end
end
# Letters that follow a letter are lowercased
# Letters that follow a non-letter are upcased.
#
+ # If `keep_upper = true`, uppercase letters are not lowercased.
+ #
+ # When `src` is specified, this method reads from `src` instead of `self`
+ # but it still writes the result to the beginning of `self`.
+ # This requires `self` to have the capacity to receive all of the
+ # capitalized content of `src`.
+ #
# SEE: `Char::is_letter` for the definition of a letter.
#
# var b = new FlatBuffer.from("jAVAsCriPt")
# b = new FlatBuffer.from("ab_c -ab0c ab\nc")
# b.capitalize
# assert b == "Ab_C -Ab0C Ab\nC"
- fun capitalize do
+ #
+ # b = new FlatBuffer.from("12345")
+ # b.capitalize(src="foo")
+ # assert b == "Foo45"
+ #
+ # b = new FlatBuffer.from("preserve my ACRONYMS")
+ # b.capitalize(keep_upper=true)
+ # assert b == "Preserve My ACRONYMS"
+ fun capitalize(keep_upper: nullable Bool, src: nullable Text) do
+ src = src or else self
+ var length = src.length
if length == 0 then return
- var c = self[0].to_upper
+ keep_upper = keep_upper or else false
+
+ var c = src[0].to_upper
self[0] = c
var prev = c
for i in [1 .. length[ do
prev = c
- c = self[i]
+ c = src[i]
if prev.is_letter then
- self[i] = c.to_lower
+ if keep_upper then
+ self[i] = c
+ else
+ self[i] = c.to_lower
+ end
else
self[i] = c.to_upper
end
### `--no-shortcut-equal`
Always call == in a polymorphic way.
-### `--no-tag-primitive`
+### `--no-tag-primitives`
Use only boxes for primitive types.
The separate compiler uses tagged values to encode common primitive types like Int, Bool and Char.
Just add the trampolines of `--substitute-monomorph` without doing any additionnal optimizations.
-### `--no-tag-primitives`
-Use only boxes for primitive types.
-
## INTERNAL OPTIONS
These options can be used to control the fine behavior of the tool.
add_cast(paramtype)
end
+ if mmethoddef.is_abstract then continue
+
var npropdef = modelbuilder.mpropdef2node(mmethoddef)
if npropdef isa AClassdef then