From: Jean Privat Date: Mon, 15 Aug 2016 17:17:49 +0000 (-0400) Subject: Merge: lib/github: use serialization X-Git-Url: http://nitlanguage.org?hp=8f148a97e44f7477c5bcdbfb16892402dea9625f Merge: lib/github: use serialization Just to see what I broke... Pull-Request: #2246 Reviewed-by: Jean Privat Reviewed-by: Alexis Laferrière --- diff --git a/contrib/shibuqam/examples/reloadgame.nit b/contrib/shibuqam/examples/reloadgame.nit new file mode 100644 index 0000000..24eecf5 --- /dev/null +++ b/contrib/shibuqam/examples/reloadgame.nit @@ -0,0 +1,115 @@ +# 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 = """ + + + + + Nitcorn on Shibboleth/UQAM + + +
+

Nitcorn on Shibboleth/UQAM

+ """ + + var user = http_request.user + + if user != null then + user.seen += 1 + + body += """ +

Welcome {{{user.given_name}}}

+
    +
  • Full Name: {{{user.display_name}}}
  • +
  • E-Mail: {{{user.email}}}
  • +
  • Id: {{{user.id}}}
  • +
  • Score: {{{user.seen}}}
  • +
+ """ + + #for k, v in http_request.header do body += "
  • {k}: {v}
  • " + else + # The login page, at the location the reverse proxy is expected to be configured + # to force an authentication. + var login = "/securep/login" + body += """ +

    Welcome annonymous, please log in.

    + """ + end + + var score = new Counter[User] + for u in db.users.values do + score[u] = u.seen + end + + body += "

    Scoreboard

      " + for u in score.sort.reversed do + body += "
    • {u.display_name}: {u.seen}
    • " + end + + + body += """
    +
    + + + """ + + response.html body + end +end + +var app = new App +app.use("/*", new ReloadGame) +app.listen("localhost", 3000) diff --git a/contrib/shibuqam/package.ini b/contrib/shibuqam/package.ini new file mode 100644 index 0000000..8c619ad --- /dev/null +++ b/contrib/shibuqam/package.ini @@ -0,0 +1,11 @@ +[package] +name=shibuqam +tags=web +maintainer=Jean Privat +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 diff --git a/contrib/shibuqam/shibuqam.nit b/contrib/shibuqam/shibuqam.nit new file mode 100644 index 0000000..e8da9a5 --- /dev/null +++ b/contrib/shibuqam/shibuqam.nit @@ -0,0 +1,69 @@ +# 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 diff --git a/examples/calculator/Makefile b/examples/calculator/Makefile index bf2a19e..21d74f8 100644 --- a/examples/calculator/Makefile +++ b/examples/calculator/Makefile @@ -13,34 +13,49 @@ bin/scientific: $(shell ${NITLS} -M scientific linux) ${NITC} # --- # 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 diff --git a/examples/calculator/android/res/values/color.xml b/examples/calculator/android/res/values/color.xml new file mode 100644 index 0000000..3ed5b06 --- /dev/null +++ b/examples/calculator/android/res/values/color.xml @@ -0,0 +1,56 @@ + + + + + + + #00BCD4 + + + #F40056 + + + #FFF + + + #8A000000 + + + #6C000000 + + + #434343 + + + #636363 + + + #1DE9B6 + + + #FFF + + + #91000000 + + + #33FFFFFF + + + #1A000000 + + diff --git a/examples/calculator/org.nitlanguage.scientific_calculator.txt b/examples/calculator/org.nitlanguage.scientific_calculator.txt new file mode 100644 index 0000000..0ec8577 --- /dev/null +++ b/examples/calculator/org.nitlanguage.scientific_calculator.txt @@ -0,0 +1,10 @@ +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. +. diff --git a/examples/calculator/package.ini b/examples/calculator/package.ini index 8237327..014014d 100644 --- a/examples/calculator/package.ini +++ b/examples/calculator/package.ini @@ -9,4 +9,4 @@ git=https://github.com/nitlang/nit.git 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 diff --git a/examples/calculator/src/android14.nit b/examples/calculator/src/android14.nit new file mode 100644 index 0000000..8695c3b --- /dev/null +++ b/examples/calculator/src/android14.nit @@ -0,0 +1,64 @@ +# 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 diff --git a/examples/calculator/src/android21/android/res/values/styles.xml b/examples/calculator/src/android21/android/res/values/styles.xml new file mode 100644 index 0000000..95c844a --- /dev/null +++ b/examples/calculator/src/android21/android/res/values/styles.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + diff --git a/examples/calculator/src/android21/android21.nit b/examples/calculator/src/android21/android21.nit new file mode 100644 index 0000000..36d1025 --- /dev/null +++ b/examples/calculator/src/android21/android21.nit @@ -0,0 +1,50 @@ +# 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 diff --git a/examples/calculator/src/android_calculator.nit b/examples/calculator/src/android_calculator.nit deleted file mode 100644 index 1d51073..0000000 --- a/examples/calculator/src/android_calculator.nit +++ /dev/null @@ -1,36 +0,0 @@ -# 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 diff --git a/lib/core/file.nit b/lib/core/file.nit index bea9672..79ddf45 100644 --- a/lib/core/file.nit +++ b/lib/core/file.nit @@ -626,6 +626,22 @@ class Path 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`. # @@ -657,7 +673,7 @@ class 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 diff --git a/lib/json/serialization.nit b/lib/json/serialization.nit index 6a66407..e2c3adf 100644 --- a/lib/json/serialization.nit +++ b/lib/json/serialization.nit @@ -242,6 +242,9 @@ class JsonDeserializer # 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`. @@ -265,7 +268,10 @@ class JsonDeserializer 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 @@ -441,11 +447,16 @@ class JsonDeserializer 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. @@ -461,6 +472,7 @@ class JsonDeserializer # serialize # # var error: String + # var related_data: MyData # end # # class MyJsonDeserializer @@ -468,18 +480,26 @@ class JsonDeserializer # # 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 diff --git a/lib/nitcorn/nitcorn.nit b/lib/nitcorn/nitcorn.nit index 2a5b43a..40d02ef 100644 --- a/lib/nitcorn/nitcorn.nit +++ b/lib/nitcorn/nitcorn.nit @@ -25,26 +25,27 @@ # Basic usage example: # ~~~~ # class MyAction -# super Action -# -# redef fun answer(http_request, turi) -# do -# var response = new HttpResponse(200) -# response.body = """ -# -# -# -# Hello World -# -# -#

    Hello World

    -# -# """ -# return response -# end +# super Action +# +# redef fun answer(http_request, turi) +# do +# var response = new HttpResponse(200) +# response.body = """ +# +# +# +# Hello World +# +# +#

    Hello World

    +# +# """ +# 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) diff --git a/lib/nitcorn/reactor.nit b/lib/nitcorn/reactor.nit index de9ddc0..66334c3 100644 --- a/lib/nitcorn/reactor.nit +++ b/lib/nitcorn/reactor.nit @@ -201,8 +201,15 @@ redef class Interfaces 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 @@ -212,7 +219,7 @@ redef class VirtualHosts 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 diff --git a/lib/nitcorn/server_config.nit b/lib/nitcorn/server_config.nit index e61caad..3cf51ff 100644 --- a/lib/nitcorn/server_config.nit +++ b/lib/nitcorn/server_config.nit @@ -25,7 +25,7 @@ class ServerConfig 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 @@ -82,7 +82,7 @@ class Interfaces 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) diff --git a/lib/serialization/serialization.nit b/lib/serialization/serialization.nit index 0ad3019..435f8df 100644 --- a/lib/serialization/serialization.nit +++ b/lib/serialization/serialization.nit @@ -88,21 +88,27 @@ end # 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 diff --git a/src/nitweb.nit b/src/nitweb.nit index 7c6cdc1..9376172 100644 --- a/src/nitweb.nit +++ b/src/nitweb.nit @@ -15,35 +15,52 @@ # 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) @@ -59,61 +76,49 @@ private class NitwebPhase 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 @@ -121,7 +126,7 @@ end var toolcontext = new ToolContext var tpl = new Template tpl.add "Usage: nitweb [OPTION]... ...\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 diff --git a/src/web/api_catalog.nit b/src/web/api_catalog.nit index 72d207f..f0c70c6 100644 --- a/src/web/api_catalog.nit +++ b/src/web/api_catalog.nit @@ -19,23 +19,17 @@ import catalog # 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 @@ -74,7 +68,7 @@ class APICatalogStats 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 @@ -97,7 +91,7 @@ class APICatalogMostRequired 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) diff --git a/src/web/api_docdown.nit b/src/web/api_docdown.nit index 3510b64..bf4c80f 100644 --- a/src/web/api_docdown.nit +++ b/src/web/api_docdown.nit @@ -24,13 +24,10 @@ import doc_commands 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 diff --git a/src/web/api_feedback.nit b/src/web/api_feedback.nit index 61ba756..76e9d67 100644 --- a/src/web/api_feedback.nit +++ b/src/web/api_feedback.nit @@ -18,21 +18,38 @@ module api_feedback 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 @@ -40,9 +57,6 @@ 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 @@ -71,7 +85,7 @@ class APIStars 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 @@ -82,7 +96,7 @@ class APIStars 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 diff --git a/src/web/api_graph.nit b/src/web/api_graph.nit index 67a32e2..d58031d 100644 --- a/src/web/api_graph.nit +++ b/src/web/api_graph.nit @@ -21,16 +21,10 @@ import uml # 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 diff --git a/src/web/api_metrics.nit b/src/web/api_metrics.nit index 30a53f1..8d64c04 100644 --- a/src/web/api_metrics.nit +++ b/src/web/api_metrics.nit @@ -19,16 +19,10 @@ import metrics # 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 @@ -36,6 +30,7 @@ class APIStructuralMetrics 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)) @@ -58,6 +53,7 @@ class APIStructuralMetrics 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)) diff --git a/src/web/api_model.nit b/src/web/api_model.nit index f8c6eeb..63fd636 100644 --- a/src/web/api_model.nit +++ b/src/web/api_model.nit @@ -148,7 +148,7 @@ class APIEntityLinearization 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 @@ -206,7 +206,7 @@ class APIEntityUML 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) @@ -225,9 +225,6 @@ end 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 @@ -241,7 +238,7 @@ class APIEntityCode # 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 diff --git a/src/web/web_base.nit b/src/web/web_base.nit index 20d0b05..c535936 100644 --- a/src/web/web_base.nit +++ b/src/web/web_base.nit @@ -19,10 +19,11 @@ import model::model_views 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 @@ -30,6 +31,17 @@ class ModelHandler # 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 @@ -38,7 +50,7 @@ class ModelHandler # 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 @@ -59,7 +71,7 @@ abstract class APIHandler # # 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 @@ -87,6 +99,14 @@ abstract class APIHandler 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. diff --git a/tests/sav/nitweb.res b/tests/sav/nitweb.res index 7c0cf6d..a6f1e98 100644 --- a/tests/sav/nitweb.res +++ b/tests/sav/nitweb.res @@ -1,3 +1,3 @@ Usage: nitweb [OPTION]... ... -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 diff --git a/wallet b/wallet new file mode 100755 index 0000000..567234d Binary files /dev/null and b/wallet differ