Just to see what I broke...
Pull-Request: #2246
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
--- /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.
+
+# Example that uses `shibuqam` to authenticate users and count the number of time they reload.
+module reloadgame
+
+import popcorn
+import counter
+import shibuqam
+
+redef class User
+ # How many reload?
+ var seen = 0
+end
+
+# Ugly global class to track the knowledge.
+class DB
+ # All known users
+ var users = new HashMap[String, User]
+end
+# Ugly global instance to track the knowledge.
+fun db: DB do return once new DB
+
+redef class HttpRequest
+ # Like `user` but reuse an user if already seen
+ var reuser: nullable User is lazy do
+ var user = self.user
+ if user == null then return null
+
+ var saved = db.users.get_or_null(user.id)
+ if saved != null then return saved
+
+ db.users[user.id] = user
+ return user
+ end
+end
+
+# The only handler of the example.
+class ReloadGame
+ super Handler
+
+ redef fun get(http_request, response)
+ do
+ var body = """
+ <!DOCTYPE html>
+ <head>
+ <meta charset="utf-8">
+ <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
+ <title>Nitcorn on Shibboleth/UQAM</title>
+ </head>
+ <body>
+ <div class="container">
+ <h1>Nitcorn on Shibboleth/UQAM</h1>
+ """
+
+ var user = http_request.user
+
+ if user != null then
+ user.seen += 1
+
+ body += """
+ <p>Welcome {{{user.given_name}}}</p>
+ <ul>
+ <li>Full Name: {{{user.display_name}}}</li>
+ <li>E-Mail: {{{user.email}}}</li>
+ <li>Id: {{{user.id}}}</li>
+ <li>Score: {{{user.seen}}}</li>
+ </ul>
+ """
+
+ #for k, v in http_request.header do body += "<li>{k}: {v}</li>"
+ else
+ # The login page, at the location the reverse proxy is expected to be configured
+ # to force an authentication.
+ var login = "/securep/login"
+ body += """
+ <p>Welcome annonymous, please <a href="{{{login}}}">log in</a>.</p>
+ """
+ end
+
+ var score = new Counter[User]
+ for u in db.users.values do
+ score[u] = u.seen
+ end
+
+ body += "<h2>Scoreboard</h2><ul>"
+ for u in score.sort.reversed do
+ body += "<li><img src='{u.avatar}'> {u.display_name}: {u.seen}</li>"
+ end
+
+
+ body += """</ul>
+ </div>
+ </body>
+ </html>
+ """
+
+ response.html body
+ end
+end
+
+var app = new App
+app.use("/*", new ReloadGame)
+app.listen("localhost", 3000)
--- /dev/null
+[package]
+name=shibuqam
+tags=web
+maintainer=Jean Privat <jean@pryen.org>
+license=Apache-2.0
+[upstream]
+browse=https://github.com/nitlang/nit/tree/master/contrib/shibuqam/
+git=https://github.com/nitlang/nit.git
+git.directory=contrib/shibuqam/
+homepage=http://nitlanguage.org
+issues=https://github.com/nitlang/nit/issues
--- /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.
+
+# Gather the authenticated users on UQAM websites.
+#
+# The main method to use is `HttpRequest::user` and it extracts the information
+# of the authenticated user from the request header.
+# The real authentication must be done by a mandatory reverse proxy server.
+module shibuqam
+
+import nitcorn
+private import md5
+
+# Information on a user from Shibboleth/UQAM
+class User
+ # The *code permanent* (or the uid for non student)
+ var id: String
+
+ # Usually the first name
+ var given_name: String
+
+ # Usually "FamilyName, FirstName"
+ var display_name: String
+
+ # The email @courrier.uqam.ca (or @uqam.ca for non student)
+ var email: String
+
+ # The Gravatar URL (based on `email`)
+ var avatar: String is lazy do
+ var md5 = email.md5
+ return "https://www.gravatar.com/avatar/{md5}?d=retro"
+ end
+end
+
+redef class HttpRequest
+ # Extract the Shibboleth/UQAM information from the header, if any.
+ #
+ # We assume that a reverse proxy does the authentication and fill the request header.
+ # If the server is accessible directly, these headers can be easily forged.
+ # Thus, we assume that the reverse proxy is not by-passable.
+ #
+ # The reverse proxy might choose to force an authentication or not.
+ # If there is no authentication, there is no information in the request header (or with the `(null)` value).
+ # In this case, `null` is returned by this function.
+ fun user: nullable User do
+ var user = header.get_or_null("Remote-User")
+ if user == null or user == "(null)" then return null
+
+ var display_name = header.get_or_null("User-Display-Name")
+ var given_name = header.get_or_null("User-Given-Name")
+ var email = header.get_or_null("User-Mail")
+
+ if display_name == null or given_name == null or email == null then return null
+
+ var res = new User(user, given_name, display_name, email)
+ return res
+ end
+end
# ---
# Android
+#
+# There are 4 versions, combining 2 variations:
+# * scientific vs non-scientific
+# * android API 21+ vs under 21
-android: bin/calculator.apk bin/scientific.apk
+android: bin/calculator14.apk bin/scientific14.apk bin/calculator21.apk bin/scientific21.apk
-bin/calculator.apk: $(shell ${NITLS} -M src/android_calculator.nit) ${NITC} android/res/drawable-hdpi/icon.png
+bin/calculator14.apk: $(shell ${NITLS} -M src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o $@ src/android_calculator.nit -D debug
+ ${NITC} -o $@ src/android14.nit -D debug
-bin/scientific.apk: $(shell ${NITLS} -M src/scientific src/android_calculator.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+bin/calculator21.apk: $(shell ${NITLS} -M src/android21) ${NITC} android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o $@ src/scientific -m src/android_calculator.nit -D debug
+ ${NITC} -o $@ src/android21 -D debug
-android-release: $(shell ${NITLS} -M src/scientific src/android_calculator.nit) ${NITC} android/res/drawable-hdpi/icon.png
+bin/scientific14.apk: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
mkdir -p bin
- ${NITC} -o bin/calculator.apk src/scientific -m src/android_calculator.nit --release
+ ${NITC} -o $@ src/scientific -m src/android14.nit -D debug
+
+bin/scientific21.apk: $(shell ${NITLS} -M src/scientific src/android21) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+ mkdir -p bin
+ ${NITC} -o $@ src/scientific -m src/android21 -D debug
+
+android-release: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
+ mkdir -p bin
+ ${NITC} -o bin/calculator14.apk src/android14.nit --release
+ ${NITC} -o bin/calculator21.apk src/android21 --release
+ ${NITC} -o bin/scientific14.apk src/scientific -m src/android14.nit --release
+ ${NITC} -o bin/scientific21.apk src/scientific -m src/android21 --release
android/res/drawable-hdpi/icon.png: art/icon.svg ../../contrib/inkscape_tools/bin/svg_to_icons
mkdir -p android/res
../../contrib/inkscape_tools/bin/svg_to_icons art/icon.svg --android --out android/res/
-src/scientific/android/res/drawable-hdpi/icon.png: art/icon_sci.svg ../../contrib/inkscape_tools/bin/svg_to_icons
+src/scientific/android/res/drawable-hdpi/icon.png: art/icon-sci.svg ../../contrib/inkscape_tools/bin/svg_to_icons
mkdir -p src/scientific/android/res
../../contrib/inkscape_tools/bin/svg_to_icons art/icon-sci.svg --android --out src/scientific/android/res/
../../contrib/inkscape_tools/bin/svg_to_icons:
make -C ../../contrib/inkscape_tools/
-android-install: bin/calculator.apk
- adb install -r bin/calculator.apk
+android-install: bin/calculator14.apk
+ adb install -r bin/calculator14.apk
# ---
# iOS
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ 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.
+ -->
+
+<resources>
+
+ <!-- Default background color for the status bar. -->
+ <color name="calculator_accent_color">#00BCD4</color>
+
+ <!-- Color to indicate an error has occured. -->
+ <color name="calculator_error_color">#F40056</color>
+
+ <!-- Background color of the calculator display. -->
+ <color name="display_background_color">#FFF</color>
+
+ <!-- Text color for the formula in the calculator display. -->
+ <color name="display_formula_text_color">#8A000000</color>
+
+ <!-- Text color for the result in the calculator display. -->
+ <color name="display_result_text_color">#6C000000</color>
+
+ <!-- Background color for the numeric pad. -->
+ <color name="pad_numeric_background_color">#434343</color>
+
+ <!-- Background color for the operator pad. -->
+ <color name="pad_operator_background_color">#636363</color>
+
+ <!-- Background color for the advanced pad. -->
+ <color name="pad_advanced_background_color">#1DE9B6</color>
+
+ <!-- Text color for a button in a pad. -->
+ <color name="pad_button_text_color">#FFF</color>
+
+ <!-- Text color for a button in the advanced pad. -->
+ <color name="pad_button_advanced_text_color">#91000000</color>
+
+ <!-- Ripple color when a button is pressed in a pad. -->
+ <color name="pad_button_ripple_color">#33FFFFFF</color>
+
+ <!-- Ripple color when a button is pressed in a pad. -->
+ <color name="pad_button_advanced_ripple_color">#1A000000</color>
+
+</resources>
--- /dev/null
+Categories:Nit
+License:Apache2
+Web Site:http://nitlanguage.org
+Source Code:http://nitlanguage.org/nit.git/tree/HEAD:/examples/calculator
+Issue Tracker:https://github.com/nitlang/nit/issues
+
+Summary:A Scientific Calculator
+Description:
+10 digits, 16 operations, hours of fun.
+.
git.directory=examples/calculator/
homepage=http://nitlanguage.org
issues=https://github.com/nitlang/nit/issues
-apk=http://nitlanguage.org/fdroid/apk/calculator.apk
+apk=http://nitlanguage.org/fdroid/apk/calculator21.apk
--- /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.
+
+# Aesthetic adaptations for Android for API 14+
+module android14
+
+import calculator
+import android
+
+redef class Button
+ init do set_android_style(native, app.native_activity,
+ (text or else "?").is_int,
+ ["+","-","×","C","÷","=","."].has(text))
+
+ # Set color and text style
+ private fun set_android_style(java_button: NativeButton, activity: NativeActivity,
+ is_number: Bool, is_basic_op: Bool)
+ in "Java" `{
+ // Set color
+ int back_color_id = 0;
+ if (is_number)
+ back_color_id = R.color.pad_numeric_background_color;
+ else if (is_basic_op)
+ back_color_id = R.color.pad_operator_background_color;
+ else {
+ back_color_id = R.color.pad_advanced_background_color;
+
+ int text_color = activity.getResources().getColor(R.color.pad_button_advanced_text_color);
+ java_button.setTextColor(text_color);
+ }
+ java_button.setBackgroundResource(back_color_id);
+
+ // Center label, use lowercase and make text bigger
+ java_button.setGravity(android.view.Gravity.CENTER);
+ java_button.setAllCaps(false);
+ java_button.setTextSize(android.util.TypedValue.COMPLEX_UNIT_FRACTION, 100.0f);
+ `}
+end
+
+redef class TextInput
+ init do set_android_style(native, app.native_activity)
+
+ # Set text style and hide cursor
+ private fun set_android_style(java_edit_text: NativeEditText, activity: NativeActivity)
+ in "Java" `{
+ java_edit_text.setBackgroundResource(R.color.display_background_color);
+ java_edit_text.setTextColor(
+ activity.getResources().getColor(R.color.display_formula_text_color));
+ java_edit_text.setTextSize(android.util.TypedValue.COMPLEX_UNIT_FRACTION, 120.0f);
+ java_edit_text.setCursorVisible(false);
+ java_edit_text.setGravity(android.view.Gravity.CENTER_VERTICAL | android.view.Gravity.END);
+ `}
+end
--- /dev/null
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ 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.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <style name="CalculatorTheme" parent="@android:style/Theme.Material.Light.NoActionBar">
+ <item name="android:colorPrimary">@color/calculator_accent_color</item>
+ <item name="android:navigationBarColor">@color/calculator_accent_color</item>
+ <item name="android:statusBarColor">@color/calculator_accent_color</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
+ <style name="DisplayEditTextStyle" parent="@android:style/Widget.Material.Light.EditText">
+ <item name="android:background">@android:color/transparent</item>
+ <item name="android:cursorVisible">false</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:gravity">bottom|end</item>
+ </style>
+
+ <style name="PadButtonStyle" parent="@android:style/Widget.Material.Light.Button.Borderless">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:gravity">center</item>
+ <item name="android:includeFontPadding">false</item>
+ <item name="android:minWidth">0dip</item>
+ <item name="android:minHeight">0dip</item>
+ <item name="android:textAllCaps">false</item>
+ <item name="android:textColor">@color/pad_button_text_color</item>
+ </style>
+
+ <style name="PadLayoutStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">match_parent</item>
+ </style>
+</resources>
--- /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.
+
+# Aesthetic adaptations for Android Lollypop (API 21)
+module android21 is
+ android_api_min 21
+ android_api_target 21
+ android_manifest_activity """android:theme="@style/CalculatorTheme" """
+ app_files
+end
+
+import android14
+
+redef class TextInput
+ init do
+ set_android_style(native, app.native_activity)
+ super
+ end
+
+ # Deactivate the virtual keyboard and set the text style from XML resources
+ private fun set_android_style(java_edit_text: NativeEditText, activity: NativeActivity)
+ in "Java" `{
+ java_edit_text.setShowSoftInputOnFocus(false);
+ java_edit_text.setTextAppearance(activity, R.style.DisplayEditTextStyle);
+ `}
+end
+
+redef class Button
+ init do
+ set_text_style(native, app.native_activity)
+ super
+ end
+
+ # Set the text style from XML resources
+ private fun set_text_style(java_button: NativeButton, activity: NativeActivity)
+ in "Java" `{
+ java_button.setTextAppearance(activity, R.style.PadButtonStyle);
+ `}
+end
+++ /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.
-
-# Aesthetic adaptations for Android
-module android_calculator
-
-import calculator
-import android
-
-redef class Button
- init do set_android_style(native, (text or else "?").is_int)
-
- private fun set_android_style(java_button: NativeButton, is_number: Bool)
- in "Java" `{
- // Flatten the background and use a different color for digit buttons
- int color = is_number? android.graphics.Color.DKGRAY: android.graphics.Color.TRANSPARENT;
- java_button.setBackgroundColor(color);
-
- // Center the label on both horizontal and vertical axes
- java_button.setGravity(android.view.Gravity.CENTER);
-
- // Set lowercase text to correctly display constants like e and π
- java_button.setAllCaps(false);
- `}
-end
return res
end
+ # Correctly join `self` with `subpath` using the directory separator.
+ #
+ # Using a standard "{self}/{path}" does not work in the following cases:
+ #
+ # * `self` is empty.
+ # * `path` starts with `'/'`.
+ #
+ # This method ensures that the join is valid.
+ #
+ # var hello = "hello".to_path
+ # assert (hello/"world").to_s == "hello/world"
+ # assert ("hel/lo".to_path / "wor/ld").to_s == "hel/lo/wor/ld"
+ # assert ("".to_path / "world").to_s == "world"
+ # assert (hello / "/world").to_s == "/world"
+ # assert ("hello/".to_path / "world").to_s == "hello/world"
+ fun /(subpath: String): Path do return new Path(path / subpath)
# Lists the files contained within the directory at `path`.
#
end
var name = de.to_s_with_copy
if name == "." or name == ".." then continue
- res.add new Path(path / name)
+ res.add self / name
end
d.closedir
# Depth-first path in the serialized object tree.
private var path = new Array[Map[String, nullable Object]]
+ # Names of the attributes from the root to the object currently being deserialized
+ var attributes_path = new Array[String]
+
# Last encountered object reference id.
#
# See `id_to_object`.
var value = current[name]
- return convert_object(value)
+ attributes_path.add name
+ var res = convert_object(value)
+ attributes_path.pop
+ return res
end
# This may be called multiple times by the same object from constructors
return convert_object(root)
end
- # User customizable heuristic to get the name of the Nit class to deserialize `json_object`
+ # User customizable heuristic to infer the name of the Nit class to deserialize `json_object`
#
# This method is called only when deserializing an object without the metadata `__class`.
- # Return the class name as a `String` when it can be inferred.
- # Return `null` when the class name cannot be found.
+ # Use the content of `json_object` to identify what Nit class it should be deserialized into.
+ # Or use `self.attributes_path` indicating where the deserialized object will be stored,
+ # is is less reliable as some objects don't have an associated attribute:
+ # the root/first deserialized object and collection elements.
+ #
+ # Return the class name as a `String` when it can be inferred,
+ # or `null` when the class name cannot be found.
#
# If a valid class name is returned, `json_object` will then be deserialized normally.
# So it must contain the attributes of the corresponding class, as usual.
# serialize
#
# var error: String
+ # var related_data: MyData
# end
#
# class MyJsonDeserializer
#
# redef fun class_name_heuristic(json_object)
# do
+ # # Infer the Nit class from the content of the JSON object.
# if json_object.keys.has("error") then return "MyError"
# if json_object.keys.has("data") then return "MyData"
+ #
+ # # Infer the Nit class from the attribute where it will be stored.
+ # # This line duplicates a previous line, and would only apply when
+ # # `MyData` is within a `MyError`.
+ # if attributes_path.not_empty and attributes_path.last == "related_data" then return "MyData"
+ #
# return null
# end
# end
#
- # var json = """{"data": "some other data"}"""
+ # var json = """{"data": "some data"}"""
# var deserializer = new MyJsonDeserializer(json)
# var deserialized = deserializer.deserialize
# assert deserialized isa MyData
#
- # json = """{"error": "some error message"}"""
+ # json = """{"error": "some error message",
+ # "related_data": {"data": "some other data"}"""
# deserializer = new MyJsonDeserializer(json)
# deserialized = deserializer.deserialize
# assert deserialized isa MyError
# Basic usage example:
# ~~~~
# class MyAction
-# super Action
-#
-# redef fun answer(http_request, turi)
-# do
-# var response = new HttpResponse(200)
-# response.body = """
-# <!DOCTYPE html>
-# <head>
-# <meta charset="utf-8">
-# <title>Hello World</title>
-# </head>
-# <body>
-# <p>Hello World</p>
-# </body>
-# </html>"""
-# return response
-# end
+# super Action
+#
+# redef fun answer(http_request, turi)
+# do
+# var response = new HttpResponse(200)
+# response.body = """
+# <!DOCTYPE html>
+# <head>
+# <meta charset="utf-8">
+# <title>Hello World</title>
+# </head>
+# <body>
+# <p>Hello World</p>
+# </body>
+# </html>"""
+# return response
+# end
# end
#
-# var vh = new VirtualHost("localhost:80")
+# # Listen to port 8080 on all interfaces
+# var vh = new VirtualHost("0.0.0.0:8080")
#
# # Serve index.html with our custom handler
# vh.routes.add new Route("/index.html", new MyAction)
redef fun add(e)
do
super
- var config = vh.server_config
- if config != null then sys.listen_on(e, config.factory)
+ var config = virtual_host.server_config
+ if config != null then register_and_listen(e, config)
+ end
+
+ # Indirection to `listen_on` and check if this targets all addresses
+ private fun register_and_listen(e: Interface, config: ServerConfig)
+ do
+ listen_on(e, config.factory)
+ if e.name == "0.0.0.0" or e.name == "::0" then config.default_virtual_host = virtual_host
end
# TODO remove
redef fun add(e)
do
super
- for i in e.interfaces do sys.listen_on(i, config.factory)
+ for i in e.interfaces do e.interfaces.register_and_listen(i, config)
end
# TODO remove
var virtual_hosts = new VirtualHosts(self)
# Default `VirtualHost` to respond to requests not handled by any of the `virtual_hosts`
- var default_virtual_host: nullable VirtualHost = null
+ var default_virtual_host: nullable VirtualHost = null is writable
end
# A `VirtualHost` configuration
super Array[Interface]
# Back reference to the associtated `VirtualHost`
- var vh: VirtualHost
+ var virtual_host: VirtualHost
# Add an `Interface` described by `text` formatted as `interface.name.com:port`
fun add_from_string(text: String)
# Abstract deserialization service
#
-# After initialization of one of its sub-classes, call `deserialize`
+# The main service is `deserialize`.
abstract class Deserializer
- # Main method of this class, returns a Nit object
+ # Deserialize and return an object, storing errors in the attribute `errors`
+ #
+ # This method behavior varies according to the implementation engines.
fun deserialize: nullable Object is abstract
- # Internal method to be implemented by sub-classes
+ # Deserialize the attribute with `name` from the object open for deserialization
+ #
+ # Internal method to be implemented by the engines.
fun deserialize_attribute(name: String): nullable Object is abstract
- # Internal method called by objects in creation,
- # to be implemented by sub-classes
+ # Register a newly allocated object (even if not completely built)
+ #
+ # Internal method called by objects in creation, to be implemented by the engines.
fun notify_of_creation(new_object: Object) is abstract
# Deserialize the next available object as an instance of `class_name`
#
- # Returns the deserialized object on success, aborts on error.
+ # Return the deserialized object on success and
+ # record in `errors` if `class_name` is unknown.
#
# This method should be redefined for each custom subclass of `Serializable`.
# All refinement should look for a precise `class_name` and call super
# Runs a webserver based on nitcorn that render things from model.
module nitweb
+import popcorn::pop_config
import frontend
import web
redef class ToolContext
- # Host name to bind on.
+ # Path to app config file.
+ var opt_config = new OptionString("Path to app config file", "--config")
+
+ # Host name to bind on (will overwrite the config one).
var opt_host = new OptionString("Host to bind the server on", "--host")
- # Port number to bind on.
- var opt_port = new OptionInt("Port number to use", 3000, "--port")
+ # Port number to bind on (will overwrite the config one).
+ var opt_port = new OptionInt("Port number to use", -1, "--port")
# Web rendering phase.
var webphase: Phase = new NitwebPhase(self, null)
init do
super
- option_context.add_option(opt_host, opt_port)
+ option_context.add_option(opt_config, opt_host, opt_port)
end
end
# Phase that builds the model and wait for http request to serve pages.
private class NitwebPhase
super Phase
- redef fun process_mainmodule(mainmodule, mmodules)
- do
- var model = mainmodule.model
- var modelbuilder = toolcontext.modelbuilder
- # Build catalog
+ # Build the nitweb config from `toolcontext` options.
+ fun build_config(toolcontext: ToolContext, mainmodule: MModule): NitwebConfig do
+ var config_file = toolcontext.opt_config.value
+ if config_file == null then config_file = "nitweb.ini"
+ var config = new NitwebConfig(
+ config_file,
+ toolcontext.modelbuilder.model,
+ mainmodule,
+ toolcontext.modelbuilder)
+ var opt_host = toolcontext.opt_host.value
+ if opt_host != null then config["app.host"] = opt_host
+ var opt_port = toolcontext.opt_port.value
+ if opt_port >= 0 then config["app.port"] = opt_port.to_s
+ return config
+ end
+
+ # Build the nit catalog used in homepage.
+ fun build_catalog(model: Model, modelbuilder: ModelBuilder): Catalog do
var catalog = new Catalog(modelbuilder)
for mpackage in model.mpackages do
catalog.deps.add_node(mpackage)
catalog.git_info(mpackage)
catalog.package_page(mpackage)
end
+ return catalog
+ end
- # Prepare mongo connection
- var mongo = new MongoClient("mongodb://localhost:27017/")
- var db = mongo.database("nitweb")
- var collection = db.collection("stars")
-
- # Run the server
- var host = toolcontext.opt_host.value or else "localhost"
- var port = toolcontext.opt_port.value
+ redef fun process_mainmodule(mainmodule, mmodules)
+ do
+ var model = mainmodule.model
+ var modelbuilder = toolcontext.modelbuilder
+ var config = build_config(toolcontext, mainmodule)
+ var catalog = build_catalog(model, modelbuilder)
var app = new App
app.use_before("/*", new RequestClock)
- app.use("/api", new APIRouter(model, modelbuilder, mainmodule, catalog, collection))
+ app.use("/api", new NitwebAPIRouter(config, catalog))
app.use("/*", new StaticHandler(toolcontext.share_dir / "nitweb", "index.html"))
app.use_after("/*", new ConsoleLog)
- app.listen(host, port.to_i)
+ app.listen(config.app_host, config.app_port)
end
end
# Group all api handlers in one router.
-class APIRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # ModelBuilder to pass to handlers.
- var modelbuilder: ModelBuilder
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+class NitwebAPIRouter
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
- # Mongo collection used to store ratings.
- var collection: MongoCollection
-
init do
- use("/catalog", new APICatalogRouter(model, mainmodule, catalog))
- use("/list", new APIList(model, mainmodule))
- use("/search", new APISearch(model, mainmodule))
- use("/random", new APIRandom(model, mainmodule))
- use("/entity/:id", new APIEntity(model, mainmodule))
- use("/code/:id", new APIEntityCode(model, mainmodule, modelbuilder))
- use("/uml/:id", new APIEntityUML(model, mainmodule))
- use("/linearization/:id", new APIEntityLinearization(model, mainmodule))
- use("/defs/:id", new APIEntityDefs(model, mainmodule))
- use("/feedback/", new APIFeedbackRouter(model, mainmodule, collection))
- use("/inheritance/:id", new APIEntityInheritance(model, mainmodule))
- use("/graph/", new APIGraphRouter(model, mainmodule))
- use("/docdown/", new APIDocdown(model, mainmodule, modelbuilder))
- use("/metrics/", new APIMetricsRouter(model, mainmodule))
+ use("/catalog", new APICatalogRouter(config, catalog))
+ use("/list", new APIList(config))
+ use("/search", new APISearch(config))
+ use("/random", new APIRandom(config))
+ use("/entity/:id", new APIEntity(config))
+ use("/code/:id", new APIEntityCode(config))
+ use("/uml/:id", new APIEntityUML(config))
+ use("/linearization/:id", new APIEntityLinearization(config))
+ use("/defs/:id", new APIEntityDefs(config))
+ use("/feedback/", new APIFeedbackRouter(config))
+ use("/inheritance/:id", new APIEntityInheritance(config))
+ use("/graph/", new APIGraphRouter(config))
+ use("/docdown/", new APIDocdown(config))
+ use("/metrics/", new APIMetricsRouter(config))
end
end
var toolcontext = new ToolContext
var tpl = new Template
tpl.add "Usage: nitweb [OPTION]... <file.nit>...\n"
-tpl.add "Run a webserver based on nitcorn that serve pages about model."
+tpl.add "Run a webserver based on nitcorn that serves pages about model."
toolcontext.tooldescription = tpl.write_to_string
# process options
# Group all api handlers in one router.
class APICatalogRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
# Catalog to pass to handlers.
var catalog: Catalog
init do
- use("/highlighted", new APICatalogHighLighted(model, mainmodule, catalog))
- use("/required", new APICatalogMostRequired(model, mainmodule, catalog))
- use("/bytags", new APICatalogByTags(model, mainmodule, catalog))
- use("/contributors", new APICatalogContributors(model, mainmodule, catalog))
- use("/stats", new APICatalogStats(model, mainmodule, catalog))
+ use("/highlighted", new APICatalogHighLighted(config, catalog))
+ use("/required", new APICatalogMostRequired(config, catalog))
+ use("/bytags", new APICatalogByTags(config, catalog))
+ use("/contributors", new APICatalogContributors(config, catalog))
+ use("/stats", new APICatalogStats(config, catalog))
end
end
redef fun get(req, res) do
var obj = new JsonObject
- obj["packages"] = model.mpackages.length
+ obj["packages"] = config.model.mpackages.length
obj["maintainers"] = catalog.maint2proj.length
obj["contributors"] = catalog.contrib2proj.length
obj["modules"] = catalog.mmodules.sum
redef fun get(req, res) do
if catalog.deps.not_empty then
var reqs = new Counter[MPackage]
- for p in model.mpackages do
+ for p in config.model.mpackages do
reqs[p] = catalog.deps[p].smallers.length - 1
end
res.json list_best(reqs)
class APIDocdown
super APIHandler
- # Modelbuilder used by the commands
- var modelbuilder: ModelBuilder
-
# Specific Markdown processor to use within Nitweb
var md_processor: MarkdownProcessor is lazy do
var proc = new MarkdownProcessor
- proc.emitter.decorator = new NitwebDecorator(view, modelbuilder)
+ proc.emitter.decorator = new NitwebDecorator(view, config.modelbuilder)
return proc
end
import web_base
import mongodb
-# Group all api handlers in one router
-class APIFeedbackRouter
- super Router
+redef class NitwebConfig
+
+ # MongoDB uri used for data persistence.
+ #
+ # * key: `mongo.uri`
+ # * default: `mongodb://localhost:27017/`
+ var mongo_uri: String is lazy do
+ return value_or_default("mongo.uri", "mongodb://localhost:27017/")
+ end
- # Model to pass to handlers
- var model: Model
+ # MongoDB DB used for data persistence.
+ #
+ # * key: `mongo.db`
+ # * default: `nitweb`
+ var mongo_db: String is lazy do return value_or_default("mongo.db", "nitweb")
- # Mainmodule to pass to handlers
- var mainmodule: MModule
+ # Mongo instance
+ var mongo: MongoClient is lazy do return new MongoClient(mongo_uri)
- # Mongo collection used to store ratings
- var collection: MongoCollection
+ # Database instance
+ var db: MongoDb is lazy do return mongo.database(mongo_db)
+
+ # MongoDB collection used to store stars.
+ var stars: MongoCollection is lazy do return db.collection("stars")
+end
+
+# Group all api handlers in one router
+class APIFeedbackRouter
+ super APIRouter
init do
- use("/stars/:id", new APIStars(model, mainmodule, collection))
+ use("/stars/:id", new APIStars(config))
end
end
class APIStars
super APIHandler
- # Collection used to store ratings
- var collection: MongoCollection
-
redef fun get(req, res) do
var mentity = mentity_from_uri(req, res)
if mentity == null then
end
var val = new MEntityRating(mentity.full_name, rating, get_time)
- collection.insert(val.json)
+ config.stars.insert(val.json)
res.json mentity_ratings(mentity)
end
var req = new JsonObject
req["mentity"] = mentity.full_name
- var rs = collection.find_all(req)
+ var rs = config.stars.find_all(req)
for r in rs do ratings.ratings.add new MEntityRating.from_json(r)
return ratings
end
# Group all api handlers in one router.
class APIGraphRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/inheritance/:id", new APIInheritanceGraph(model, mainmodule))
+ use("/inheritance/:id", new APIInheritanceGraph(config))
end
end
# Group all api handlers in one router.
class APIMetricsRouter
- super Router
-
- # Model to pass to handlers.
- var model: Model
-
- # Mainmodule to pass to handlers.
- var mainmodule: MModule
+ super APIRouter
init do
- use("/structural/:id", new APIStructuralMetrics(model, mainmodule))
+ use("/structural/:id", new APIStructuralMetrics(config))
end
end
super APIHandler
private fun mclasses_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new CNOA(mainmodule, view))
metrics.register(new CNOP(mainmodule, view))
end
private fun mmodules_metrics: MetricSet do
+ var mainmodule = config.mainmodule
var metrics = new MetricSet
metrics.register(new MNOA(mainmodule, view))
metrics.register(new MNOP(mainmodule, view))
res.error 404
return
end
- var lin = mentity.collect_linearization(mainmodule)
+ var lin = mentity.collect_linearization(config.mainmodule)
if lin == null then
res.error 404
return
var dot
if mentity isa MClassDef then mentity = mentity.mclass
if mentity isa MClass then
- var uml = new UMLModel(view, mainmodule)
+ var uml = new UMLModel(view, config.mainmodule)
dot = uml.generate_class_uml.write_to_string
else if mentity isa MModule then
var uml = new UMLModel(view, mentity)
class APIEntityCode
super APIHandler
- # Modelbuilder used to access sources.
- var modelbuilder: ModelBuilder
-
redef fun get(req, res) do
var mentity = mentity_from_uri(req, res)
if mentity == null then return
# Highlight `mentity` source code.
private fun render_source(mentity: MEntity): nullable HTMLTag do
- var node = modelbuilder.mentity2node(mentity)
+ var node = config.modelbuilder.mentity2node(mentity)
if node == null then return null
var hl = new HighlightVisitor
hl.enter_visit node
import model::model_json
import doc_down
import popcorn
+import popcorn::pop_config
-# Specific nitcorn Action that uses a Model
-class ModelHandler
- super Handler
+# Nitweb config file.
+class NitwebConfig
+ super AppConfig
# Model to use.
var model: Model
# MModule used to flatten model.
var mainmodule: MModule
+ # Modelbuilder used to access sources.
+ var modelbuilder: ModelBuilder
+end
+
+# Specific nitcorn Action that uses a Model
+class ModelHandler
+ super Handler
+
+ # App config.
+ var config: NitwebConfig
+
# Find the MEntity ` with `full_name`.
fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
if full_name == null then return null
# Init the model view from the `req` uri parameters.
fun init_model_view(req: HttpRequest): ModelView do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
var show_private = req.bool_arg("private") or else false
if not show_private then view.min_visibility = protected_visibility
#
# So we can cache the model view.
var view: ModelView is lazy do
- var view = new ModelView(model)
+ var view = new ModelView(config.model)
view.min_visibility = private_visibility
view.include_fictive = true
view.include_empty_doc = true
end
end
+# A Rooter dedicated to APIHandlers.
+class APIRouter
+ super Router
+
+ # App config.
+ var config: NitwebConfig
+end
+
redef class MEntity
# URL to `self` within the web interface.
Usage: nitweb [OPTION]... <file.nit>...
-Run a webserver based on nitcorn that serve pages about model.
+Run a webserver based on nitcorn that serves pages about model.
Use --help for help