Merge: nitin FFI seems to work
authorJean Privat <jean@pryen.org>
Mon, 27 Nov 2017 15:02:56 +0000 (10:02 -0500)
committerJean Privat <jean@pryen.org>
Mon, 27 Nov 2017 15:02:56 +0000 (10:02 -0500)
This adds the bad triple-quoted string and bad extern in the lexer to enable continuing in nitin.
Now FFI seems to work.

~~~raw
-->fun hello `{
...puts("Hello, world");
...`}
-->hello
Hello, world
~~~

Pull-Request: #2587

71 files changed:
clib/gc_chooser.c
contrib/benitlux/src/client/android.nit
lib/android/NitActivity.java
lib/android/README.md
lib/android/audio.nit
lib/android/native_app_glue.nit
lib/android/nit_activity.nit
lib/android/service/NitService.java
lib/android/ui/ui.nit
lib/html/bootstrap.nit
lib/markdown/man.nit
lib/markdown/markdown.nit
lib/pthreads/pthreads.nit
misc/docker/full/Dockerfile
share/android-bdwgc/.gitignore [new file with mode: 0644]
share/android-bdwgc/setup.sh [new file with mode: 0755]
share/android-gradlew/gradle/wrapper/gradle-wrapper.jar [new file with mode: 0644]
share/android-gradlew/gradle/wrapper/gradle-wrapper.properties [new file with mode: 0644]
share/android-gradlew/gradlew [new file with mode: 0755]
share/libgc/.gitignore [deleted file]
share/libgc/android-setup-libgc.sh [deleted file]
src/catalog/catalog.nit [moved from src/catalog.nit with 100% similarity]
src/catalog/catalog_json.nit [new file with mode: 0644]
src/doc/commands/commands.nit [new file with mode: 0644]
src/doc/commands/commands_base.nit [new file with mode: 0644]
src/doc/commands/commands_catalog.nit [new file with mode: 0644]
src/doc/commands/commands_docdown.nit [new file with mode: 0644]
src/doc/commands/commands_graph.nit [new file with mode: 0644]
src/doc/commands/commands_html.nit [new file with mode: 0644]
src/doc/commands/commands_http.nit [new file with mode: 0644]
src/doc/commands/commands_json.nit [new file with mode: 0644]
src/doc/commands/commands_model.nit [new file with mode: 0644]
src/doc/commands/commands_parser.nit [new file with mode: 0644]
src/doc/commands/commands_usage.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_catalog.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_graph.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_http.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_ancestors.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_call.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_children.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_comment.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_descendants.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_entity.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_features.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_lin.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_mentities.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_new.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_param.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_parents.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_return.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_json.sav/test_cmd_search.res [new file with mode: 0644]
src/doc/commands/tests/test_commands_model.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_parser.nit [new file with mode: 0644]
src/doc/commands/tests/test_commands_usage.nit [new file with mode: 0644]
src/doc/doc_down.nit
src/doc/html_templates/html_components.nit
src/doc/html_templates/html_model.nit
src/doc/html_templates/html_templates.nit
src/doc/html_templates/model_html.nit
src/doc/templates/templates_html.nit [new file with mode: 0644]
src/model/model_collect.nit
src/model/model_index.nit
src/model/model_views.nit
src/model/test_model_json.sav/test_classdefs_to_full_json.res
src/model/test_model_json.sav/test_classes_to_full_json.res
src/model/test_model_json.sav/test_propdefs_to_full_json.res
src/model/test_model_json.sav/test_props_to_full_json.res
src/platform/android.nit
src/web/api_catalog.nit

index 5cf3065..3a37da5 100644 (file)
@@ -18,9 +18,6 @@
 #ifdef ANDROID
        #include <android/log.h>
        #define PRINT_ERROR(...) ((void)__android_log_print(ANDROID_LOG_WARN, "nit", __VA_ARGS__))
-
-       // FIXME bring back when the GC is fixed in Android
-       #undef WITH_LIBGC
 #else
        #define PRINT_ERROR(...) ((void)fprintf(stderr, __VA_ARGS__))
 #endif
@@ -28,7 +25,7 @@
 enum gc_option { gc_opt_large, gc_opt_malloc, gc_opt_boehm } gc_option;
 
 #ifdef WITH_LIBGC
-#include <gc/gc.h>
+       #include <gc.h>
 #endif
 
 void *nit_raw_alloc(size_t s0)
index c009b50..ee51327 100644 (file)
@@ -53,6 +53,7 @@ redef class App
                android.content.IntentFilter filter = new android.content.IntentFilter();
                filter.addAction(android.net.wifi.WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
                final int final_self = self;
+               App_incr_ref(final_self);
 
                context.registerReceiver(
                        new android.content.BroadcastReceiver() {
index cbe6ad4..2f712f1 100644 (file)
@@ -32,7 +32,7 @@ public class NitActivity extends Activity {
         */
 
        static {
-               System.loadLibrary("main");
+               System.loadLibrary("nit_app");
        }
 
        /*
index b6bcf7e..457f426 100644 (file)
@@ -8,29 +8,52 @@ file can be specified using the `-o` and `--dir` options.
 
 # Host system configuration
 
-Some configuration is required to compile for the Android platform from a GNU/Linux host.
+To compile Android apps from a 64 bits GNU/Linux host you can reuse an existing Android Studio
+installation or make a clean install with command line tools only.
 
-1. Download and install the latest Android SDK __and__ NDK.
+Note that this guide supports only 64 bits GNU/Linux hosts with support for a Java 8 JDK,
+it may be possible to support other platforms with some tweaks.
 
-2. Update PATH so it includes the tools `android`, `ndk-build` and `ant`.
-       You should add something like the following snippet to your .bashrc or equivalent,
-       be careful to replace `ANDROID_SDK` and `ANDROID_NDK` with the full path where you installed each package.
+1.     Install the required SDK packages using one of these two methods:
+
+       a.      Using Android Studio, open `Tools > Android > SDK Manager`, in the SDK Tools tab,
+               install "Android SDK Build-Tools", CMake and NDK.
+
+       b.      From the command line, run this script for a quick setup without Android Studio.
+               You will probably need to tweak it to you system or update the download URL
+               to the latest SDK tools from https://developer.android.com/studio/index.html#command-tools
+
+               ~~~
+               # Fetch and extract SDK tools
+               mkdir -p ~/Android/Sdk
+               cd ~/Android/Sdk
+               wget https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip
+               unzip sdk-tools-linux-3859397.zip
+
+               # Update tools
+               tools/bin/sdkmanager --update
+
+               # Accept the licenses
+               tools/bin/sdkmanager --licenses
+
+               # Install the basic build tools
+               tools/bin/sdkmanager "build-tools;27.0.0" ndk-bundle
+               ~~~
+
+3.     Set the environment variable ANDROID_HOME to the SDK installation directory, usually `~/Android/Sdk/`.
+       Use the following command to setup the variable for bash.
 
        ~~~
-       export PATH=$PATH:ANDROID_SDK/tools/:ANDROID_SDK/platform-tools/:ANDROID_NDK/
+       echo "export ANDROID_HOME=~/Android/Sdk/" >> ~/.bashrc
        ~~~
 
-2. Using the `android` executable, download the latest `tools, build-tools` and within the Android 4.0.3 (API 15) folder, install `SDK platform`.
-       You may have to install additional SDK platforms for applications with different targets.
-
-3. Using your OS package manager, install `apt openjdk-7-jdk lib32stdc++6 lib32z1`.
-       On Debian and Ubuntu the command is:
+4.     Install Java 8 JDK, on Debian/Ubuntu systems you can use the following command:
 
        ~~~
-       sudo apt-get install apt openjdk-7-jdk lib32stdc++6 lib32z1
+       sudo apt install openjdk-8-jdk
        ~~~
 
-# Configure your Android application
+# Configure the Android application
 
 The _app.nit_ framework and this project offers some services to
 customize the generated Android application.
index a8d0afe..19fac5a 100644 (file)
@@ -152,6 +152,13 @@ private extern class NativeMediaPlayer in "Java" `{ android.media.MediaPlayer `}
                }
        `}
        fun reset in "Java" `{ self.reset(); `}
+
+       # HACK for bug #845
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = NativeMediaPlayer_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
 end
 
 # Sound Pool from Java, used to play sounds simultaneously
@@ -203,6 +210,13 @@ private extern class NativeSoundPool in "Java" `{ android.media.SoundPool `}
        fun stop(stream_id: Int) in "Java" `{ self.stop((int)stream_id); `}
        fun unload(sound_id: Int): Bool in "Java" `{ return self.unload((int)sound_id); `}
        fun release in "Java" `{ self.release(); `}
+
+       # HACK for bug #845
+       redef fun new_global_ref import sys, Sys.jni_env `{
+               Sys sys = NativeSoundPool_sys(self);
+               JNIEnv *env = Sys_jni_env(sys);
+               return (*env)->NewGlobalRef(env, self);
+       `}
 end
 
 
@@ -238,7 +252,7 @@ class SoundPool
        # Stream priority
        private var priority = 1
 
-       init do self.nsoundpool = new NativeSoundPool(max_streams, stream_type, src_quality)
+       init do self.nsoundpool = (new NativeSoundPool(max_streams, stream_type, src_quality)).new_global_ref
 
        # Load the sound from an asset file descriptor
        # this function is for advanced use
@@ -364,7 +378,7 @@ class MediaPlayer
 
        # Create a new MediaPlayer, but no sound is attached, you'll need
        # to use `load_sound` before using it
-       init do self.nmedia_player = new NativeMediaPlayer
+       init do self.nmedia_player = (new NativeMediaPlayer).new_global_ref
 
        # Init the mediaplayer with a sound resource id
        init from_id(context: NativeActivity, id: Int) do
@@ -656,14 +670,14 @@ redef class App
        var default_soundpool: SoundPool is lazy do return new SoundPool
 
        # Get the native audio manager
-       private fun audio_manager: NativeAudioManager import native_activity in "Java" `{
-               return (AudioManager)App_native_activity(self).getSystemService(Context.AUDIO_SERVICE);
+       private fun audio_manager(native_activity: NativeContext): NativeAudioManager in "Java" `{
+               return (AudioManager)native_activity.getSystemService(Context.AUDIO_SERVICE);
        `}
 
        # Sets the stream of the app to STREAM_MUSIC.
        # STREAM_MUSIC is the default stream used by android apps.
-       private fun manage_audio_stream import native_activity in "Java" `{
-               App_native_activity(self).setVolumeControlStream(AudioManager.STREAM_MUSIC);
+       private fun manage_audio_stream(native_activity: NativeActivity) in "Java" `{
+               native_activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
        `}
 
        # Same as `load_sound` but load the sound from the `res/raw` folder
@@ -688,18 +702,18 @@ redef class App
                                s.paused = false
                        end
                end
-               audio_manager.abandon_audio_focus
+               audio_manager(native_activity).abandon_audio_focus
        end
 
        redef fun on_create do
                super
-               audio_manager.request_audio_focus
-               manage_audio_stream
+               audio_manager(native_activity).request_audio_focus
+               manage_audio_stream native_activity
        end
 
        redef fun on_resume do
                super
-               audio_manager.request_audio_focus
+               audio_manager(native_activity).request_audio_focus
                for s in sounds do
                        # Resumes only the sounds paused by the App
                        if not s.paused then s.resume
index f83350e..2a8f895 100644 (file)
@@ -37,7 +37,7 @@
 #   main activity of the running application. Use it to get anything related
 #   to the `Context` and as anchor to execute Java UI code.
 module native_app_glue is
-       ldflags "-landroid"
+       ldflags("-landroid", "-lnative_app_glue")
        android_activity "android.app.NativeActivity"
 end
 
@@ -56,7 +56,8 @@ in "C body" `{
        // We relay the call to the Nit application.
        void android_main(struct android_app* app) {
                native_app_glue_data = app;
-               app_dummy();
+
+               int main(int argc, char ** argv);
                main(0, NULL);
        }
 
index 1bdf597..dac5a78 100644 (file)
@@ -64,6 +64,7 @@ in "C body" `{
                global_jvm = vm;
 
                // Invoke Nit system and main
+               int main(int argc, char ** argv);
                main(0, NULL);
 
                return JNI_VERSION_1_2;
index 6603b8e..b0767d6 100644 (file)
@@ -25,7 +25,7 @@ public class NitService extends Service {
        protected int nitService = 0;
 
        static {
-               System.loadLibrary("main");
+               System.loadLibrary("nit_app");
        }
 
        @Override
index c3798f5..60f7909 100644 (file)
@@ -208,6 +208,7 @@ redef class Android_widget_ArrayAdapter
        private new (context: NativeContext, res: Int, sender: ListLayout)
        import ListLayout.create_view in "Java" `{
                final int final_sender_object = sender;
+               ListLayout_incr_ref(sender);
 
                return new android.widget.ArrayAdapter(context, (int)res) {
                                @Override
@@ -349,6 +350,7 @@ redef class Android_app_Fragment
        private new (nit_window: Window)
        import Window.on_create_fragment in "Java" `{
                final int final_nit_window = nit_window;
+               Window_incr_ref(nit_window);
 
                return new android.app.Fragment(){
                        @Override
index 7b5084f..cc3d6e2 100644 (file)
@@ -31,7 +31,7 @@ abstract class BSComponent
        super Template
 
        # CSS classes to add on this element.
-       var css_classes = new Array[String]
+       var css_classes = new Array[String] is optional
 
        # Render `self` css clases as a `class` attribute.
        fun render_css_classes: String do
@@ -53,11 +53,12 @@ end
 #
 # Creates a link with a title attribute:
 # ~~~
-# lnk = new Link.with_title("http://nitlanguage.org", "Nit", "Nit homepage")
+# lnk = new Link("http://nitlanguage.org", "Nit", "Nit homepage")
 # assert lnk.write_to_string == "<a href=\"http://nitlanguage.org\" title=\"Nit homepage\">Nit</a>"
 # ~~~
 class Link
        super BSComponent
+       autoinit(href, text, title, css_classes)
 
        # URL pointed by this link.
        var href: String is writable
@@ -66,13 +67,7 @@ class Link
        var text: Writable is writable
 
        # Optional title.
-       var title: nullable String is noinit, writable
-
-       # Creates a link with a `title` attribute.
-       init with_title(href: String, text: Writable, title: nullable String) do
-               init(href, text)
-               self.title = title
-       end
+       var title: nullable String = null is optional, writable
 
        redef fun rendering do
                add "<a{render_css_classes} href=\"{href}\""
@@ -95,11 +90,12 @@ end
 #
 # With subtext:
 # ~~~
-# var h6 = new Header.with_subtext(6, "Title", "with subtext")
+# var h6 = new Header(6, "Title", "with subtext")
 # assert h6.write_to_string == "<h6>Title<small>with subtext</small></h6>"
 # ~~~
 class Header
        super BSComponent
+       autoinit(level, text, subtext, id, css_classes)
 
        # Header level between 1 and 6.
        var level: Int
@@ -108,13 +104,10 @@ class Header
        var text: Writable
 
        # Optional subtext.
-       var subtext: nullable Writable is noinit, writable
+       var subtext: nullable Writable = null is optional, writable
 
-       # Creates a link with a `title` attribute.
-       init with_subtext(level: Int, text: Writable, subtext: String) do
-               init(level, text)
-               self.subtext = subtext
-       end
+       # Optional id.
+       var id: nullable String = null is optional, writable
 
        redef fun rendering do
                add "<h{level}{render_css_classes}>{text.write_to_string}"
@@ -131,11 +124,12 @@ end
 # Used to factorize behavior between OrderedList and UnorderedList.
 abstract class HTMLList
        super BSComponent
+       autoinit(items, css_classes)
 
        # A list contains `<li>` tags as children.
        #
        # See ListItem.
-       var items = new Array[ListItem]
+       var items = new Array[ListItem] is optional
 
        # Adds a new ListItem to `self`.
        fun add_li(item: ListItem) do items.add item
@@ -203,6 +197,7 @@ end
 # A `<li>` tag.
 class ListItem
        super BSComponent
+       autoinit(text, css_classes)
 
        # Content to display in this list item.
        var text: Writable is writable
@@ -222,6 +217,7 @@ end
 # ~~~
 class BSIcon
        super BSComponent
+       autoinit(icon, css_classes)
 
        # Glyphicon name to display.
        #
@@ -278,6 +274,7 @@ end
 # ~~~
 class BSLabel
        super BSComponent
+       autoinit(color, text, css_classes)
 
        # Class used to change the color of the label.
        #
@@ -306,6 +303,7 @@ end
 # ~~~
 class BSBadge
        super BSComponent
+       autoinit(text, css_classes)
 
        # Text to display in the label.
        var text: Writable
@@ -333,6 +331,7 @@ end
 # ~~~
 class BSPageHeader
        super BSComponent
+       autoinit(text, css_classes)
 
        # Text to display as title.
        var text: Writable
@@ -362,6 +361,7 @@ end
 # ~~~
 class BSAlert
        super BSComponent
+       autoinit(color, text, is_dismissible, css_classes)
 
        # Class used to change the color of the alert.
        #
@@ -376,7 +376,7 @@ class BSAlert
        # See http://getbootstrap.com/components/#alerts-dismissible
        #
        # Default is `false`.
-       var is_dismissible = false
+       var is_dismissible = false is optional, writable
 
        init do css_classes.add "alert alert-{color}"
 
@@ -399,7 +399,7 @@ end
 # Example:
 #
 # ~~~
-# var p = new BSPanel("default", "Panel content")
+# var p = new BSPanel("default", body = "Panel content")
 #
 # assert p.write_to_string == """
 # <div class="panel panel-default">
@@ -413,8 +413,7 @@ end
 # Panel with heading:
 #
 # ~~~
-# p = new BSPanel("danger", "Panel content")
-# p.heading = "Panel heading"
+# p = new BSPanel("danger", heading = "Panel heading", body = "Panel content")
 #
 # assert p.write_to_string == """
 # <div class="panel panel-danger">
@@ -429,20 +428,21 @@ end
 # ~~~
 class BSPanel
        super BSComponent
+       autoinit(color, heading, body, footer, css_classes)
 
        # Panel color.
        #
        # Can be one of `default`, `primary`, `success`, `info`, `warning` or `danger`.
-       var color: String
+       var color: String is writable
 
        # Panel header if any.
-       var heading: nullable Writable is noinit, writable
+       var heading: nullable Writable = null is optional, writable
 
        # Body to display in the panel.
-       var body: Writable
+       var body: nullable Writable = null is optional, writable
 
        # Panel footer is any.
-       var footer: nullable Writable is noinit, writable
+       var footer: nullable Writable = null is optional, writable
 
        init do css_classes.add "panel panel-{color}"
 
@@ -454,9 +454,12 @@ class BSPanel
                        addn heading.write_to_string
                        addn "</div>"
                end
-               addn "<div class=\"panel-body\">"
-               addn body.write_to_string
-               addn "</div>"
+               var body = self.body
+               if body != null then
+                       addn "<div class=\"panel-body\">"
+                       addn body.write_to_string
+                       addn "</div>"
+               end
                var footer = self.footer
                if footer != null then
                        addn "<div class=\"panel-footer\">"
index 8128642..2c7c419 100644 (file)
@@ -21,6 +21,8 @@ import markdown
 class ManDecorator
        super Decorator
 
+       redef var headlines = new ArrayMap[String, HeadLine]
+
        redef fun add_ruler(v, block) do v.add "***\n"
 
        redef fun add_headline(v, block) do
index b29372f..2426d53 100644 (file)
@@ -150,6 +150,7 @@ class MarkdownProcessor
                parent.remove_surrounding_empty_lines
                recurse(parent, false)
                # output processed text
+               decorator.headlines.clear
                return emit(parent.kind)
        end
 
index 585005b..3a6dc7b 100644 (file)
@@ -45,7 +45,7 @@ in "C" `{
                #endif
        #endif
 
-       #if !defined(__ANDROID__) && !defined(IOS)
+       #if !defined(IOS)
                #define GC_THREADS
                #include <gc.h>
        #endif
index eb7f26d..c19ab91 100644 (file)
@@ -48,19 +48,16 @@ RUN dpkg --add-architecture i386 \
 RUN mkdir -p /opt \
        && cd /opt \
        # Android SDK
-       && curl https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz -o android-sdk-linux.tgz \
-       && tar xzf android-sdk-linux.tgz \
-       && rm android-sdk-linux.tgz \
-       && echo y | android-sdk-linux/tools/android update sdk -a --no-ui --filter \
-               # Hardcode minimal known working things
-               platform-tools,build-tools-22.0.1,android-22,android-21,android-19,android-16,android-15,android-10 \
-       # Android NDK
-       && curl http://dl.google.com/android/repository/android-ndk-r11c-linux-x86_64.zip -o android-ndk.zip \
-       && unzip -q android-ndk.zip \
+       && curl https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip -o android-sdk-linux.zip \
+       && unzip android-sdk-linux.zip -d android-sdk-linux \
+       && rm android-sdk-linux.zip \
        && chmod -R a+X /opt \
-       && ln -s android-ndk-r11c android-ndk \
-       && rm android-ndk.zip \
-       && printf "PATH=$PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_NDK\nexport PATH\n" >> "/etc/profile.d/android.sh"
+       && yes | android-sdk-linux/tools/bin/sdkmanager --licenses \
+       && android-sdk-linux/tools/bin/sdkmanager "build-tools;27.0.0" "cmake;3.6.4111459" ndk-bundle platform-tools tools
+
+# Download gradlew and bdwgc for Android
+RUN /nit/share/android-gradlew/gradlew \
+       && /nit/share/android-bdwgc/setup.sh
 
 # Install OpenGL validator
 RUN git clone https://github.com/KhronosGroup/glslang.git \
@@ -71,9 +68,7 @@ RUN git clone https://github.com/KhronosGroup/glslang.git \
        && make
 
 # Setup environment variables
-ENV ANDROID_HOME /opt/android-sdk-linux
-ENV ANDROID_NDK /opt/android-ndk
-ENV PATH $PATH:$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$ANDROID_NDK
+ENV ANDROID_HOME=/opt/android-sdk-linux/
 ENV JAVA_HOME=/usr/lib/jvm/default-java/
 ENV JNI_LIB_PATH=$JAVA_HOME/jre/lib/amd64/server/
 ENV LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server/
diff --git a/share/android-bdwgc/.gitignore b/share/android-bdwgc/.gitignore
new file mode 100644 (file)
index 0000000..e339f9a
--- /dev/null
@@ -0,0 +1 @@
+bdwgc
diff --git a/share/android-bdwgc/setup.sh b/share/android-bdwgc/setup.sh
new file mode 100755 (executable)
index 0000000..bcd931d
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/bash
+# 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.
+
+# Fetch libgc/bdwgc
+
+# cd to the absolute installation path
+if expr match "$0" "^/.*"; then
+       install="`dirname "$0"`"
+else
+       install="`pwd`/`dirname "$0"`"
+fi
+cd $install
+
+# Download or redownload
+rm -rf bdwgc
+git clone -b android https://github.com/xymus/bdwgc.git || exit 1
+
+# Setup libatomic_ops too
+cd bdwgc || exit 1
+git submodule init || exit 1
+git submodule update || exit 1
diff --git a/share/android-gradlew/gradle/wrapper/gradle-wrapper.jar b/share/android-gradlew/gradle/wrapper/gradle-wrapper.jar
new file mode 100644 (file)
index 0000000..13372ae
Binary files /dev/null and b/share/android-gradlew/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/share/android-gradlew/gradle/wrapper/gradle-wrapper.properties b/share/android-gradlew/gradle/wrapper/gradle-wrapper.properties
new file mode 100644 (file)
index 0000000..4be4899
--- /dev/null
@@ -0,0 +1,6 @@
+#Thu Oct 26 20:48:37 EDT 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
diff --git a/share/android-gradlew/gradlew b/share/android-gradlew/gradlew
new file mode 100755 (executable)
index 0000000..9d82f78
--- /dev/null
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/share/libgc/.gitignore b/share/libgc/.gitignore
deleted file mode 100644 (file)
index 98279d0..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-include
-lib
-share
-src
diff --git a/share/libgc/android-setup-libgc.sh b/share/libgc/android-setup-libgc.sh
deleted file mode 100755 (executable)
index 07d8c82..0000000
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/bin/bash
-# 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.
-
-# Fetch, configure and build libgc (the Boehm GC) for Android
-#
-# Will produce libgc.a which can be linked to Android NDK applications.
-#
-# The `ndk-build` tool from the Android NDK must be in PATH before
-# invoking this tool. It will be used to guess the path to the NDK.
-#
-# Alternatively, you may define a custom path to the NDK by setting
-# `ANDROID_NDK`.
-#
-# The source package should be downloaded and compiled only once.
-# If the working directory is cleared, the cached version will be lost.
-# On test servers, which clear the working directory frequently,
-# it may be a good idea to use a local version of the source packages.
-# To do so, download the following two packages and put them in your HOME directory.
-# * http://www.hboehm.info/gc/gc_source/gc-7.4.0.tar.gz
-# * http://www.hboehm.info/gc/gc_source/libatomic_ops-7.4.0.tar.gz
-
-# If ANDROID_NDK is not set, get it from the path to `ndk-build`
-if test -z "$ANDROID_NDK"; then
-       ndk_build_path=`which ndk-build`
-       if test $? -ne 0; then
-               echo "Error: ndk-build from the Android NDK must be in your PATH"
-               exit 1
-       fi
-
-       ANDROID_NDK=`dirname $ndk_build_path`
-fi
-
-# Information on the currently targeted libgc and libatomic_ops source URL
-# These may have to be updated according to server-side changes and newer
-# versions of the Boehm GC.
-libgc_url=http://www.hboehm.info/gc/gc_source/gc-7.4.0.tar.gz
-libgc_local=~/gc-7.4.0.tar.gz
-libgc_dir=gc-7.4.0
-libatomic_ops_url=http://www.hboehm.info/gc/gc_source/libatomic_ops-7.4.0.tar.gz
-libatomic_ops_local=~/libatomic_ops-7.4.0.tar.gz
-libatomic_ops_dir=libatomic_ops-7.4.0
-
-# Absolute installation path
-if expr match "$0" "^/.*"; then
-       install="`dirname "$0"`"
-else
-       install="`pwd`/`dirname "$0"`"
-fi
-
-# Local source directory
-mkdir -p "$install/src"
-cd "$install/src"
-
-# Download libs, trying to use a local version if possible
-urls=""
-if test -f $libgc_local; then
-       cp $libgc_local .
-else
-       urls=$libgc_url
-fi
-
-if test -f $libatomic_ops_local; then
-       cp $libatomic_ops_local .
-else
-       urls="$urls $libatomic_ops_url"
-fi
-
-for url in $urls; do
-       echo "Downloading $url..."
-       curl --progress-bar -o `basename $url` $url || exit 1
-done
-
-if test -d $libgc_dir; then
-       rm -r $libgc_dir
-fi
-
-# Extract
-tar -xzf `basename $libgc_url` || exit 1
-tar -xzf `basename $libatomic_ops_url` || exit 1
-mv $libatomic_ops_dir $libgc_dir/libatomic_ops || exit 1
-
-cd $libgc_dir || exit 1
-
-archs=(         arm                       x86                mips)
-tools_dirs=(    arm-linux-androideabi-4.9 x86-4.9            mipsel-linux-android-4.9)
-tools_prefixes=(arm-linux-androideabi     i686-linux-android mipsel-linux-android)
-hosts=(         arm-linux-androideabi     x86-linux-android  mips-linux-android)
-
-n_archs=$(( ${#archs[@]} - 1 ))
-for i in $(eval echo "{0..$n_archs}"); do
-       arch=${archs[i]}
-       tools_dir=${tools_dirs[i]}
-       tools_prefix=${tools_prefixes[i]}
-       host=${hosts[i]}
-
-       # Get the first platform available (it shouldn't change much, but it may
-       # have to be adjusted)
-       for platform in `echo $ANDROID_NDK/platforms/android-*/arch-$arch`; do
-               sys_root=$platform
-               break
-       done
-
-       if test -z "$sys_root"; then
-               echo "Error: could not an Android platform for $arch in the NDK, define ANDROID_NDK to the correct path."
-               exit 1
-       fi
-
-       # Configure for Android
-       path="$ANDROID_NDK/toolchains/$tools_dir/prebuilt/linux-x86_64/bin/"
-       export CC="$path/$tools_prefix-gcc --sysroot=$sys_root"
-       export CXX="$path/$tools_prefix-g++ --sysroot=$sys_root"
-       export LD="$path/$tools_prefix-ld"
-       export AR="$path/$tools_prefix-ar"
-       export RANLIB="$path/$tools_prefix-ranlib"
-       export STRIP="$path/$tools_prefix-strip"
-       export CFLAGS="-DIGNORE_DYNAMIC_LOADING -DPLATFORM_ANDROID -I libatomic_ops/src/"
-       export LIBS="-lc -lgcc"
-       ./configure --host=$host --enable-static --disable-shared --prefix="$install/$arch/" || exit 1
-
-       # Compile and install locally
-       make install -j 4 || exit 1
-done
similarity index 100%
rename from src/catalog.nit
rename to src/catalog/catalog.nit
diff --git a/src/catalog/catalog_json.nit b/src/catalog/catalog_json.nit
new file mode 100644 (file)
index 0000000..da1bcfc
--- /dev/null
@@ -0,0 +1,85 @@
+# 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.
+
+# Translate catalog entities to JSON
+module catalog_json
+
+import catalog
+
+redef class MPackageMetadata
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("license", license)
+               v.serialize_attribute("maintainers", maintainers)
+               v.serialize_attribute("contributors", contributors)
+               v.serialize_attribute("tags", tags)
+               v.serialize_attribute("tryit", tryit)
+               v.serialize_attribute("apk", apk)
+               v.serialize_attribute("homepage", homepage)
+               v.serialize_attribute("browse", browse)
+               v.serialize_attribute("git", git)
+               v.serialize_attribute("issues", issues)
+               v.serialize_attribute("first_date", first_date)
+               v.serialize_attribute("last_date", last_date)
+       end
+end
+
+# Catalog statistics
+redef class CatalogStats
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("packages", packages)
+               v.serialize_attribute("maintainers", maintainers)
+               v.serialize_attribute("contributors", contributors)
+               v.serialize_attribute("tags", tags)
+               v.serialize_attribute("modules", modules)
+               v.serialize_attribute("classes", classes)
+               v.serialize_attribute("methods", methods)
+               v.serialize_attribute("loc", loc)
+       end
+end
+
+# MPackage statistics for the catalog
+redef class MPackageStats
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("mmodules", mmodules)
+               v.serialize_attribute("mclasses", mclasses)
+               v.serialize_attribute("mmethods", mmethods)
+               v.serialize_attribute("loc", loc)
+               v.serialize_attribute("errors", errors)
+               v.serialize_attribute("warnings", warnings)
+               v.serialize_attribute("warnings_per_kloc", warnings_per_kloc)
+               v.serialize_attribute("documentation_score", documentation_score)
+               v.serialize_attribute("commits", commits)
+               v.serialize_attribute("score", score)
+       end
+end
+
+redef class Person
+       serialize
+
+       redef fun core_serialize_to(v) do
+               super
+               v.serialize_attribute("name", name)
+               v.serialize_attribute("email", email)
+               v.serialize_attribute("gravatar", gravatar)
+       end
+end
diff --git a/src/doc/commands/commands.nit b/src/doc/commands/commands.nit
new file mode 100644 (file)
index 0000000..25206d4
--- /dev/null
@@ -0,0 +1,21 @@
+# 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.
+
+module commands
+
+import commands::commands_base
+import commands::commands_model
+import commands::commands_graph
+import commands::commands_usage
+import commands::commands_parser
diff --git a/src/doc/commands/commands_base.nit b/src/doc/commands/commands_base.nit
new file mode 100644 (file)
index 0000000..fce0b61
--- /dev/null
@@ -0,0 +1,326 @@
+# 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.
+
+# Documentation commands
+#
+# A DocCommand returns data about a model, an entity or a piece of documentation.
+#
+# Each command assumes a different goal like getting the comment of an entity,
+# getting a list of packages, getting an UML class diagram etc.
+#
+# Commands are used by documentation tools to build up documentation ressources
+# like Nitweb, Nitx, Nitdoc or documentation cards within READMEs.
+module commands_base
+
+import model::model_index
+import catalog
+
+# Documentation command
+#
+# An abstract command that works on a ModelView.
+#
+# Since they are used by a wide variety of clients, initialization of DocCommands
+# works in two steps.
+#
+# First, you pass the data you already have to the command at init:
+# ~~~nitish
+# var c1 = new CmdEntity(view, mentity_name = "Array")
+# var c2 = new CmdEntity(view, mentity = my_entity)
+# ~~~
+#
+# Then, you call `init_command` to initialize the missing field from the stub data:
+# ~~~nitish
+# var r1 = c1.init_command
+# assert c1.mentity != null
+# assert r1 isa CmdSuccess
+#
+# var r2 = c2.init_command
+# assert c2.mentity_name != null
+# assert r2 isa CmdSuccess
+# ~~~
+#
+# See `init_command` for more details about the returned statuses.
+abstract class DocCommand
+
+       # ModelView
+       #
+       # Set of entities and filters used to retrieve data from the model.
+       var view: ModelView
+
+       # Initialize the command
+       #
+       # Returns a command message that gives the status of the command initialization.
+       #
+       # There is 3 categories of messages:
+       # * `CmdSuccess`: when the command that initialized correctly;
+       # * `CmdError`: when the command cannot be initialized;
+       # * `CmdWarning`: when something is wrong with the command but a result still can be produced.
+       #
+       # Warnings are generally used to distinguish empty list or mdoc from no data at all.
+       fun init_command: CmdMessage do return new CmdSuccess
+end
+
+# Command message
+#
+# A message returned by a command.
+# Messages are used to inform the client of the command initialization status and results.
+# Mostly, messages are used to check if a command is in an error state.
+abstract class CmdMessage
+end
+
+# Command Success
+#
+# Returned when the command was performed without any error or warning.
+class CmdSuccess
+       super CmdMessage
+end
+
+# Command Error
+#
+# Command errors are returned when the command cannot provide results because
+# of a problem on the user-end (i.e. Bad command name, MEntity not found etc.).
+abstract class CmdError
+       super CmdMessage
+end
+
+# Command Warning
+#
+# Command warnings are returned when the command cannot provide results because
+# of a problem on the model-end (i.e. No documentation for a MEntity, no code etc.)
+abstract class CmdWarning
+       super CmdMessage
+end
+
+# Basic commands
+
+# A command about a MEntity
+class CmdEntity
+       super DocCommand
+
+       # MEntity this command is about
+       #
+       # Alternatively you can provide a `mentity_name`.
+       var mentity: nullable MEntity = null is optional, writable
+
+       # Name of the mentity this command is about
+       #
+       # Alternatively you can directly provide the `mentity`.
+       var mentity_name: nullable String = null is optional, writable
+
+       # Initialize the command mentity.
+       #
+       # If not already set, tries to find the `mentity` from the `mentity_name`.
+       #
+       # This function try to match `mentity_name` both as a `full_name` and
+       # `name`.
+       #
+       # Return states:
+       # * `CmdSuccess`: everything was ok;
+       # * `ErrorMEntityNoName`: no `mentity` and no `mentity_name` provided;
+       # * `ErrorMEntityNotFound`: no mentity for `mentity_name`;
+       # * `ErrorMEntityConflict`: `mentity_name` was a non-qualified name that
+       #   returns more than one MEntity.
+       fun init_mentity: CmdMessage do
+               if mentity != null then
+                       if mentity_name == null then mentity_name = mentity.as(not null).full_name
+                       return new CmdSuccess
+               end
+
+               var mentity_name = self.mentity_name
+               if mentity_name == null then return new ErrorMEntityNoName
+
+               mentity = view.mentity_by_full_name(mentity_name)
+               if mentity == null then
+                       var mentities = view.mentities_by_name(mentity_name)
+                       if mentities.is_empty then
+                               var suggest = view.find(mentity_name, 3)
+                               return new ErrorMEntityNotFound(mentity_name, suggest)
+                       else if mentities.length > 1 then
+                               return new ErrorMEntityConflict(mentity_name, mentities)
+                       end
+                       mentity = mentities.first
+               end
+               return new CmdSuccess
+       end
+
+       # See `init_mentity`.
+       redef fun init_command do return init_mentity
+end
+
+# No MEntity name provided
+class ErrorMEntityNoName
+       super CmdError
+       redef fun to_s do return "No entity name provided"
+end
+
+# No MEntity matching `mentity_name`
+class ErrorMEntityNotFound
+       super CmdError
+
+       # MEntity name provided
+       var mentity_name: String
+
+       # Suggestions matching the `mentity_name`.
+       var suggestions: Array[MEntity]
+
+       redef fun to_s do
+               var res = new Buffer
+               res.append "No entity for `{mentity_name}`.\n"
+               res.append "Did you mean: "
+               for mentity in suggestions do
+                       res.append " `{mentity.full_name}`"
+                       if mentity != suggestions.last then res.append ","
+               end
+               return res.write_to_string
+       end
+end
+
+# Multiple MEntities matching `mentity_name`
+class ErrorMEntityConflict
+       super CmdError
+
+       # MEntity name provided
+       var mentity_name: String
+
+       # Conflicts for `mentity_name`
+       var conflicts: Array[MEntity]
+
+       redef fun to_s do
+               var res = new Buffer
+               res.append "Multiple entities for `{mentity_name}`:"
+               for mentity in conflicts do
+                       res.append " `{mentity.full_name}`"
+                       if mentity != conflicts.last then res.append ","
+               end
+               return res.write_to_string
+       end
+end
+
+# A command that returns a list of results
+abstract class CmdList
+       super DocCommand
+
+       # Type of result
+       type ITEM: Object
+
+       # Limit the items in the list
+       var limit: nullable Int = null is optional, writable
+
+       # Page to display
+       var page: nullable Int = null is optional, writable
+
+       # Total number of ret
+       var count: nullable Int = null is optional, writable
+
+       # Total number of pages
+       var max: nullable Int = null is optional, writable
+
+       # Comparator used to sort the list
+       var sorter: nullable Comparator = null is writable
+
+       # Items in the list
+       var results: nullable Array[ITEM] = null is writable
+
+       # `init_command` is used to factorize the sorting and pagination of results
+       #
+       # See `init_results` for the result list initialization.
+       redef fun init_command do
+               var res = super
+               if not res isa CmdSuccess then return res
+               res = init_results
+               if not res isa CmdSuccess then return res
+               sort
+               paginate
+               return res
+       end
+
+       # Initialize the `results` list
+       #
+       # This method must be redefined by CmdList subclasses.
+       fun init_results: CmdMessage do return new CmdSuccess
+
+       # Sort `mentities` with `sorter`
+       fun sort do
+               var results = self.results
+               if results == null then return
+               var sorter = self.sorter
+               if sorter == null then return
+               sorter.sort(results)
+       end
+
+       # Paginate the results
+       #
+       # This methods keeps only a subset of `results` depending on the current `page` and the
+       # number of elements to return set by `limit`.
+       #
+       # The `count` can be specified when `results` does not contain all the results.
+       # For example when the results are already limited from a DB statement.
+       fun paginate do
+               var results = self.results
+               if results == null then return
+
+               var limit = self.limit
+               if limit == null then return
+
+               var page = self.page
+               if page == null or page <= 0 then page = 1
+
+               var count = self.count
+               if count == null then count = results.length
+
+               var max = count / limit
+               if max == 0 then
+                       page = 1
+                       max = 1
+               else if page > max then
+                       page = max
+               end
+
+               var lstart = (page - 1) * limit
+               var lend = limit
+               if lstart + lend > count then lend = count - lstart
+               self.results = results.subarray(lstart, lend)
+               self.max = max
+               self.limit = limit
+               self.page = page
+               self.count = count
+       end
+end
+
+# A list of mentities
+abstract class CmdEntities
+       super CmdList
+
+       redef type ITEM: MEntity
+
+       redef var sorter = new MEntityNameSorter
+end
+
+# A command about a MEntity that returns a list of mentities
+abstract class CmdEntityList
+       super CmdEntity
+       super CmdEntities
+
+       autoinit(view, mentity, mentity_name, limit, page, count, max)
+
+       redef fun init_command do
+               var res = init_mentity
+               if not res isa CmdSuccess then return res
+               res = init_results
+               if not res isa CmdSuccess then return res
+               sort
+               paginate
+               return res
+       end
+end
diff --git a/src/doc/commands/commands_catalog.nit b/src/doc/commands/commands_catalog.nit
new file mode 100644 (file)
index 0000000..be823d0
--- /dev/null
@@ -0,0 +1,341 @@
+# 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.
+
+# Commands to retrieve Catalog related data
+module commands_catalog
+
+import commands_model
+
+# A DocCommand based on a Catalog
+abstract class CmdCatalog
+       super DocCommand
+
+       # Catalog to query at
+       var catalog: Catalog
+end
+
+# A CmdSearch command using a Catalog
+class CmdCatalogSearch
+       super CmdCatalog
+       super CmdSearch
+
+       autoinit(view, catalog, query, limit, page, count, max)
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+
+               var query = self.query
+               if query == null then return new ErrorNoQuery
+               sorter = null
+
+               var index = view.index
+
+               # lookup by name prefix
+               var matches = index.find_by_name_prefix(query).uniq.
+                       sort(lname_sorter, name_sorter, kind_sorter)
+               matches = matches.rerank.sort(vis_sorter, score_sorter)
+
+               # lookup by tags
+               var malus = matches.length
+               if catalog.tag2proj.has_key(query) then
+                       for mpackage in catalog.tag2proj[query] do
+                               matches.add new IndexMatch(mpackage, malus)
+                               malus += 1
+                       end
+                       matches = matches.uniq.rerank.sort(vis_sorter, score_sorter)
+               end
+
+               # lookup by full_name prefix
+               malus = matches.length
+               var full_matches = new IndexMatches
+               for match in index.find_by_full_name_prefix(query).
+                       sort(lfname_sorter, fname_sorter) do
+                       match.score += 1
+                       full_matches.add match
+               end
+               matches = matches.uniq
+
+               # lookup by similarity
+               malus = matches.length
+               var sim_matches = new IndexMatches
+               for match in index.find_by_similarity(query).sort(score_sorter, lname_sorter, name_sorter) do
+                       if match.score > query.length then break
+                       match.score += 1
+                       sim_matches.add match
+               end
+               matches.add_all sim_matches
+               matches = matches.uniq
+               results = matches.rerank.sort(vis_sorter, score_sorter).mentities
+               return res
+       end
+
+       private var score_sorter = new ScoreComparator
+       private var vis_sorter = new VisibilityComparator
+       private var name_sorter = new NameComparator
+       private var lname_sorter = new NameLengthComparator
+       private var fname_sorter = new FullNameComparator
+       private var lfname_sorter = new FullNameLengthComparator
+       private var kind_sorter = new MEntityComparator
+end
+
+# Retrieve the catalog metadata for a MPackage
+class CmdMetadata
+       super CmdEntity
+
+       # MPackage metadata retrieved
+       var metadata: nullable MPackageMetadata = null is optional, writable
+
+       redef fun init_command do
+               if metadata != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MPackage then
+                       metadata = mentity.metadata
+               else
+                       return new WarningNoMetadata(mentity)
+               end
+               return res
+       end
+end
+
+# No metadata for `mentity`
+class WarningNoMetadata
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No metadata for `{mentity.full_name}`"
+end
+
+# Retrieve the packages in the catalog
+class CmdCatalogPackages
+       super CmdCatalog
+       super CmdEntities
+
+       autoinit(view, catalog, limit, page, count, max)
+
+       redef var sorter = new CatalogScoreSorter(catalog) is lazy
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+
+               results = catalog.mpackages.values.to_a
+               return res
+       end
+end
+
+# Retrieve the catalog stats
+class CmdCatalogStats
+       super CmdCatalog
+
+       # Retrieved catalog statistics
+       var stats: nullable CatalogStats = null is optional, writable
+
+       redef fun init_command do
+               super
+               self.stats = catalog.catalog_stats
+               return new CmdSuccess
+       end
+end
+
+# Retrieve the catalog tags list
+class CmdCatalogTags
+       super CmdCatalog
+
+       # Sorter to sort tags alphabetically
+       var tags_sorter = new CatalogTagsSorter is optional, writable
+
+       # Count of packages by tag
+       var packages_count_by_tags: nullable ArrayMap[String, Int] = null is optional, writable
+
+       redef fun init_command do
+               super
+               var tags_to_projects = new ArrayMap[String, Int]
+               var tags = catalog.tag2proj.keys.to_a
+               tags_sorter.sort(tags)
+               for tag in tags do
+                       if not catalog.tag2proj.has_key(tag) then continue
+                       tags_to_projects[tag] = catalog.tag2proj[tag].length
+               end
+               packages_count_by_tags = tags_to_projects
+               return new CmdSuccess
+       end
+end
+
+# Retrieve the packages for a tag
+class CmdCatalogTag
+       super CmdCatalogPackages
+
+       autoinit(view, catalog, tag, limit, page, count, max)
+
+       # The tag to retrieve
+       var tag: nullable String = null is optional, writable
+
+       redef fun init_command do
+               var tag = self.tag
+               if tag == null then return new ErrorNoTag
+
+               if not catalog.tag2proj.has_key(tag) then return new ErrorTagNotFound(tag)
+               return super
+       end
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+
+               results = catalog.tag2proj[tag].to_a
+               return res
+       end
+end
+
+# No tag name provided
+class ErrorNoTag
+       super CmdError
+
+       redef fun to_s do return "No tag name provided"
+end
+
+# No tag with this name in the catalog
+class ErrorTagNotFound
+       super CmdError
+
+       # The tag that was not found
+       var tag: String
+
+       redef fun to_s do return "No tag found for `{tag}`"
+end
+
+# Retrieve a person from the catalog
+class CmdCatalogPerson
+       super CmdCatalog
+
+       # Person to retrieve
+       #
+       # You can also pass a `person_name`.
+       var person: nullable Person = null is optional, writable
+
+       # Name of the person to retrieve
+       #
+       # You can also pass a `person` instance.
+       var person_name: nullable String = null is optional, writable
+
+       # Initialize the `person` result
+       fun init_person: CmdMessage do
+               var person = self.person
+               if person != null then
+                       person_name = person.name
+                       return new CmdSuccess
+               end
+
+               var name = self.person_name
+               if name == null then return new ErrorNoPerson
+               if not catalog.name2person.has_key(name) then return new ErrorPersonNotFound(name)
+               self.person = catalog.name2person[name]
+               return new CmdSuccess
+       end
+
+       redef fun init_command do
+               init_person
+               return super
+       end
+end
+
+# No person instance or name provided
+class ErrorNoPerson
+       super CmdError
+
+       redef fun to_s do return "No person provided"
+end
+
+# No person found with this name
+class ErrorPersonNotFound
+       super CmdError
+
+       # Name of the person that was not found
+       var name: String
+
+       redef fun to_s do return "No person found for `{name}`"
+end
+
+# Retrieve the packages maintained by a person
+class CmdCatalogMaintaining
+       super CmdCatalogPerson
+       super CmdCatalogPackages
+
+       autoinit(view, catalog, person, person_name, limit, page, count, max)
+
+       redef fun init_command do return super
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+               var res = super
+               if not res isa CmdSuccess then return res
+               var person = self.person.as(not null)
+
+               if not catalog.maint2proj.has_key(person) then return res
+               results = catalog.maint2proj[person]
+               return res
+       end
+end
+
+# Retrieve the packages contributed by a person
+class CmdCatalogContributing
+       super CmdCatalogPerson
+       super CmdCatalogPackages
+
+       autoinit(view, catalog, person, person_name, limit, page, count, max)
+
+       # Include maintained packages?
+       #
+       # Default is `false`.
+       var maintaining = false is optional, writable
+
+       # FIXME linearization
+       redef fun init_command do return super
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var person = self.person.as(not null)
+
+               if not catalog.contrib2proj.has_key(person) then return res
+
+               var maint2proj = null
+               if catalog.maint2proj.has_key(person) then
+                       maint2proj = catalog.maint2proj[person]
+               end
+
+               var results = new Array[MPackage]
+               for mpackage in catalog.contrib2proj[person] do
+                       if not maintaining and maint2proj != null and maint2proj.has(mpackage) then continue
+                       results.add mpackage
+               end
+               self.results = results
+               return res
+       end
+end
diff --git a/src/doc/commands/commands_docdown.nit b/src/doc/commands/commands_docdown.nit
new file mode 100644 (file)
index 0000000..9ec8294
--- /dev/null
@@ -0,0 +1,175 @@
+# 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.
+
+# Doc down related queries
+module commands_docdown
+
+import commands::commands_parser
+import commands::commands_html
+
+intrude import doc_down
+intrude import markdown::wikilinks
+
+# Retrieve the MDoc summary
+#
+# List all MarkdownHeading found and their ids.
+class CmdSummary
+       super CmdComment
+
+       # Markdown processor used to parse the headlines
+       var markdown_processor: nullable MarkdownProcessor = null is optional, writable
+
+       # Resulting summary
+       #
+       # Associates each headline to its id.
+       var summary: nullable ArrayMap[String, HeadLine] = null is optional, writable
+
+       redef fun init_command do
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               var markdown_processor = self.markdown_processor
+               if markdown_processor == null then
+                       markdown_processor = new MarkdownProcessor
+                       self.markdown_processor = markdown_processor
+               end
+
+               var mdoc = self.mdoc
+               if mdoc == null then
+                       mdoc = if fallback then mentity.mdoc_or_fallback else mentity.mdoc
+                       self.mdoc = mdoc
+               end
+               if mdoc == null then return new WarningNoMDoc(mentity)
+
+               markdown_processor.process(mdoc.md_documentation.write_to_string)
+
+               var summary = new ArrayMap[String, HeadLine]
+               summary.add_all markdown_processor.decorator.headlines
+               self.summary = summary
+               return res
+       end
+end
+
+# Custom Markdown processor able to process doc commands
+class CmdDecorator
+       super NitdocDecorator
+
+       redef type PROCESSOR: CmdMarkdownProcessor
+
+       # View used by wikilink commands to find model entities
+       var view: ModelView
+
+       redef fun add_span_code(v, buffer, from, to) do
+               var text = new FlatBuffer
+               buffer.read(text, from, to)
+               var name = text.write_to_string
+               name = name.replace("nullable ", "")
+               var mentity = try_find_mentity(view, name)
+               if mentity == null then
+                       super
+               else
+                       v.add "<code>"
+                       v.emit_text mentity.html_link.write_to_string
+                       v.add "</code>"
+               end
+       end
+
+       private fun try_find_mentity(view: ModelView, text: String): nullable MEntity do
+               var mentity = view.mentity_by_full_name(text)
+               if mentity != null then return mentity
+
+               var mentities = view.mentities_by_name(text)
+               if mentities.is_empty then
+                       return null
+               else if mentities.length > 1 then
+                       # TODO smart resolve conflicts
+               end
+               return mentities.first
+       end
+
+       redef fun add_wikilink(v, token) do
+               v.render_wikilink(token, view)
+       end
+end
+
+# Same as `InlineDecorator` but with wikilink commands handling
+class CmdInlineDecorator
+       super InlineDecorator
+
+       redef type PROCESSOR: CmdMarkdownProcessor
+
+       # View used by wikilink commands to find model entities
+       var view: ModelView
+
+       redef fun add_wikilink(v, token) do
+               v.render_wikilink(token, view)
+       end
+end
+
+# Custom MarkdownEmitter for commands
+class CmdMarkdownProcessor
+       super MarkdownProcessor
+
+       # Parser used to process doc commands
+       var parser: CommandParser
+
+       # Render a wikilink
+       fun render_wikilink(token: TokenWikiLink, model: ModelView) do
+               var link = token.link
+               if link == null then return
+               var name = token.name
+               if name != null then link = "{name} | {link}"
+
+               var cmd = link.write_to_string
+               if cmd.is_empty then
+                       var error = new CmdParserError("Empty wikilink")
+                       emit_text error.to_html.write_to_string
+                       return
+               end
+
+               var command = parser.parse(cmd)
+               var error = parser.error
+
+               # If not a command, try a comment command
+               if command == null and error isa CmdParserError then
+                       error = null
+                       command = new CmdEntity(parser.view, mentity_name = cmd)
+                       var status = command.parser_init(cmd, new HashMap[String, String])
+                       if not status isa CmdSuccess then error = status
+               end
+
+               if error isa CmdError then
+                       emit_text error.to_html.write_to_string
+                       return
+               end
+               if error isa CmdWarning then
+                       emit_text error.to_html.write_to_string
+               end
+               add command.as(not null).to_html
+       end
+end
+
+redef class Text
+       # Read `self` between `nstart` and `nend` (excluded) and writte chars to `out`.
+       private fun read(out: FlatBuffer, nstart, nend: Int): Int do
+               var pos = nstart
+               while pos < length and pos < nend do
+                       out.add self[pos]
+                       pos += 1
+               end
+               if pos == length then return -1
+               return pos
+       end
+end
diff --git a/src/doc/commands/commands_graph.nit b/src/doc/commands/commands_graph.nit
new file mode 100644 (file)
index 0000000..7cb0f97
--- /dev/null
@@ -0,0 +1,392 @@
+# 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.
+
+# Graph commands
+#
+# Commands that return graphical representations about a Model or a MEntity.
+module commands_graph
+
+import commands_model
+
+import uml
+import dot
+
+# An abstract command that returns a dot graph
+abstract class CmdGraph
+       super DocCommand
+
+       # Rendering format
+       #
+       # Default is `dot`.
+       # See `allowed_formats`.
+       var format = "dot" is optional, writable
+
+       # Allowed rendering formats.
+       #
+       # Can be `dot` or `svg`.
+       var allowed_formats: Array[String] = ["dot", "svg"]
+
+       # Dot to render
+       var dot: nullable Writable = null is optional, writable
+
+       # Render `dot` depending on `format`
+       fun render: nullable Writable do
+               var dot = self.dot
+               if dot == null then return null
+               if format == "svg" then
+                       var proc = new ProcessDuplex("dot", "-Tsvg")
+                       var svg = proc.write_and_read(dot.write_to_string)
+                       proc.close
+                       proc.wait
+                       return svg
+               end
+               return dot
+       end
+
+       redef fun init_command do
+               if not allowed_formats.has(format) then
+                       return new ErrorBadGraphFormat(format, allowed_formats)
+               end
+               return super
+       end
+end
+
+# Bad graph format requested
+class ErrorBadGraphFormat
+       super CmdError
+
+       # Provided format
+       var format: String
+
+       # Allowed formats
+       var allowed_formats: Array[String]
+
+       redef fun to_s do
+               var allowed_values = new Buffer
+               for allowed in allowed_formats do
+                       allowed_values.append "`{allowed}`"
+                       if allowed != allowed_formats.last then
+                               allowed_values.append ", "
+                       end
+               end
+               return "Bad format `{format}`. Allowed values are {allowed_values.write_to_string}."
+       end
+end
+
+# UML command
+#
+# Return an UML diagram about a `mentity`.
+class CmdUML
+       super CmdEntity
+       super CmdGraph
+
+       autoinit(view, mentity, mentity_name, format, uml)
+
+       # UML model to return
+       var uml: nullable UMLModel = null is optional, writable
+
+       redef fun init_command do
+               if uml != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if mentity isa MClass then
+                       uml = new UMLModel(view, view.mainmodule)
+               else if mentity isa MModule then
+                       uml = new UMLModel(view, view.mainmodule)
+               else
+                       return new WarningNoUML(mentity)
+               end
+               return res
+       end
+
+       redef fun render do
+               var uml = self.uml
+               if uml == null then return null
+               if mentity isa MClass then
+                       dot = uml.generate_class_uml.write_to_string
+               else if mentity isa MModule then
+                       dot = uml.generate_package_uml.write_to_string
+               end
+               return super
+       end
+end
+
+# No UML model for `mentity`
+class WarningNoUML
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No UML for `{mentity.full_name}`"
+end
+
+# Render a hierarchy graph for `mentity` if any.
+class CmdInheritanceGraph
+       super CmdEntity
+       super CmdGraph
+
+       autoinit(view, mentity, mentity_name, pdepth, cdepth, format, graph)
+
+       # Parents depth to display
+       var pdepth: nullable Int = null is optional, writable
+
+       # Children depth to display
+       var cdepth: nullable Int = null is optional, writable
+
+       # Inheritance graph to return
+       var graph: nullable InheritanceGraph = null is optional, writable
+
+       redef fun init_command do
+               if graph != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               graph = new InheritanceGraph(mentity, view)
+               return res
+       end
+
+       redef fun render do
+               var graph = self.graph
+               if graph == null then return ""
+               self.dot = graph.draw(pdepth, cdepth).to_dot
+               return super
+       end
+end
+
+# Graph for mentity hierarchies
+#
+# Recursively build parents and children list from a `center`.
+class InheritanceGraph
+
+       # MEntity at the center of this graph
+       var center: MEntity
+
+       # ModelView used to filter graph
+       var view: ModelView
+
+       # Graph generated
+       var graph: DotGraph is lazy do
+               var graph = new DotGraph("package_diagram", "digraph")
+
+               graph["compound"] = "true"
+               graph["rankdir"] = "BT"
+               graph["ranksep"] = 0.3
+               graph["nodesep"] = 0.3
+
+               graph.nodes_attrs["margin"] = 0.1
+               graph.nodes_attrs["width"] = 0
+               graph.nodes_attrs["height"] = 0
+               graph.nodes_attrs["fontsize"] = 10
+               graph.nodes_attrs["fontname"] = "helvetica"
+
+               graph.edges_attrs["dir"] = "none"
+               graph.edges_attrs["color"] = "gray"
+
+               return graph
+       end
+
+       # Build the graph
+       fun draw(parents_depth, children_depth: nullable Int): DotGraph do
+               draw_node center
+               draw_parents(center, parents_depth)
+               draw_children(center, children_depth)
+               return graph
+       end
+
+       private var nodes = new HashMap[MEntity, DotElement]
+       private var done_parents = new HashSet[MEntity]
+       private var done_children = new HashSet[MEntity]
+
+       # Recursively draw parents of mentity
+       fun draw_parents(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
+               if done_parents.has(mentity) then return
+               done_parents.add mentity
+               current_depth = current_depth or else 0
+               if max_depth != null and current_depth >= max_depth then
+                       from_dotdotdot(mentity)
+                       return
+               end
+               var parents = mentity.collect_parents(view)
+               if parents.length > 10 then
+                       from_dotdotdot(mentity)
+                       return
+               end
+               for parent in parents do
+                       if parent isa MModule then
+                               var mgroup = parent.mgroup
+                               if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
+                       end
+                       if parent isa MGroup then
+                               if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
+                       end
+                       draw_edge(mentity, parent)
+               end
+               for parent in parents do
+                       if parent isa MModule then
+                               var mgroup = parent.mgroup
+                               if mgroup != null and mgroup.default_mmodule == parent then parent = mgroup
+                       end
+                       if parent isa MGroup then
+                               if parent.mpackage.mgroups.first == parent then parent = parent.mpackage
+                       end
+                       draw_parents(parent, max_depth, current_depth + 1)
+               end
+       end
+
+       # Recursively draw children of mentity
+       fun draw_children(mentity: MEntity, max_depth: nullable Int, current_depth: nullable Int) do
+               if done_children.has(mentity) then return
+               done_children.add mentity
+               current_depth = current_depth or else 0
+               if max_depth != null and current_depth >= max_depth then
+                       to_dotdotdot(mentity)
+                       return
+               end
+               var children = mentity.collect_children(view)
+               if children.length > 10 then
+                       to_dotdotdot(mentity)
+                       return
+               end
+               for child in children do
+                       if child isa MGroup then
+                               if child.mpackage.mgroups.first == child then child = child.mpackage
+                       end
+                       draw_edge(child, mentity)
+               end
+               for child in children do
+                       if child isa MGroup then
+                               if child.mpackage.mgroups.first == child then child = child.mpackage
+                       end
+                       draw_children(child, max_depth, current_depth + 1)
+               end
+       end
+
+       # Draw a node from a `mentity`
+       fun draw_node(mentity: MEntity): DotElement do
+               if nodes.has_key(mentity) then return nodes[mentity]
+               var node: DotElement = mentity.to_dot_node
+               if mentity == center then node = highlight(node)
+               nodes[mentity] = node
+               graph.add node
+               return node
+       end
+
+       private var edges = new HashMap2[MEntity, MEntity, DotEdge]
+
+       # Draw a edges between two mentities
+       fun draw_edge(from, to: MEntity): DotEdge do
+               if edges.has(from, to) then return edges[from, to].as(not null)
+               if edges.has(to, from) then return edges[to, from].as(not null)
+               var nfrom = draw_node(from)
+               var nto = draw_node(to)
+               var edge = new DotEdge(nfrom, nto)
+               edges[from, to] = edge
+               graph.add edge
+               return edge
+       end
+
+       private var to_dots = new HashMap[MEntity, DotElement]
+
+       # Create a link from `mentity` to a `...` node
+       fun to_dotdotdot(mentity: MEntity): DotEdge do
+               var nto = draw_node(mentity)
+               var dots = to_dots.get_or_null(mentity)
+               if dots == null then
+                       dots = dotdotdot("{nto.id}...")
+                       to_dots[mentity] = dots
+               end
+               graph.add dots
+               var edge = new DotEdge(dots, nto)
+               graph.add edge
+               return edge
+       end
+
+       private var from_dots = new HashMap[MEntity, DotElement]
+
+       # Create a link from a `...` node to a `mentity`
+       fun from_dotdotdot(mentity: MEntity): DotEdge do
+               var nfrom = draw_node(mentity)
+               var dots = to_dots.get_or_null(mentity)
+               if dots == null then
+                       dots = dotdotdot("...{nfrom.id}")
+                       from_dots[mentity] = dots
+               end
+               graph.add dots
+               var edge = new DotEdge(dots, nfrom)
+               graph.add edge
+               return edge
+       end
+
+       # Change the border color of the node
+       fun highlight(dot: DotElement): DotElement do
+               dot["color"] = "#1E9431"
+               return dot
+       end
+
+       # Generate a `...` node
+       fun dotdotdot(id: String): DotNode do
+               var node = new DotNode(id)
+               node["label"] = "..."
+               node["shape"] = "none"
+               return node
+       end
+end
+
+redef class MEntity
+       # Return `self` as a DotNode
+       fun to_dot_node: DotNode do
+               var node = new DotNode(full_name)
+               node["label"] = name
+               return node
+       end
+end
+
+redef class MPackage
+       redef fun to_dot_node do
+               var node = super
+               node["shape"] = "tab"
+               return node
+       end
+end
+
+redef class MGroup
+       redef fun to_dot_node do
+               var node = super
+               node["shape"] = "folder"
+               return node
+       end
+end
+
+redef class MModule
+       redef fun to_dot_node do
+               var node = super
+               node["shape"] = "note"
+               return node
+       end
+end
+
+redef class MClass
+       redef fun to_dot_node do
+               var node = super
+               node["shape"] = "box"
+               return node
+       end
+end
diff --git a/src/doc/commands/commands_html.nit b/src/doc/commands/commands_html.nit
new file mode 100644 (file)
index 0000000..ce5d89a
--- /dev/null
@@ -0,0 +1,123 @@
+# 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.
+
+# Render commands results as HTML
+module commands_html
+
+import commands::commands_graph
+import commands::commands_usage
+
+import templates::templates_html
+import doc_down
+import highlight
+
+redef class DocCommand
+
+       # Render results as a HTML string
+       fun to_html: Writable do return "<p class='text-danger'>Not yet implemented</p>"
+end
+
+redef class CmdMessage
+
+       # Render the message as a HTML string
+       fun to_html: Writable is abstract
+end
+
+redef class CmdError
+       redef fun to_html do return "<p class='text-danger'>Error: {to_s}</p>"
+end
+
+redef class CmdWarning
+       redef fun to_html do return "<p class='text-warning'>Warning: {to_s}</p>"
+end
+
+# Model commands
+
+redef class CmdEntity
+       redef fun to_html do
+               var mentity = self.mentity
+               if mentity == null then return ""
+               return mentity.html_link
+       end
+end
+
+redef class CmdEntities
+       redef fun to_html do
+               var mentities = self.results
+               if mentities == null then return ""
+
+               var tpl = new Template
+               tpl.add "<ul>"
+               for mentity in mentities do
+                       var mdoc = mentity.mdoc_or_fallback
+                       tpl.add "<li>"
+                       tpl.add mentity.html_link
+                       if mdoc != null then
+                               tpl.add " - "
+                               tpl.add mdoc.html_synopsis
+                       end
+                       tpl.add "</li>"
+               end
+               tpl.add "</ul>"
+               return tpl.write_to_string
+       end
+end
+
+redef class CmdComment
+       redef fun to_html do
+               var mentity = self.mentity
+               if mentity == null then return ""
+
+               var mdoc = self.mdoc
+               var tpl = new Template
+               tpl.add "<h3>"
+               # FIXME comments left here until I figure out what to do about the presentation options
+               # if not opts.has_key("no-link") then
+                       tpl.add mentity.html_link
+               # end
+               if mdoc != null then
+                       # if not opts.has_key("no-link") and not opts.has_key("no-synopsis") then
+                               tpl.add " - "
+                       # end
+                       # if not opts.has_key("no-synopsis") then
+                               tpl.add mdoc.html_synopsis
+                       # end
+               end
+               tpl.add "</h3>"
+               if mdoc != null then
+                       # if not opts.has_key("no-comment") then
+                               tpl.add mdoc.html_comment
+                       # end
+               end
+               return tpl.write_to_string
+       end
+end
+
+redef class CmdCode
+       redef fun to_html do
+               var output = render
+               if output == null then return ""
+               return "<pre>{output.write_to_string}</pre>"
+       end
+end
+
+# Graph commands
+
+redef class CmdGraph
+       redef fun to_html do
+               var output = render
+               if output == null then return ""
+               return output.write_to_string
+       end
+end
diff --git a/src/doc/commands/commands_http.nit b/src/doc/commands/commands_http.nit
new file mode 100644 (file)
index 0000000..83f2ef1
--- /dev/null
@@ -0,0 +1,165 @@
+# 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.
+
+# Initialize commands from HTTP requests
+#
+# FIXME: this module is pretty tied up to the nitwed routes.
+# To be more generic, param names should be extracted as variables.
+module commands_http
+
+import commands
+import commands::commands_catalog
+import nitcorn::vararg_routes
+
+redef class DocCommand
+       # Init the command from an HTTPRequest
+       fun http_init(req: HttpRequest): CmdMessage do return init_command
+end
+
+redef class CmdEntity
+
+
+       redef fun http_init(req) do
+               var name = req.param("id")
+               if name != null then name = name.from_percent_encoding
+               self.mentity_name = name
+
+               return super
+       end
+end
+
+redef class CmdList
+       redef fun http_init(req) do
+               limit = req.int_arg("l")
+               page = req.int_arg("p")
+               return super
+       end
+end
+
+# Error Handling
+
+# Message handling
+
+redef class CmdMessage
+       # HTTP code to return for this message
+       var http_status_code = 200
+end
+
+redef class CmdError
+       redef var http_status_code = 400
+end
+
+redef class CmdWarning
+       redef var http_status_code = 404
+end
+
+redef class ErrorMEntityNoName
+       redef var http_status_code = 400
+end
+
+redef class ErrorMEntityNotFound
+       redef var http_status_code = 404
+end
+
+redef class ErrorMEntityConflict
+       redef var http_status_code = 300
+end
+
+# CmdModel
+
+redef class CmdComment
+       redef fun http_init(req) do
+               full_doc = req.bool_arg("full_doc") or else true
+               fallback = req.bool_arg("fallback") or else true
+               format = req.string_arg("format") or else "raw"
+               return super
+       end
+end
+
+redef class CmdAncestors
+       redef fun http_init(req) do
+               parents = req.bool_arg("parents") or else true
+               return super
+       end
+end
+
+redef class CmdDescendants
+       redef fun http_init(req) do
+               children = req.bool_arg("children") or else true
+               return super
+       end
+end
+
+redef class CmdEntityList
+       # FIXME avoid linearization conflict
+       redef fun http_init(req) do return super
+end
+
+redef class CmdSearch
+       redef fun http_init(req) do
+               query = req.string_arg("q")
+               return super
+       end
+end
+
+redef class CmdModelEntities
+       redef fun http_init(req) do
+               kind = req.string_arg("kind") or else "all"
+               return super
+       end
+end
+
+redef class CmdCode
+       redef fun http_init(req) do
+               format = req.string_arg("format") or else "raw"
+               return super
+       end
+end
+
+# CmdGraph
+
+redef class CmdGraph
+       redef fun http_init(req) do
+               format = req.string_arg("format") or else "dot"
+               return super
+       end
+end
+
+redef class CmdInheritanceGraph
+       redef fun http_init(req) do
+               pdepth = req.int_arg("pdepth")
+               cdepth = req.int_arg("cdepth")
+               return super
+       end
+end
+
+# CmdCatalog
+
+redef class CmdCatalogTag
+       redef fun http_init(req) do
+               var tag = req.param("tid")
+               if tag != null then tag = tag.from_percent_encoding
+               self.tag = tag
+               return super
+       end
+end
+
+redef class CmdCatalogPerson
+       redef fun http_init(req) do
+               var name = req.param("pid")
+               if name != null then name = name.from_percent_encoding
+               self.person_name = name
+               return super
+       end
+end
diff --git a/src/doc/commands/commands_json.nit b/src/doc/commands/commands_json.nit
new file mode 100644 (file)
index 0000000..05fcf95
--- /dev/null
@@ -0,0 +1,153 @@
+# 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.
+
+# Translate command results to json
+module commands_json
+
+import commands::commands_model
+import commands::commands_graph
+import commands::commands_usage
+import commands::commands_catalog
+
+import model::model_json
+import catalog::catalog_json
+import doc::doc_down
+
+redef class DocCommand
+       # Return a JSON Serializable representation of `self` results
+       fun to_json: nullable Serializable is abstract
+end
+
+# Message handling
+
+redef class CmdMessage
+       # Return a JSON Serializable representation of `self`
+       fun to_json: nullable Serializable do
+               var obj = new JsonObject
+               obj["status"] = class_name
+               obj["message"] = to_s
+               return obj
+       end
+end
+
+redef class CmdEntity
+       redef fun to_json do return mentity
+end
+
+redef class CmdList
+       redef fun to_json do
+               var obj = new JsonObject
+               obj["results"] = results
+               obj["page"] = page
+               obj["count"] = count
+               obj["limit"] = limit
+               obj["max"] = max
+               return obj
+       end
+end
+
+redef class CmdEntityList
+       redef fun to_json do return super
+end
+
+# Model commands
+
+redef class CmdComment
+       redef fun to_json do
+               var obj = new JsonObject
+               var render = self.render
+               if render != null then
+                       obj["documentation"] = render.write_to_string
+               end
+               return obj
+       end
+end
+
+redef class CmdCode
+       redef fun to_json do
+               var obj = new JsonObject
+               var node = self.node
+               if node != null then
+                       obj["location"] = node.location
+               end
+               var output = render
+               if output != null then
+                       obj["code"] = output.write_to_string
+               end
+               return obj
+       end
+end
+
+redef class CmdGraph
+       redef fun to_json do
+               var obj = new JsonObject
+               var output = render
+               if output != null then
+                       obj["graph"] = output.write_to_string
+               end
+               return obj
+       end
+end
+
+redef class CmdMetadata
+       redef fun to_json do return metadata
+end
+
+# CmdCatalog
+
+redef class CmdCatalogStats
+       redef fun to_json do return stats
+end
+
+redef class CmdCatalogTags
+       redef fun to_json do return packages_count_by_tags
+end
+
+redef class CmdCatalogTag
+       redef fun to_json do
+               var obj = super.as(JsonObject)
+               obj["tag"] = tag
+               return obj
+       end
+end
+
+redef class CmdCatalogPerson
+       redef fun to_json do return person
+end
+
+redef class CmdCatalogMaintaining
+       redef fun to_json do
+               var obj = new JsonObject
+               obj["person"] = person
+               obj["results"] = results
+               obj["page"] = page
+               obj["count"] = count
+               obj["limit"] = limit
+               obj["max"] = max
+               return obj
+       end
+end
+
+redef class CmdCatalogContributing
+       redef fun to_json do
+               var obj = new JsonObject
+               obj["person"] = person
+               obj["results"] = results
+               obj["page"] = page
+               obj["count"] = count
+               obj["limit"] = limit
+               obj["max"] = max
+               return obj
+       end
+end
diff --git a/src/doc/commands/commands_model.nit b/src/doc/commands/commands_model.nit
new file mode 100644 (file)
index 0000000..139afaf
--- /dev/null
@@ -0,0 +1,515 @@
+# 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.
+
+# Doc commands about a Model or a MEntity
+#
+# This module defines several commands to retrieve data about a Model and MEntities.
+module commands_model
+
+import commands_base
+
+import model::model_collect
+import modelize
+import modelbuilder
+import highlight
+import doc_down
+
+# Retrieve the MDoc related to a MEntity
+class CmdComment
+       super CmdEntity
+
+       # Allow fallback
+       #
+       # If `true`, the command uses `mdoc_or_fallback`.
+       # Default is `true`.
+       var fallback = true is optional, writable
+
+       # Retrieve the full documentation
+       #
+       # If `true`, retrieves the full documentation.
+       # If `false`, retrieves only the synopsis.
+       # Default is `true`.
+       #
+       # Since the rendering the final string (md, html...) depends on the kind of
+       # client, the handling of this option is delegated to submodules.
+       var full_doc = true is optional, writable
+
+       # Format to render the comment
+       #
+       # Can be one of `raw` or `html`.
+       # Default is `raw`.
+       var format = "raw" is optional, writable
+
+       # MDoc to return
+       var mdoc: nullable MDoc = null is optional, writable
+
+       # Same states than `CmdEntity::init_mentity`
+       #
+       # Plus returns `WarningNoMDoc` if no MDoc was found for the MEntity.
+       redef fun init_command do
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mdoc == null then
+                       mdoc = if fallback then mentity.mdoc_or_fallback else mentity.mdoc
+               end
+               if mdoc == null then return new WarningNoMDoc(mentity)
+               return res
+       end
+
+       # Render `mdoc` depending on `full_doc` and `format`
+       fun render: nullable Writable do
+               var mdoc = self.mdoc
+               if mdoc == null then return null
+
+               if format == "html" then
+                       if full_doc then return mdoc.html_documentation
+                       return mdoc.html_synopsis
+               end
+               if full_doc then return mdoc.documentation
+               return mdoc.synopsis
+       end
+end
+
+# No MDoc for `mentity`
+class WarningNoMDoc
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No documentation for `{mentity.full_name}`."
+end
+
+# MEntity ancestors command
+#
+# Retrieve all the ancestors (direct and indirect) of a MEntity.
+class CmdAncestors
+       super CmdEntityList
+
+       # Include direct parents in the ancestors list
+       #
+       # Default is `true`.
+       var parents = true is optional, writable
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               var ancestors = mentity.collect_ancestors(view).to_a
+               if parents then
+                       results = ancestors
+                       return res
+               end
+
+               var parents = mentity.collect_parents(view)
+               var mentities = new HashSet[MEntity]
+               for ancestor in ancestors do
+                       if not parents.has(ancestor) then mentities.add ancestor
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+# MEntity parents command
+class CmdParents
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               results = mentity.collect_parents(view).to_a
+               return res
+       end
+end
+
+# MEntity children command
+class CmdChildren
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               results = mentity.collect_children(view).to_a
+               return res
+       end
+end
+
+# MEntity descendants command
+class CmdDescendants
+       super CmdEntityList
+
+       # Include direct children in the descendants list
+       #
+       # Default is `true`.
+       var children = true is optional, writable
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               var descendants = mentity.collect_descendants(view).to_a
+               if children then
+                       results = descendants
+                       return res
+               end
+
+               var children = mentity.collect_children(view)
+               var mentities = new HashSet[MEntity]
+               for descendant in descendants do
+                       if not children.has(descendant) then mentities.add descendant
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+# Linearization command
+#
+# Collects and linearizes definitions about an MEntity.
+class CmdLinearization
+       super CmdEntityList
+
+       # Same states than `CmdEntity::init_mentity`
+       #
+       # Plus returns `WarningNoLinearization` if no linearization can be computed
+       # from the mentity.
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               sorter = null
+               results = mentity.collect_linearization(view.mainmodule)
+               if results == null then return new WarningNoLinearization(mentity)
+               return res
+       end
+end
+
+# No linearization computed for `mentity`.
+class WarningNoLinearization
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No linearization for `{mentity.full_name}`"
+end
+
+# A free text search command
+class CmdSearch
+       super CmdEntities
+
+       # Free text command string
+       var query: nullable String = null is optional, writable
+
+       # Return states:
+       # * `CmdSuccess`: everything was ok;
+       # * `ErrorNoQuery`: no `query` provided.
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+
+               var query = self.query
+               if query == null then return new ErrorNoQuery
+               sorter = null
+               results = view.find(query)
+               return res
+       end
+end
+
+# No query string given
+class ErrorNoQuery
+       super CmdError
+
+       redef fun to_s do return "Missing search string"
+end
+
+# MEntity feature list
+#
+# Mostly a list of mentities defined in `mentity`.
+class CmdFeatures
+       super CmdEntityList
+
+       # Same as `CmdEntity::init_mentity`
+       #
+       # Plus `WarningNoFeatures` if no features are found for `mentity`.
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               var mentities = new Array[MEntity]
+               if mentity isa MPackage then
+                       mentities.add_all mentity.collect_mgroups(view)
+                       mentities.add_all mentity.collect_mmodules(view)
+               else if mentity isa MGroup then
+                       mentities.add_all mentity.collect_mgroups(view)
+                       mentities.add_all mentity.collect_mmodules(view)
+               else if mentity isa MModule then
+                       mentities.add_all mentity.collect_local_mclassdefs(view)
+               else if mentity isa MClass then
+                       mentities.add_all mentity.collect_intro_mproperties(view)
+                       mentities.add_all mentity.collect_redef_mpropdefs(view)
+               else if mentity isa MClassDef then
+                       mentities.add_all mentity.collect_intro_mpropdefs(view)
+                       mentities.add_all mentity.collect_redef_mpropdefs(view)
+               else if mentity isa MProperty then
+                       mentities.add_all mentity.collect_mpropdefs(view)
+               else
+                       return new WarningNoFeatures(mentity)
+               end
+               self.results = mentities
+               return res
+       end
+end
+
+# TODO remove once the filters/sorters are merged
+class CmdIntros
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MModule then
+                       var mentities = mentity.collect_intro_mclasses(view).to_a
+                       self.results = mentities
+               else if mentity isa MClass then
+                       var mentities = mentity.collect_intro_mproperties(view).to_a
+                       self.results = mentities
+               else if mentity isa MClassDef then
+                       var mentities = mentity.collect_intro_mpropdefs(view).to_a
+                       view.mainmodule.linearize_mpropdefs(mentities)
+                       self.results = mentities
+               else
+                       return new WarningNoFeatures(mentity)
+               end
+               return res
+       end
+end
+
+# TODO remove once the filters/sorters are merged
+class CmdRedefs
+       super CmdEntityList
+
+       redef fun init_command do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MModule then
+                       var mentities = mentity.collect_redef_mclasses(view).to_a
+                       self.results = mentities
+               else if mentity isa MClass then
+                       var mentities = mentity.collect_redef_mproperties(view).to_a
+                       self.results = mentities
+               else if mentity isa MClassDef then
+                       var mentities = mentity.collect_redef_mpropdefs(view).to_a
+                       view.mainmodule.linearize_mpropdefs(mentities)
+                       self.results = mentities
+               else
+                       return new WarningNoFeatures(mentity)
+               end
+               return res
+       end
+end
+
+# TODO remove once the filters/sorters are merged
+class CmdAllProps
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClass then
+                       results = mentity.collect_accessible_mproperties(view).to_a
+               else
+                       return new WarningNoFeatures(mentity)
+               end
+               return res
+       end
+end
+
+# No feature list for `mentity`
+class WarningNoFeatures
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No features for `{mentity.full_name}`"
+end
+
+# Cmd that finds the source code related to an `mentity`
+class CmdCode
+       super CmdEntity
+
+       autoinit(view, modelbuilder, mentity, mentity_name, format)
+
+       # ModelBuilder used to get AST nodes
+       var modelbuilder: ModelBuilder
+
+       # AST node to return
+       var node: nullable ANode = null is optional, writable
+
+       # Rendering format
+       #
+       # Set the output format for this piece of code.
+       # Can be "raw" or "html".
+       # Default is "raw".
+       #
+       # This format can be different than the format used in the command response.
+       # For example you can choose to render code as HTML inside a JSON object response.
+       # Another example is to render raw format to put into a HTML code tag.
+       var format = "raw" is optional, writable
+
+       # Same as `CmdEntity::init_mentity`
+       #
+       # Plus `WarningNoCode` if no code/AST node is found for `mentity`.
+       redef fun init_command do
+               if node != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClass then mentity = mentity.intro
+               if mentity isa MProperty then mentity = mentity.intro
+               node = modelbuilder.mentity2node(mentity)
+               if node == null then return new WarningNoCode(mentity)
+               return res
+       end
+
+       # Render `node` depending on the selected `format`
+       fun render: nullable Writable do
+               var node = self.node
+               if node == null then return null
+               if format == "html" then
+                       var hl = new HighlightVisitor
+                       hl.enter_visit node
+                       return hl.html
+               end
+               # TODO make a raw visitor
+               return node.to_s
+       end
+end
+
+# No code for `mentity`
+class WarningNoCode
+       super CmdWarning
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "No code for `{mentity.full_name}`"
+end
+
+# Model commands
+
+# A command that returns a list of all mentities in a model
+class CmdModelEntities
+       super CmdEntities
+
+       # Kind of mentities to be returned.
+       #
+       # Value must be one of "packages", "groups", "modules", "classes", "classdefs",
+       # "properties", "propdefs" or "all".
+       #
+       # Default is "all".
+       var kind = "all" is optional, writable
+
+       # Default limit is `10`
+       redef var limit = 10
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+
+               var mentities = new Array[MEntity]
+               if kind == "packages" then
+                       mentities = view.mpackages.to_a
+               else if kind == "groups" then
+                       mentities = view.mgroups.to_a
+               else if kind == "modules" then
+                       mentities = view.mmodules.to_a
+               else if kind == "classes" then
+                       mentities = view.mclasses.to_a
+               else if kind == "classdefs" then
+                       mentities = view.mclassdefs.to_a
+               else if kind == "properties" then
+                       mentities = view.mproperties.to_a
+               else if kind == "propdefs" then
+                       mentities = view.mpropdefs.to_a
+               else
+                       mentities = view.mentities.to_a
+               end
+               results = mentities
+               return res
+       end
+end
+
+# A command that returns a random list of mentities from a model
+class CmdRandomEntities
+       super CmdModelEntities
+
+       # Always return `CmdSuccess`
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+               var res = super
+               if not res isa CmdSuccess then return res
+               randomize
+               return res
+       end
+
+       # Randomize mentities order
+       fun randomize do
+               var results = self.results
+               if results == null then return
+               results.shuffle
+       end
+end
diff --git a/src/doc/commands/commands_parser.nit b/src/doc/commands/commands_parser.nit
new file mode 100644 (file)
index 0000000..af75cbe
--- /dev/null
@@ -0,0 +1,286 @@
+# 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.
+
+# A parser that create DocCommand from a string
+#
+# Used by both Nitx and the Markdown doc commands.
+module commands_parser
+
+import commands::commands_model
+import commands::commands_graph
+import commands::commands_usage
+import commands::commands_catalog
+
+# Parse string commands to create DocQueries
+class CommandParser
+
+       # ModelView used to retrieve mentities
+       var view: ModelView
+
+       # ModelBuilder used to retrieve AST nodes
+       var modelbuilder: ModelBuilder
+
+       # Catalog used for catalog commands
+       var catalog: nullable Catalog
+
+       # List of allowed command names for this parser
+       var allowed_commands: Array[String] = [
+       "doc", "code", "lin", "uml", "graph", "search",
+       "parents", "ancestors", "children", "descendants",
+       "param", "return", "new", "call", "defs", "list", "random",
+       "catalog", "stats", "tags", "tag", "person", "contrib", "maintain"] is writable
+
+       # Parse `string` as a DocCommand
+       #
+       # Returns `null` if the string cannot be parsed.
+       # See `error` for the error messages produced by both the parser and the commands.
+       fun parse(string: String): nullable DocCommand do
+               var pos = 0
+               var tmp = new FlatBuffer
+               error = null
+
+               # Parse command name
+               pos = string.read_until(tmp, pos, ':')
+               var name = tmp.write_to_string.trim
+
+               # Check allowed commands
+               if name.is_empty then
+                       error = new CmdParserError("empty command name", 0)
+                       return null
+               end
+               if not allowed_commands.has(name) then
+                       error = new CmdParserError("unknown command name", 0)
+                       return null
+               end
+
+               # Parse the argument
+               tmp.clear
+               pos = string.read_until(tmp, pos + 1, '|')
+               var arg = tmp.write_to_string.trim
+
+               # Parse command options
+               var opts = new HashMap[String, String]
+               while pos < string.length do
+                       # Parse option name
+                       tmp.clear
+                       pos = string.read_until(tmp, pos + 1, ':', ',')
+                       var oname = tmp.write_to_string.trim
+                       var oval = ""
+                       if oname.is_empty then break
+                       # Parse option value
+                       if pos < string.length and string[pos] == ':' then
+                               tmp.clear
+                               pos = string.read_until(tmp, pos + 1, ',')
+                               oval = tmp.write_to_string.trim
+                       end
+                       opts[oname] = oval
+               end
+
+               # Build the command
+               var command = new_command(name)
+               if command == null then
+                       error = new CmdParserError("Unknown command name")
+                       return null
+               end
+
+               # Initialize command from string options
+               var status = command.parser_init(arg, opts)
+               if not status isa CmdSuccess then error = status
+
+               return command
+       end
+
+       # Init a new DocCommand from its `name`
+       #
+       # You must redefine this method to add new custom commands.
+       fun new_command(name: String): nullable DocCommand do
+               # CmdEntity
+               if name == "doc" then return new CmdComment(view)
+               if name == "code" then return new CmdCode(view, modelbuilder)
+               if name == "lin" then return new CmdLinearization(view)
+               if name == "defs" then return new CmdFeatures(view)
+               if name == "parents" then return new CmdParents(view)
+               if name == "ancestors" then return new CmdAncestors(view)
+               if name == "children" then return new CmdChildren(view)
+               if name == "descendants" then return new CmdDescendants(view)
+               if name == "param" then return new CmdParam(view)
+               if name == "return" then return new CmdReturn(view)
+               if name == "new" then return new CmdNew(view, modelbuilder)
+               if name == "call" then return new CmdCall(view, modelbuilder)
+               # CmdGraph
+               if name == "uml" then return new CmdUML(view)
+               if name == "graph" then return new CmdInheritanceGraph(view)
+               # CmdModel
+               if name == "list" then return new CmdModelEntities(view)
+               if name == "random" then return new CmdRandomEntities(view)
+               # CmdCatalog
+               var catalog = self.catalog
+               if catalog != null then
+                       if name == "catalog" then return new CmdCatalogPackages(view, catalog)
+                       if name == "stats" then return new CmdCatalogStats(view, catalog)
+                       if name == "tags" then return new CmdCatalogTags(view, catalog)
+                       if name == "tag" then return new CmdCatalogTag(view, catalog)
+                       if name == "person" then return new CmdCatalogPerson(view, catalog)
+                       if name == "contrib" then return new CmdCatalogContributing(view, catalog)
+                       if name == "maintain" then return new CmdCatalogMaintaining(view, catalog)
+                       if name == "search" then return new CmdCatalogSearch(view, catalog)
+               else
+                       if name == "search" then return new CmdSearch(view)
+               end
+               return null
+       end
+
+       # Error or warning from last call to `parse`
+       var error: nullable CmdMessage = null
+end
+
+# An error produced by the CmdParser
+class CmdParserError
+       super CmdError
+
+       # Error message
+       var message: String
+
+       # Column related to the error
+       var column: nullable Int
+
+       redef fun to_s do return message
+end
+
+redef class DocCommand
+
+       # Initialize the command from the CommandParser data
+       fun parser_init(arg: String, options: Map[String, String]): CmdMessage do
+               return init_command
+       end
+end
+
+redef class CmdEntity
+       redef fun parser_init(mentity_name, options) do
+               self.mentity_name = mentity_name
+               return super
+       end
+end
+
+redef class CmdList
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("limit") and options["limit"].is_int then limit = options["limit"].to_i
+               return super
+       end
+end
+
+# Model commands
+
+redef class CmdComment
+       redef fun parser_init(mentity_name, options) do
+               full_doc = not options.has_key("only-synopsis")
+               fallback = not options.has_key("no-fallback")
+               if options.has_key("format") then format = options["format"]
+               return super
+       end
+end
+
+redef class CmdCode
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("format") then format = options["format"]
+               return super
+       end
+end
+
+redef class CmdSearch
+       redef fun parser_init(mentity_name, options) do
+               query = mentity_name
+               if options.has_key("page") and options["page"].is_int then page = options["page"].to_i
+               return super
+       end
+end
+
+redef class CmdAncestors
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("parents") and options["parents"] == "false" then parents = false
+               return super
+       end
+end
+
+redef class CmdDescendants
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("children") and options["children"] == "false" then children = false
+               return super
+       end
+end
+
+redef class CmdModelEntities
+       redef fun parser_init(kind, options) do
+               self.kind = kind
+               return super
+       end
+end
+
+redef class CmdGraph
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("format") then format = options["format"]
+               return super
+       end
+end
+
+redef class CmdInheritanceGraph
+       redef fun parser_init(mentity_name, options) do
+               if options.has_key("pdepth") and options["pdepth"].is_int then
+                       pdepth = options["pdepth"].to_i
+               end
+               if options.has_key("cdepth") and options["cdepth"].is_int then
+                       cdepth = options["cdepth"].to_i
+               end
+               return super
+       end
+end
+
+# Catalog commands
+
+redef class CmdCatalogTag
+       redef fun parser_init(mentity_name, options) do
+               tag = mentity_name
+               return super
+       end
+end
+
+redef class CmdCatalogPerson
+       redef fun parser_init(mentity_name, options) do
+               person_name = mentity_name
+               return super
+       end
+end
+
+# Utils
+
+redef class Text
+       # Read `self` as raw text until `nend` and append it to the `out` buffer.
+       private fun read_until(out: FlatBuffer, start: Int, nend: Char...): Int do
+               var pos = start
+               while pos < length do
+                       var c = self[pos]
+                       var end_reached = false
+                       for n in nend do
+                               if c == n then
+                                       end_reached = true
+                                       break
+                               end
+                       end
+                       if end_reached then break
+                       out.add c
+                       pos += 1
+               end
+               return pos
+       end
+end
diff --git a/src/doc/commands/commands_usage.nit b/src/doc/commands/commands_usage.nit
new file mode 100644 (file)
index 0000000..3b4efab
--- /dev/null
@@ -0,0 +1,224 @@
+# 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.
+
+# Commands about how mentities are used
+module commands_usage
+
+import commands_model
+import semantize
+
+# Retrieve all the mproperties using `mentity` as a type for its parameters
+#
+# `mentity` must be a MClass or a MClassDef.
+class CmdParam
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if not mentity isa MClass then return new ErrorNotClass(mentity)
+
+               var mentities = new HashSet[MEntity]
+               for mproperty in view.mproperties do
+                       if not mproperty isa MMethod then continue
+                       var msignature = mproperty.intro.msignature
+                       if msignature != null then
+                               for mparam in msignature.mparameters do
+                                       var mtype = mparam.mtype
+                                       if mtype isa MNullableType then mtype = mtype.mtype
+                                       if not mtype isa MClassType then continue
+                                       if mtype.mclass != mentity then continue
+                                       mentities.add mproperty
+                               end
+                       end
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+# Retrieve all the mproperties that return somethinf of the `mentity` type.
+#
+# `mentity` must be a MClass or a MClassDef.
+class CmdReturn
+       super CmdEntityList
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if not mentity isa MClass then return new ErrorNotClass(mentity)
+
+               var mentities = new HashSet[MEntity]
+               for mproperty in view.mproperties do
+                       if not mproperty isa MMethod then continue
+                       var msignature = mproperty.intro.msignature
+                       if msignature != null then
+                               var mtype = msignature.return_mtype
+                               if mtype == null then continue
+                               if mtype isa MNullableType then mtype = mtype.mtype
+                               if not mtype isa MClassType then continue
+                               if mtype.mclass != mentity then continue
+                               mentities.add mproperty
+                       end
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+# Retrieve all the mproperties that initialize `mentity`
+#
+# `mentity` must be a MClass or a MClassDef.
+class CmdNew
+       super CmdEntityList
+
+       autoinit(view, modelbuilder, mentity, mentity_name, limit, page, count, max)
+
+       # ModelBuilder used to retrieve AST nodes
+       var modelbuilder: ModelBuilder
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MClassDef then mentity = mentity.mclass
+               if not mentity isa MClass then return new ErrorNotClass(mentity)
+
+               var mentities = new HashSet[MEntity]
+               for mpropdef in view.mpropdefs do
+                       var visitor = new TypeInitVisitor(mentity)
+                       var npropdef = modelbuilder.mpropdef2node(mpropdef)
+                       if npropdef == null then continue
+                       visitor.enter_visit(npropdef)
+                       if visitor.called then
+                               mentities.add mpropdef
+                       end
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+# Retrieve all the mproperties that call `mentity`
+#
+# `mentity` must be a MProperty or a MPropDef.
+class CmdCall
+       super CmdEntityList
+
+       autoinit(view, modelbuilder, mentity, mentity_name, limit, page, count, max)
+
+       # ModelBuilder used to retrieve AST nodes
+       var modelbuilder: ModelBuilder
+
+       redef fun init_results do
+               if results != null then return new CmdSuccess
+
+               var res = super
+               if not res isa CmdSuccess then return res
+               var mentity = self.mentity.as(not null)
+
+               if mentity isa MPropDef then mentity = mentity.mproperty
+               if not mentity isa MProperty then return new ErrorNotProperty(mentity)
+
+               var mentities = new HashSet[MEntity]
+               for mpropdef in view.mpropdefs do
+                       if mpropdef.mproperty == mentity then continue
+                       var visitor = new MPropertyCallVisitor
+                       var npropdef = modelbuilder.mpropdef2node(mpropdef)
+                       if npropdef == null then continue
+                       visitor.enter_visit(npropdef)
+                       if visitor.calls.has(mentity) then
+                               mentities.add mpropdef
+                       end
+               end
+               results = mentities.to_a
+               return res
+       end
+end
+
+## exploration
+
+# Visitor looking for initialized `MType` (new T).
+#
+# See `NewCmd`.
+private class TypeInitVisitor
+       super Visitor
+
+       var mclass: MClass
+
+       var called = false
+
+       redef fun visit(node)
+       do
+               node.visit_all(self)
+               # look for init
+               if not node isa ANewExpr then return
+               var mtype = node.n_type.mtype
+
+               if mtype == null then return
+               if mtype isa MNullableType then mtype = mtype.mtype
+               if not mtype isa MClassType then return
+               if mtype.mclass != mclass then return
+
+               called = true
+       end
+end
+
+# Visitor looking for calls to a `MProperty` (new T).
+#
+# See `CallCmd`.
+private class MPropertyCallVisitor
+       super Visitor
+
+       var calls = new HashSet[MProperty]
+       redef fun visit(node)
+       do
+               node.visit_all(self)
+               if not node isa ASendExpr then return
+               calls.add node.callsite.as(not null).mproperty
+       end
+end
+
+# The MEntity is not a MClass or a MClassDef
+class ErrorNotClass
+       super CmdError
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "`{mentity.full_name}` is not a class"
+end
+
+# The MEntity is not a MProperty or a MClassDef
+class ErrorNotProperty
+       super CmdError
+
+       # MEntity provided
+       var mentity: MEntity
+
+       redef fun to_s do return "`{mentity.full_name}` is not a property"
+end
diff --git a/src/doc/commands/tests/test_commands.nit b/src/doc/commands/tests/test_commands.nit
new file mode 100644 (file)
index 0000000..d91701b
--- /dev/null
@@ -0,0 +1,66 @@
+# 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.
+
+# Nitunit for doc commands
+module test_commands
+
+import commands_base
+import frontend
+
+# Nitunit test suite specific to commands
+class TestCommands
+
+       # The path to the testunit being executed
+       #
+       # Used to retrieve the path to sources to compile.
+       var test_path: String = "NIT_TESTING_PATH".environ.dirname is lazy
+
+       # Test program to compile
+       #
+       # Default is `$NIT_DIR/tests/test_prog`.
+       var test_src: String = test_path / "../../../../tests/test_prog" is lazy
+
+       # ModelView used for tests
+       var test_view: ModelView is noinit
+
+       # ModelBuilder used for tests
+       var test_builder: ModelBuilder is noinit
+
+       # Initialize test variables
+       #
+       # Must be called before test execution.
+       # FIXME should be before_all
+       fun build_test_env is before do
+               var toolcontext = new ToolContext
+
+               # build model
+               var model = new Model
+               var modelbuilder = new ModelBuilder(model, toolcontext)
+               var mmodules = modelbuilder.parse_full([test_src])
+
+               # process
+               modelbuilder.run_phases
+               toolcontext.run_global_phases(mmodules)
+               var mainmodule = toolcontext.make_main_module(mmodules)
+
+               # Build index
+               var filters = new ModelFilter(
+                       private_visibility,
+                       accept_fictive = false,
+                       accept_test = false)
+
+               test_builder = modelbuilder
+               test_view = new ModelView(model, mainmodule, filters)
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_catalog.nit b/src/doc/commands/tests/test_commands_catalog.nit
new file mode 100644 (file)
index 0000000..9d60706
--- /dev/null
@@ -0,0 +1,113 @@
+# 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.
+
+module test_commands_catalog is test
+
+import test_commands
+import doc::commands::commands_catalog
+
+class TestCommandsCatalog
+       super TestCommands
+       test
+
+       # Catalog used for tests
+       var test_catalog: Catalog is lazy do
+               var catalog = new Catalog(test_builder)
+
+               # Compute the poset
+               for p in test_view.mpackages do
+                       var g = p.root
+                       assert g != null
+                       test_builder.scan_group(g)
+
+                       catalog.deps.add_node(p)
+                       for gg in p.mgroups do for m in gg.mmodules do
+                               for im in m.in_importation.direct_greaters do
+                                       var ip = im.mpackage
+                                       if ip == null or ip == p then continue
+                                       test_catalog.deps.add_edge(p, ip)
+                               end
+                       end
+               end
+               # Build the catalog
+               for mpackage in test_view.mpackages do
+                       catalog.package_page(mpackage)
+                       catalog.git_info(mpackage)
+                       catalog.mpackage_stats(mpackage)
+               end
+               return catalog
+       end
+
+       fun test_cmd_catalog is test do
+               var cmd = new CmdCatalogPackages(test_view, test_catalog)
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).first.full_name == "test_prog"
+       end
+
+       fun test_cmd_catalog_search is test do
+               var cmd = new CmdCatalogSearch(test_view, test_catalog, "test")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).first.full_name == "test_prog"
+               assert cmd.results.as(not null).first isa MPackage
+       end
+
+       fun test_cmd_catalog_stats is test do
+               var cmd = new CmdCatalogStats(test_view, test_catalog)
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.stats != null
+       end
+
+       fun test_cmd_catalog_tags is test do
+               var cmd = new CmdCatalogTags(test_view, test_catalog)
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.packages_count_by_tags.as(not null).length == 2
+       end
+
+       fun test_cmd_catalog_tag is test do
+               var cmd = new CmdCatalogTag(test_view, test_catalog, "test")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.tag == "test"
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_catalog_person is test do
+               var cmd = new CmdCatalogPerson(test_view, test_catalog, person_name = "Alexandre Terrasa")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+       end
+
+       fun test_cmd_catalog_contributing is test do
+               var cmd = new CmdCatalogContributing(test_view, test_catalog,
+                       person_name = "Alexandre Terrasa")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_catalog_maintaining is test do
+               var cmd = new CmdCatalogMaintaining(test_view, test_catalog,
+                       person_name = "Alexandre Terrasa")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+               assert cmd.results.as(not null).length == 2
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_graph.nit b/src/doc/commands/tests/test_commands_graph.nit
new file mode 100644 (file)
index 0000000..04f5966
--- /dev/null
@@ -0,0 +1,51 @@
+# 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.
+
+module test_commands_graph is test
+
+import test_commands
+import doc::commands::commands_graph
+
+class TestCommandsGraph
+       super TestCommands
+       test
+
+       fun test_cmd_uml is test do
+               var cmd = new CmdUML(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.uml != null
+       end
+
+       fun test_cmd_uml_bad_format is test do
+               var cmd = new CmdUML(test_view, mentity_name = "test_prog::Character", format = "foo")
+               var res = cmd.init_command
+               assert res isa ErrorBadGraphFormat
+               assert cmd.uml == null
+       end
+
+       fun test_cmd_uml_not_found is test do
+               var cmd = new CmdUML(test_view, mentity_name = "strength_bonus")
+               var res = cmd.init_command
+               assert res isa WarningNoUML
+               assert cmd.uml == null
+       end
+
+       fun test_cmd_inh_graph is test do
+               var cmd = new CmdInheritanceGraph(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.graph != null
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_http.nit b/src/doc/commands/tests/test_commands_http.nit
new file mode 100644 (file)
index 0000000..4b0f6b7
--- /dev/null
@@ -0,0 +1,289 @@
+# 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.
+
+module test_commands_http is test
+
+import doc::commands::test_commands_catalog
+import doc::commands::commands_http
+
+class TestCommandsHttp
+       super TestCommandsCatalog
+       test
+
+       # Http parser to create Http requests
+       var http_parser = new HttpRequestParser
+
+       # Create a new and initialized http request from the `url` string
+       fun new_request(url: String, route_pattern: nullable String): HttpRequest do
+               if route_pattern == null then route_pattern = "/:id"
+               var route = new Route(route_pattern, new TestDummyAction)
+               var req = http_parser.parse_http_request("GET {url} HTTP/1.0")
+               assert req != null
+               req.uri_params = route.parse_params(req.uri)
+               return req
+       end
+
+       # CmdEntity
+
+       fun test_cmd_http_entity is test do
+               var req = new_request("/test_prog::Character")
+               var cmd = new CmdEntity(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.mentity.as(not null).full_name == "test_prog::Character"
+       end
+
+       fun test_cmd_http_entity_not_found is test do
+               var req = new_request("/Characterzzz")
+               var cmd = new CmdEntity(test_view)
+               var res = cmd.http_init(req)
+               assert res isa ErrorMEntityNotFound
+               assert res.suggestions.first.full_name == "test_prog::Character"
+       end
+
+       fun test_cmd_http_entity_conflict is test do
+               var req = new_request("/+")
+               var cmd = new CmdEntity(test_view)
+               var res = cmd.http_init(req)
+               assert res isa ErrorMEntityConflict
+               assert res.conflicts.length == 2
+       end
+
+       # CmdComment
+
+       fun test_cmd_http_comment is test do
+               var req = new_request("/test_prog::Character")
+               var cmd = new CmdComment(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.mdoc != null
+       end
+
+       fun test_cmd_http_comment_no_mdoc is test do
+               var req = new_request("/test_prog::Character?fallback=false")
+               var cmd = new CmdComment(test_view)
+               var res = cmd.http_init(req)
+               assert res isa WarningNoMDoc
+       end
+
+       # CmdInheritance
+
+       fun test_cmd_http_parents is test do
+               var req = new_request("/test_prog::Warrior")
+               var cmd = new CmdParents(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_http_ancestors is test do
+               var req = new_request("/test_prog::Warrior")
+               var cmd = new CmdAncestors(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 2
+       end
+
+       fun test_cmd_http_ancestorsi_without_parents is test do
+               var req = new_request("/test_prog::Warrior?parents=false")
+               var cmd = new CmdAncestors(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_http_children is test do
+               var req = new_request("/test_prog::Career")
+               var cmd = new CmdChildren(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 3
+       end
+
+       fun test_cmd_http_descendants is test do
+               var req = new_request("/test_prog::Career?children=false")
+               var cmd = new CmdDescendants(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 0
+       end
+
+       # CmdSearch
+
+       fun test_cmd_http_search is test do
+               var req = new_request("/?q=Carer&l=1")
+               var cmd = new CmdSearch(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).first.full_name == "test_prog::Career"
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_http_search_no_query is test do
+               var req = new_request("/")
+               var cmd = new CmdSearch(test_view)
+               var res = cmd.http_init(req)
+               assert res isa ErrorNoQuery
+       end
+
+       # CmdFeatures
+
+       fun test_cmd_http_features is test do
+               var req = new_request("/test_prog::Character?l=10")
+               var cmd = new CmdFeatures(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 10
+       end
+
+       fun test_cmd_http_features_no_features is test do
+               var req = new_request("/test_prog$Career$strength_bonus?l=10")
+               var cmd = new CmdFeatures(test_view)
+               var res = cmd.http_init(req)
+               assert res isa WarningNoFeatures
+       end
+
+       # CmdLinearization
+
+       fun test_cmd_http_lin is test do
+               var req = new_request("/init?l=10")
+               var cmd = new CmdLinearization(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 10
+       end
+
+       fun test_cmd_http_lin_no_lin is test do
+               var req = new_request("/test_prog?l=10")
+               var cmd = new CmdLinearization(test_view)
+               var res = cmd.http_init(req)
+               assert res isa WarningNoLinearization
+       end
+
+       # CmdCode
+
+       fun test_cmd_http_code is test do
+               var req = new_request("/test_prog::Career")
+               var cmd = new CmdCode(test_view, test_builder)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.node isa AStdClassdef
+               assert cmd.format == "raw"
+       end
+
+       fun test_cmd_http_code_format is test do
+               var req = new_request("/test_prog::Career?format=html")
+               var cmd = new CmdCode(test_view, test_builder)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.node isa AStdClassdef
+               assert cmd.format == "html"
+       end
+
+       fun test_cmd_http_code_no_code is test do
+               var req = new_request("/test_prog")
+               var cmd = new CmdCode(test_view, test_builder)
+               var res = cmd.http_init(req)
+               assert res isa WarningNoCode
+       end
+
+       # CmdModel
+
+       fun test_cmd_http_results is test do
+               var req = new_request("/?kind=modules&l=2")
+               var cmd = new CmdModelEntities(test_view, kind = "modules")
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 2
+       end
+
+       fun test_cmd_http_results_random is test do
+               var req = new_request("/?kind=packages&l=1")
+               var cmd = new CmdRandomEntities(test_view, kind = "packages")
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 1
+       end
+
+       # CmdGraph
+
+       fun test_cmd_http_uml is test do
+               var req = new_request("/test_prog::Character?format=svg")
+               var cmd = new CmdUML(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.uml != null
+               assert cmd.format == "svg"
+       end
+
+       fun test_cmd_http_uml_not_found is test do
+               var req = new_request("/strength_bonus")
+               var cmd = new CmdUML(test_view)
+               var res = cmd.http_init(req)
+               assert res isa WarningNoUML
+               assert cmd.format == "dot"
+               assert cmd.uml == null
+       end
+
+       fun test_cmd_http_inh_graph is test do
+               var req = new_request("/test_prog::Character?pdepth=1&cdepth=1")
+               var cmd = new CmdInheritanceGraph(test_view)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.graph != null
+               assert cmd.pdepth == 1
+               assert cmd.cdepth == 1
+       end
+
+       # CmdCatalog
+
+       fun test_cmd_http_catalog_search is test do
+               var req = new_request("/?q=test&l=1")
+               var cmd = new CmdCatalogSearch(test_view, test_catalog)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).first.full_name == "test_prog"
+               assert cmd.results.as(not null).first isa MPackage
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_http_catalog_tag is test do
+               var req = new_request("/test", "/:tid")
+               var cmd = new CmdCatalogTag(test_view, test_catalog)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.tag == "test"
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_http_catalog_person is test do
+               var req = new_request("/Alexandre%20Terrasa", "/:pid")
+               var cmd = new CmdCatalogPerson(test_view, test_catalog)
+               var res = cmd.http_init(req)
+               assert res isa CmdSuccess
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+       end
+end
+
+# Dummy action that does nothing
+#
+# Used to build the test route / http request.
+private class TestDummyAction
+       super Action
+end
+
+redef class CmdUML
+       # FIXME linerarization
+       redef fun http_init(req) do return super
+end
diff --git a/src/doc/commands/tests/test_commands_json.nit b/src/doc/commands/tests/test_commands_json.nit
new file mode 100644 (file)
index 0000000..c7db27e
--- /dev/null
@@ -0,0 +1,142 @@
+# 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.
+
+module test_commands_json is test
+
+import test_commands
+import doc::commands::commands_json
+
+class TestCommandsJson
+       super TestCommands
+       test
+
+       fun print_json(json: nullable Serializable) do
+               if json == null then return
+               print json.serialize_to_json(pretty = true, plain = true)
+       end
+
+       # CmdEntity
+
+       fun test_cmd_entity is test do
+               var cmd = new CmdEntity(test_view, mentity_name = "test_prog::Character")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_comment is test do
+               var cmd = new CmdComment(test_view, mentity_name = "test_prog::Character")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdInheritance
+
+       fun test_cmd_parents is test do
+               var cmd = new CmdParents(test_view, mentity_name = "test_prog::Warrior")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_ancestors is test do
+               var cmd = new CmdAncestors(test_view, mentity_name = "test_prog::Warrior", parents = false)
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_children is test do
+               var cmd = new CmdChildren(test_view, mentity_name = "test_prog::Career")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_descendants is test do
+               var cmd = new CmdDescendants(test_view, mentity_name = "test_prog::Career")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdSearch
+
+       fun test_cmd_search is test do
+               var cmd = new CmdSearch(test_view, query = "Carer", limit = 10)
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdFeatures
+
+       fun test_cmd_features is test do
+               var cmd = new CmdFeatures(test_view, mentity_name = "test_prog::Career")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdLinearization
+
+       fun test_cmd_lin is test do
+               var cmd = new CmdLinearization(test_view, mentity_name = "init")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdModel
+
+       fun test_cmd_mentities is test do
+               var cmd = new CmdModelEntities(test_view, kind = "modules")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       # CmdUsage
+
+       fun test_cmd_new is test do
+               var cmd = new CmdNew(test_view, test_builder, mentity_name = "test_prog::Character")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_call is test do
+               var cmd = new CmdCall(test_view, test_builder, mentity_name = "strength_bonus")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_return is test do
+               var cmd = new CmdReturn(test_view, mentity_name = "test_prog::Character")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+
+       fun test_cmd_param is test do
+               var cmd = new CmdParam(test_view, mentity_name = "test_prog::Character")
+               cmd.init_command
+               print_json cmd.to_json
+       end
+end
+
+redef class nitc::Location
+       serialize
+
+       # Avoid diff on location absolute path
+       redef fun core_serialize_to(v) do
+               v.serialize_attribute("column_end", column_end)
+               v.serialize_attribute("column_start", column_start)
+               v.serialize_attribute("line_end", line_end)
+               v.serialize_attribute("line_start", line_start)
+               var file = self.file
+               if file != null then
+                       v.serialize_attribute("file", "test_location")
+               end
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_ancestors.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_ancestors.res
new file mode 100644 (file)
index 0000000..fcf2f01
--- /dev/null
@@ -0,0 +1,31 @@
+{
+       "results": [{
+               "name": "Object",
+               "class_name": "MClass",
+               "full_name": "test_prog::Object",
+               "mdoc": {
+                       "content": "Root of everything.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 21,
+                               "line_start": 20,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["interface"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 30,
+                       "line_start": 20,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_call.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_call.res
new file mode 100644 (file)
index 0000000..9dbc03c
--- /dev/null
@@ -0,0 +1,60 @@
+{
+       "results": [{
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Character$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 68,
+                       "line_start": 21,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "total_strengh",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Character$total_strengh",
+               "mdoc": {
+                       "content": "The actual strength of the character.\n\nReturns `race.base_strength + career.strength_bonus` or just `race.base_strength` is unemployed.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 2,
+                               "line_end": 42,
+                               "line_start": 39,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 45,
+                       "line_start": 39,
+                       "file": "test_location"
+               },
+               "is_intro": true,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "test_prog::Int"
+                       },
+                       "vararg_rank": -1
+               }
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_children.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_children.res
new file mode 100644 (file)
index 0000000..6106b9b
--- /dev/null
@@ -0,0 +1,79 @@
+{
+       "results": [{
+               "name": "Alcoholic",
+               "class_name": "MClass",
+               "full_name": "test_prog::Alcoholic",
+               "mdoc": {
+                       "content": "Alcoholics are good to nothing escept taking punches.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 61,
+                               "line_start": 60,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 69,
+                       "line_start": 60,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Magician",
+               "class_name": "MClass",
+               "full_name": "test_prog::Magician",
+               "mdoc": {
+                       "content": "Magicians know magic and how to use it.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 50,
+                               "line_start": 49,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 58,
+                       "line_start": 49,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Warrior",
+               "class_name": "MClass",
+               "full_name": "test_prog::Warrior",
+               "mdoc": {
+                       "content": "Warriors are good for fighting.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 39,
+                               "line_start": 38,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 47,
+                       "line_start": 38,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_comment.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_comment.res
new file mode 100644 (file)
index 0000000..5f4128d
--- /dev/null
@@ -0,0 +1,3 @@
+{
+       "documentation": "Characters can be played by both the human or the machine."
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_descendants.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_descendants.res
new file mode 100644 (file)
index 0000000..6106b9b
--- /dev/null
@@ -0,0 +1,79 @@
+{
+       "results": [{
+               "name": "Alcoholic",
+               "class_name": "MClass",
+               "full_name": "test_prog::Alcoholic",
+               "mdoc": {
+                       "content": "Alcoholics are good to nothing escept taking punches.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 61,
+                               "line_start": 60,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 69,
+                       "line_start": 60,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Magician",
+               "class_name": "MClass",
+               "full_name": "test_prog::Magician",
+               "mdoc": {
+                       "content": "Magicians know magic and how to use it.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 50,
+                               "line_start": 49,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 58,
+                       "line_start": 49,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Warrior",
+               "class_name": "MClass",
+               "full_name": "test_prog::Warrior",
+               "mdoc": {
+                       "content": "Warriors are good for fighting.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 39,
+                               "line_start": 38,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 47,
+                       "line_start": 38,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_entity.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_entity.res
new file mode 100644 (file)
index 0000000..63035b5
--- /dev/null
@@ -0,0 +1,25 @@
+{
+       "name": "Character",
+       "class_name": "MClass",
+       "full_name": "test_prog::Character",
+       "mdoc": {
+               "content": "Characters can be played by both the human or the machine.",
+               "location": {
+                       "column_end": 0,
+                       "column_start": 1,
+                       "line_end": 22,
+                       "line_start": 21,
+                       "file": "test_location"
+               }
+       },
+       "visibility": "public",
+       "modifiers": ["class"],
+       "location": {
+               "column_end": 3,
+               "column_start": 1,
+               "line_end": 68,
+               "line_start": 21,
+               "file": "test_location"
+       },
+       "mparameters": []
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_features.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_features.res
new file mode 100644 (file)
index 0000000..402b350
--- /dev/null
@@ -0,0 +1,229 @@
+{
+       "results": [{
+               "name": "_endurance_bonus",
+               "class_name": "MAttribute",
+               "full_name": "test_prog::careers::Career::_endurance_bonus",
+               "mdoc": null,
+               "visibility": "private",
+               "modifiers": ["private", "var"],
+               "location": {
+                       "column_end": 25,
+                       "column_start": 2,
+                       "line_end": 32,
+                       "line_start": 32,
+                       "file": "test_location"
+               },
+               "static_mtype": {
+                       "full_name": "test_prog::Int"
+               }
+       }, {
+               "name": "_intelligence_bonus",
+               "class_name": "MAttribute",
+               "full_name": "test_prog::careers::Career::_intelligence_bonus",
+               "mdoc": null,
+               "visibility": "private",
+               "modifiers": ["private", "var"],
+               "location": {
+                       "column_end": 28,
+                       "column_start": 2,
+                       "line_end": 33,
+                       "line_start": 33,
+                       "file": "test_location"
+               },
+               "static_mtype": {
+                       "full_name": "test_prog::Int"
+               }
+       }, {
+               "name": "_strength_bonus",
+               "class_name": "MAttribute",
+               "full_name": "test_prog::careers::Career::_strength_bonus",
+               "mdoc": null,
+               "visibility": "private",
+               "modifiers": ["private", "var"],
+               "location": {
+                       "column_end": 24,
+                       "column_start": 2,
+                       "line_end": 31,
+                       "line_start": 31,
+                       "file": "test_location"
+               },
+               "static_mtype": {
+                       "full_name": "test_prog::Int"
+               }
+       }, {
+               "name": "endurance_bonus",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::endurance_bonus",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 25,
+                       "column_start": 2,
+                       "line_end": 32,
+                       "line_start": 32,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "test_prog::Int"
+                       },
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "endurance_bonus=",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::endurance_bonus=",
+               "mdoc": null,
+               "visibility": "protected",
+               "modifiers": ["protected", "fun"],
+               "location": {
+                       "column_end": 25,
+                       "column_start": 2,
+                       "line_end": 32,
+                       "line_start": 32,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 1,
+                       "mparams": [{
+                               "is_vararg": false,
+                               "name": "endurance_bonus",
+                               "mtype": {
+                                       "full_name": "test_prog::Int"
+                               }
+                       }],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Career$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 12,
+                       "column_start": 2,
+                       "line_end": 35,
+                       "line_start": 35,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "intelligence_bonus",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::intelligence_bonus",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 28,
+                       "column_start": 2,
+                       "line_end": 33,
+                       "line_start": 33,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "test_prog::Int"
+                       },
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "intelligence_bonus=",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::intelligence_bonus=",
+               "mdoc": null,
+               "visibility": "protected",
+               "modifiers": ["protected", "fun"],
+               "location": {
+                       "column_end": 28,
+                       "column_start": 2,
+                       "line_end": 33,
+                       "line_start": 33,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 1,
+                       "mparams": [{
+                               "is_vararg": false,
+                               "name": "intelligence_bonus",
+                               "mtype": {
+                                       "full_name": "test_prog::Int"
+                               }
+                       }],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "strength_bonus",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::strength_bonus",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 24,
+                       "column_start": 2,
+                       "line_end": 31,
+                       "line_start": 31,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "test_prog::Int"
+                       },
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "strength_bonus=",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Career::strength_bonus=",
+               "mdoc": null,
+               "visibility": "protected",
+               "modifiers": ["protected", "fun"],
+               "location": {
+                       "column_end": 24,
+                       "column_start": 2,
+                       "line_end": 31,
+                       "line_start": 31,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 1,
+                       "mparams": [{
+                               "is_vararg": false,
+                               "name": "strength_bonus",
+                               "mtype": {
+                                       "full_name": "test_prog::Int"
+                               }
+                       }],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_lin.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_lin.res
new file mode 100644 (file)
index 0000000..ae8f477
--- /dev/null
@@ -0,0 +1,217 @@
+{
+       "results": [{
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Object$init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["init"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 30,
+                       "line_start": 20,
+                       "file": "test_location"
+               },
+               "is_intro": true,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Race$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 12,
+                       "column_start": 2,
+                       "line_end": 44,
+                       "line_start": 44,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Career$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 12,
+                       "column_start": 2,
+                       "line_end": 35,
+                       "line_start": 35,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Human$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 55,
+                       "line_start": 51,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Elf$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 77,
+                       "line_start": 73,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Warrior$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 46,
+                       "line_start": 42,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Magician$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 57,
+                       "line_start": 53,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Alcoholic$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 68,
+                       "line_start": 64,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Character$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 68,
+                       "line_start": 21,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "init",
+               "class_name": "MMethodDef",
+               "full_name": "test_prog$Dwarf$Object::init",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["redef", "init"],
+               "location": {
+                       "column_end": 4,
+                       "column_start": 2,
+                       "line_end": 66,
+                       "line_start": 62,
+                       "file": "test_location"
+               },
+               "is_intro": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": null,
+                       "vararg_rank": -1
+               }
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_mentities.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_mentities.res
new file mode 100644 (file)
index 0000000..6a09f6b
--- /dev/null
@@ -0,0 +1,205 @@
+{
+       "results": [{
+               "name": "careers",
+               "class_name": "MModule",
+               "full_name": "test_prog::careers",
+               "mdoc": {
+                       "content": "Careers of the game.\n\nAll characters can have a `Career`.\nA character can also quit its current career and start a new one.\n\nAvailable careers:\n\n * `Warrior`\n * `Magician`\n * `Alcoholic`",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 25,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 69,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "character",
+               "class_name": "MModule",
+               "full_name": "test_prog::character",
+               "mdoc": {
+                       "content": "Characters are playable entity in the world.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 68,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "combat",
+               "class_name": "MModule",
+               "full_name": "test_prog::combat",
+               "mdoc": {
+                       "content": "COmbat interactions between characters.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 67,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "excluded",
+               "class_name": "MModule",
+               "full_name": "excluded::excluded",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 0,
+                       "column_start": 0,
+                       "line_end": 0,
+                       "line_start": 0,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "game",
+               "class_name": "MModule",
+               "full_name": "test_prog::game",
+               "mdoc": {
+                       "content": "A game abstraction for RPG.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 45,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "platform",
+               "class_name": "MModule",
+               "full_name": "test_prog::platform",
+               "mdoc": {
+                       "content": "Declares base types allowed on the platform.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 17,
+                       "column_start": 1,
+                       "line_end": 59,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "races",
+               "class_name": "MModule",
+               "full_name": "test_prog::races",
+               "mdoc": {
+                       "content": "Races of the game.\n\nAll characters belong to a `Race`.\n\nAvailable races:\n\n * `Human`\n * `Dwarf`\n * `Elf`",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 24,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 78,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "rpg",
+               "class_name": "MModule",
+               "full_name": "test_prog::rpg",
+               "mdoc": {
+                       "content": "A worlg RPG abstraction.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 13,
+                       "column_start": 1,
+                       "line_end": 21,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "test_prog",
+               "class_name": "MModule",
+               "full_name": "test_prog::test_prog",
+               "mdoc": {
+                       "content": "A test program with a fake model to check model tools.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 13,
+                       "column_start": 1,
+                       "line_end": 26,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_new.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_new.res
new file mode 100644 (file)
index 0000000..99893dd
--- /dev/null
@@ -0,0 +1,7 @@
+{
+       "results": [],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_param.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_param.res
new file mode 100644 (file)
index 0000000..99893dd
--- /dev/null
@@ -0,0 +1,7 @@
+{
+       "results": [],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_parents.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_parents.res
new file mode 100644 (file)
index 0000000..a746bec
--- /dev/null
@@ -0,0 +1,31 @@
+{
+       "results": [{
+               "name": "Career",
+               "class_name": "MClass",
+               "full_name": "test_prog::Career",
+               "mdoc": {
+                       "content": "A `Career` gives a characteristic bonus or malus to the character.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 30,
+                               "line_start": 29,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["abstract class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 36,
+                       "line_start": 29,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_return.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_return.res
new file mode 100644 (file)
index 0000000..99893dd
--- /dev/null
@@ -0,0 +1,7 @@
+{
+       "results": [],
+       "page": null,
+       "count": null,
+       "limit": null,
+       "max": null
+}
diff --git a/src/doc/commands/tests/test_commands_json.sav/test_cmd_search.res b/src/doc/commands/tests/test_commands_json.sav/test_cmd_search.res
new file mode 100644 (file)
index 0000000..95bf855
--- /dev/null
@@ -0,0 +1,241 @@
+{
+       "results": [{
+               "name": "Career",
+               "class_name": "MClass",
+               "full_name": "test_prog::Career",
+               "mdoc": {
+                       "content": "A `Career` gives a characteristic bonus or malus to the character.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 30,
+                               "line_start": 29,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["abstract class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 36,
+                       "line_start": 29,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "career",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Character::career",
+               "mdoc": {
+                       "content": "The current `Career` of the character.\nReturns `null` if character is unemployed.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 2,
+                               "line_end": 29,
+                               "line_start": 27,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 47,
+                       "column_start": 2,
+                       "line_end": 29,
+                       "line_start": 27,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "nullable test_prog::Career"
+                       },
+                       "vararg_rank": -1
+               }
+       }, {
+               "name": "game",
+               "class_name": "MGroup",
+               "full_name": "test_prog>game>",
+               "mdoc": {
+                       "content": "Gaming group",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 0,
+                               "line_end": 1,
+                               "line_start": 1,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["group"],
+               "location": {
+                       "column_end": 0,
+                       "column_start": 0,
+                       "line_end": 0,
+                       "line_start": 0,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "game",
+               "class_name": "MModule",
+               "full_name": "test_prog::game",
+               "mdoc": {
+                       "content": "A game abstraction for RPG.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 16,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 45,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "races",
+               "class_name": "MModule",
+               "full_name": "test_prog::races",
+               "mdoc": {
+                       "content": "Races of the game.\n\nAll characters belong to a `Race`.\n\nAvailable races:\n\n * `Human`\n * `Dwarf`\n * `Elf`",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 24,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 78,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "careers",
+               "class_name": "MModule",
+               "full_name": "test_prog::careers",
+               "mdoc": {
+                       "content": "Careers of the game.\n\nAll characters can have a `Career`.\nA character can also quit its current career and start a new one.\n\nAvailable careers:\n\n * `Warrior`\n * `Magician`\n * `Alcoholic`",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 25,
+                               "line_start": 15,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["module"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 69,
+                       "line_start": 15,
+                       "file": "test_location"
+               }
+       }, {
+               "name": "Game",
+               "class_name": "MClass",
+               "full_name": "test_prog::Game",
+               "mdoc": {
+                       "content": "This is the interface you have to implement to use ure gaming platform.\n\nsee http://our.platform.com",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 23,
+                               "line_start": 20,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["interface"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 45,
+                       "line_start": 20,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Race",
+               "class_name": "MClass",
+               "full_name": "test_prog::Race",
+               "mdoc": {
+                       "content": "Race determines basic characteristics and what the character will be able to do in life.\n\nThese are base characteristics, they cannot be changed\nbut you can add new ones if needed using refinement.\nObjects and spells cannot change those characteristics.",
+                       "location": {
+                               "column_end": 0,
+                               "column_start": 1,
+                               "line_end": 33,
+                               "line_start": 28,
+                               "file": "test_location"
+                       }
+               },
+               "visibility": "public",
+               "modifiers": ["abstract class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 45,
+                       "line_start": 28,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "Starter",
+               "class_name": "MClass",
+               "full_name": "test_prog::Starter",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["class"],
+               "location": {
+                       "column_end": 3,
+                       "column_start": 1,
+                       "line_end": 23,
+                       "line_start": 21,
+                       "file": "test_location"
+               },
+               "mparameters": []
+       }, {
+               "name": "age",
+               "class_name": "MMethod",
+               "full_name": "test_prog::Character::age",
+               "mdoc": null,
+               "visibility": "public",
+               "modifiers": ["fun"],
+               "location": {
+                       "column_end": 13,
+                       "column_start": 2,
+                       "line_end": 36,
+                       "line_start": 36,
+                       "file": "test_location"
+               },
+               "is_init": false,
+               "msignature": {
+                       "arity": 0,
+                       "mparams": [],
+                       "return_mtype": {
+                               "full_name": "test_prog::Int"
+                       },
+                       "vararg_rank": -1
+               }
+       }],
+       "page": 1,
+       "count": 106,
+       "limit": 10,
+       "max": 10
+}
diff --git a/src/doc/commands/tests/test_commands_model.nit b/src/doc/commands/tests/test_commands_model.nit
new file mode 100644 (file)
index 0000000..9cdfc96
--- /dev/null
@@ -0,0 +1,181 @@
+# 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.
+
+module test_commands_model is test
+
+import test_commands
+import doc::commands::commands_model
+
+class TestCommandsModel
+       super TestCommands
+       test
+
+       # CmdEntity
+
+       fun test_cmd_entity is test do
+               var cmd = new CmdEntity(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.mentity.as(not null).full_name == "test_prog::Character"
+       end
+
+       fun test_cmd_entity_not_found is test do
+               var cmd = new CmdEntity(test_view, mentity_name = "test_prog::Characterzz")
+               var res = cmd.init_command
+               assert res isa ErrorMEntityNotFound
+               assert res.suggestions.first.full_name == "test_prog::Character"
+       end
+
+       fun test_cmd_entity_conflict is test do
+               var cmd = new CmdEntity(test_view, mentity_name = "+")
+               var res = cmd.init_command
+               assert res isa ErrorMEntityConflict
+               assert res.conflicts.length == 2
+       end
+
+       fun test_cmd_entity_no_name is test do
+               var cmd = new CmdEntity(test_view)
+               var res = cmd.init_command
+               assert res isa ErrorMEntityNoName
+       end
+
+       # CmdComment
+
+       fun test_cmd_comment is test do
+               var cmd = new CmdComment(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.mdoc != null
+       end
+
+       fun test_cmd_comment_no_mdoc is test do
+               var cmd = new CmdComment(test_view, mentity_name = "test_prog::Character", fallback = false)
+               var res = cmd.init_command
+               assert res isa WarningNoMDoc
+       end
+
+       # CmdInheritance
+
+       fun test_cmd_parents is test do
+               var cmd = new CmdParents(test_view, mentity_name = "test_prog::Warrior")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_ancestors is test do
+               var cmd = new CmdAncestors(test_view, mentity_name = "test_prog::Warrior")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 2
+       end
+
+       fun test_cmd_ancestorsi_without_parents is test do
+               var cmd = new CmdAncestors(test_view, mentity_name = "test_prog::Warrior", parents = false)
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_children is test do
+               var cmd = new CmdChildren(test_view, mentity_name = "test_prog::Career")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 3
+       end
+
+       fun test_cmd_descendants is test do
+               var cmd = new CmdDescendants(test_view, mentity_name = "test_prog::Career", children = false)
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 0
+       end
+
+       # CmdSearch
+
+       fun test_cmd_search is test do
+               var cmd = new CmdSearch(test_view, query = "Carer")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).first.full_name == "test_prog::Career"
+       end
+
+       fun test_cmd_search_no_query is test do
+               var cmd = new CmdSearch(test_view)
+               var res = cmd.init_command
+               assert res isa ErrorNoQuery
+       end
+
+       # CmdFeatures
+
+       fun test_cmd_features is test do
+               var cmd = new CmdFeatures(test_view, mentity_name = "test_prog::Career")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 10
+       end
+
+       fun test_cmd_features_no_features is test do
+               var cmd = new CmdFeatures(test_view, mentity_name = "test_prog$Career$strength_bonus")
+               var res = cmd.init_command
+               assert res isa WarningNoFeatures
+       end
+
+       # CmdLinearization
+
+       fun test_cmd_lin is test do
+               var cmd = new CmdLinearization(test_view, mentity_name = "init")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               print cmd.results.as(not null)
+               assert cmd.results.as(not null).length == 10
+       end
+
+       fun test_cmd_lin_no_lin is test do
+               var cmd = new CmdLinearization(test_view, mentity_name = "test_prog")
+               var res = cmd.init_command
+               assert res isa WarningNoLinearization
+       end
+
+       # CmdCode
+
+       fun test_cmd_code is test do
+               var cmd = new CmdCode(test_view, test_builder, mentity_name = "test_prog::Career")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.node isa AStdClassdef
+       end
+
+       fun test_cmd_code_no_code is test do
+               var cmd = new CmdCode(test_view, test_builder, mentity_name = "test_prog")
+               var res = cmd.init_command
+               assert res isa WarningNoCode
+       end
+
+       # CmdModel
+
+       fun test_cmd_results is test do
+               var cmd = new CmdModelEntities(test_view, kind = "modules")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 9
+       end
+
+       fun test_cmd_results_random is test do
+               var cmd = new CmdRandomEntities(test_view, kind = "packages")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results.as(not null).length == 2
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_parser.nit b/src/doc/commands/tests/test_commands_parser.nit
new file mode 100644 (file)
index 0000000..e58a157
--- /dev/null
@@ -0,0 +1,298 @@
+# 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.
+
+module test_commands_parser is test
+
+import test_commands
+import doc::commands::tests::test_commands_catalog
+import doc::commands::commands_parser
+
+class TestCommandsParser
+       super TestCommandsCatalog
+       test
+
+       # CmdEntity
+
+       fun test_cmd_parser_comment is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("doc: test_prog::Character")
+               assert cmd isa CmdComment
+               assert parser.error == null
+               assert cmd.mdoc != null
+       end
+
+       # CmdInheritance
+
+       fun test_cmd_parser_parents is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("parents: test_prog::Warrior")
+               assert cmd isa CmdParents
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_parser_ancestors is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("ancestors: test_prog::Warrior")
+               assert cmd isa CmdAncestors
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 2
+       end
+
+       fun test_cmd_parser_ancestors_without_parents is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("ancestors: test_prog::Warrior | parents: false")
+               assert cmd isa CmdAncestors
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 1
+       end
+
+       fun test_cmd_parser_children is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("children: test_prog::Career")
+               assert cmd isa CmdChildren
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 3
+       end
+
+       fun test_cmd_parser_descendants is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("descendants: Object")
+               assert cmd isa CmdDescendants
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 19
+       end
+
+       fun test_cmd_parser_descendants_without_children is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("descendants: Object | children: false")
+               assert cmd isa CmdDescendants
+               assert parser.error == null
+               print cmd.results.as(not null)
+               assert cmd.results.as(not null).length == 7
+       end
+
+       # CmdSearch
+
+       fun test_cmd_parser_search is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("search: Caracter")
+               assert cmd isa CmdSearch
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_search_limit is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("search: Caracter | limit: 2")
+               assert cmd isa CmdSearch
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 2
+       end
+
+       # CmdFeatures
+
+       fun test_cmd_parser_features is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("defs: test_prog::Character")
+               assert cmd isa CmdFeatures
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_features_limit is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("defs: test_prog::Character | limit: 2")
+               assert cmd isa CmdFeatures
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 2
+       end
+
+       # CmdLinearization
+
+       fun test_cmd_parser_lin is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("lin: test_prog::Character")
+               assert cmd isa CmdLinearization
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_lin_limit is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("lin: test_prog::Character | limit: 2")
+               assert cmd isa CmdLinearization
+               assert parser.error == null
+               assert cmd.results.as(not null).length == 2
+       end
+
+       # CmdCode
+
+       fun test_cmd_parser_code is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("code: test_prog::Character")
+               assert cmd isa CmdCode
+               assert parser.error == null
+               assert cmd.node != null
+       end
+
+       # CmdModel
+
+       fun test_cmd_parser_mentities is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("list: modules")
+               assert cmd isa CmdModelEntities
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_results_mentities is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("random: modules")
+               assert cmd isa CmdRandomEntities
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       # CmdGraph
+
+       fun test_cmd_parser_uml is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("uml: test_prog::Career")
+               assert cmd isa CmdUML
+               assert parser.error == null
+               assert cmd.uml != null
+       end
+
+       fun test_cmd_parser_inh_graph is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("graph: test_prog::Career")
+               assert cmd isa CmdInheritanceGraph
+               assert parser.error == null
+               assert cmd.graph != null
+       end
+
+       fun test_cmd_parser_inh_graph_opts is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("graph: test_prog::Career | cdepth: 2, pdepth: 5")
+               assert cmd isa CmdInheritanceGraph
+               assert parser.error == null
+               assert cmd.graph != null
+               assert cmd.cdepth == 2
+               assert cmd.pdepth == 5
+       end
+
+       # CmdUsage
+
+       fun test_cmd_parser_new is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("new: test_prog::Career")
+               assert cmd isa CmdNew
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_call is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("call: strength_bonus")
+               assert cmd isa CmdCall
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_return is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("return: test_prog::Career")
+               assert cmd isa CmdReturn
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_param is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("param: test_prog::Career")
+               assert cmd isa CmdParam
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       # CmdCatalog
+
+       fun test_parser_catalog_search is test do
+               var parser = new CommandParser(test_view, test_builder)
+               var cmd = parser.parse("search: Caracter")
+               assert cmd isa CmdSearch
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_catalog_packages is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("catalog:")
+               assert cmd isa CmdCatalogPackages
+               assert parser.error == null
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_catalog_stats is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("stats:")
+               assert cmd isa CmdCatalogStats
+               assert parser.error == null
+               assert cmd.stats != null
+       end
+
+       fun test_cmd_parser_catalog_tags is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("tags:")
+               assert cmd isa CmdCatalogTags
+               assert parser.error == null
+               assert cmd.packages_count_by_tags != null
+       end
+
+       fun test_cmd_parser_catalog_tag is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("tag: test")
+               assert cmd isa CmdCatalogTag
+               assert parser.error == null
+               assert cmd.tag == "test"
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_catalog_person is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("person: Alexandre Terrasa")
+               assert cmd isa CmdCatalogPerson
+               assert parser.error == null
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+       end
+
+       fun test_cmd_parser_catalog_contributing is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("contrib: Alexandre Terrasa")
+               assert cmd isa CmdCatalogContributing
+               assert parser.error == null
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+               assert cmd.results != null
+       end
+
+       fun test_cmd_parser_catalog_maintaining is test do
+               var parser = new CommandParser(test_view, test_builder, test_catalog)
+               var cmd = parser.parse("maintain: Alexandre Terrasa")
+               assert cmd isa CmdCatalogMaintaining
+               assert parser.error == null
+               assert cmd.person.as(not null).name == "Alexandre Terrasa"
+               assert cmd.results != null
+       end
+end
diff --git a/src/doc/commands/tests/test_commands_usage.nit b/src/doc/commands/tests/test_commands_usage.nit
new file mode 100644 (file)
index 0000000..b07c336
--- /dev/null
@@ -0,0 +1,63 @@
+# 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.
+
+module test_commands_usage is test
+
+import test_commands
+import doc::commands::commands_usage
+
+class TestCommandsUsage
+       super TestCommands
+       test
+
+       fun test_cmd_new is test do
+               var cmd = new CmdNew(test_view, test_builder, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results != null
+       end
+
+       fun test_cmd_new_not_class is test do
+               var cmd = new CmdNew(test_view, test_builder, mentity_name = "strength_bonus")
+               var res = cmd.init_command
+               assert res isa ErrorNotClass
+       end
+
+       fun test_cmd_call is test do
+               var cmd = new CmdCall(test_view, test_builder, mentity_name = "strength_bonus")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results != null
+       end
+
+       fun test_cmd_call_not_prop is test do
+               var cmd = new CmdCall(test_view, test_builder, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa ErrorNotProperty
+       end
+
+       fun test_cmd_return is test do
+               var cmd = new CmdReturn(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results != null
+       end
+
+       fun test_cmd_param is test do
+               var cmd = new CmdParam(test_view, mentity_name = "test_prog::Character")
+               var res = cmd.init_command
+               assert res isa CmdSuccess
+               assert cmd.results != null
+       end
+end
index 7873b64..5951f8e 100644 (file)
@@ -48,7 +48,12 @@ redef class MDoc
                var syn = inline_proc.process(content.first)
                res.add "<span class=\"synopsys nitdoc\">{syn}</span>"
                return res
+       end
 
+       # Renders the synopsis as a HTML comment block.
+       var md_synopsis: Writable is lazy do
+               if content.is_empty then return ""
+               return content.first
        end
 
        # Renders the comment without the synopsis as a HTML comment block.
@@ -58,9 +63,20 @@ redef class MDoc
                return lines_to_html(lines)
        end
 
+       #
+       var md_comment: Writable is lazy do
+               if content.is_empty then return ""
+               var lines = content.to_a
+               lines.shift
+               return lines.join("\n")
+       end
+
        # Renders the synopsis and the comment as a HTML comment block.
        var html_documentation: Writable is lazy do return lines_to_html(content.to_a)
 
+       # Renders the synopsis and the comment as a HTML comment block.
+       var md_documentation: Writable is lazy do return lines_to_md(content.to_a)
+
        # Renders markdown line as a HTML comment block.
        private fun lines_to_html(lines: Array[String]): Writable do
                var res = new Template
@@ -92,7 +108,20 @@ redef class MDoc
                res.add "</div>"
                decorator.current_mdoc = null
                return res
+       end
 
+       private fun lines_to_md(lines: Array[String]): Writable do
+               var res = new Template
+               if not lines.is_empty then
+                       var syn = lines.first
+                       if not syn.has_prefix("    ") and not syn.has_prefix("\t") and
+                         not syn.trim.has_prefix("#") then
+                               lines.shift
+                               res.add "# {syn}\n"
+                       end
+               end
+               res.add lines.join("\n")
+               return res
        end
 end
 
index a0f1399..fa3bd69 100644 (file)
@@ -39,6 +39,7 @@ end
 # A component that display tabbed data.
 class DocTabs
        super BSComponent
+       autoinit(html_id, drop_text, css_classes)
 
        # HTML id of this component.
        var html_id: String
@@ -80,6 +81,7 @@ end
 # A list of tab regrouped in a dropdown
 class DocTabsDrop
        super UnorderedList
+       autoinit(html_id, html_title, items, css_classes)
 
        # HTML id used by the tabs group.
        var html_id: String
@@ -108,6 +110,7 @@ end
 # A panel that goes in a DocTabs.
 class DocTabPanel
        super BSComponent
+       autoinit(html_id, tab_title, html_content, is_active, css_classes)
 
        # HTML id of this panel.
        var html_id: String
@@ -119,7 +122,7 @@ class DocTabPanel
        var html_content: Writable is writable
 
        # Is this panel visible by default?
-       var is_active = false
+       var is_active = false is optional
 
        redef fun rendering do
                var active = ""
@@ -136,6 +139,7 @@ end
 # A ListItem that goes in a DocTabsDrop.
 private class DocTabItem
        super ListItem
+       autoinit(text, target_id, css_classes)
 
        # Panel id to trigger when the link is clicked.
        var target_id: String
index b8107ee..820a9a3 100644 (file)
@@ -143,7 +143,7 @@ end
 
 redef class MParameterType
        redef fun html_link do
-               return new Link.with_title("{mclass.nitdoc_url}#FT_{name.to_cmangle}", name, "formal type")
+               return new Link("{mclass.nitdoc_url}#FT_{name.to_cmangle}", name, "formal type")
        end
 end
 
@@ -191,7 +191,7 @@ redef class MConcern
        private fun html_concern_item: ListItem do
                var lnk = html_link
                var tpl = new Template
-               tpl.add new Link.with_title("#{nitdoc_id}.concern", lnk.text, lnk.title)
+               tpl.add new Link("#{nitdoc_id}.concern", lnk.text, lnk.title)
                var comment = html_synopsis
                if comment != null then
                        tpl.add ": "
index a7a5b9a..9f8390e 100644 (file)
@@ -334,6 +334,8 @@ end
 redef class DocSection
        super BSComponent
 
+       redef fun css_classes do return new Array[String]
+
        redef fun rendering do
                if is_hidden then
                        addn "<a id=\"{html_id}\"></a>"
@@ -349,6 +351,8 @@ end
 redef class DocArticle
        super BSComponent
 
+       redef fun css_classes do return new Array[String]
+
        redef fun rendering do
                if is_hidden then return
                addn "<article{render_css_classes} id=\"{html_id}\">"
index 20ae4ed..8e89dd4 100644 (file)
@@ -539,7 +539,7 @@ redef class MGenericType
        redef fun html_short_signature do
                var lnk = html_link
                var tpl = new Template
-               tpl.add new Link.with_title(lnk.href, mclass.name.html_escape, lnk.title)
+               tpl.add new Link(lnk.href, mclass.name.html_escape, lnk.title)
                tpl.add "["
                for i in [0..arguments.length[ do
                        tpl.add arguments[i].html_short_signature
@@ -552,7 +552,7 @@ redef class MGenericType
        redef fun html_signature do
                var lnk = html_link
                var tpl = new Template
-               tpl.add new Link.with_title(lnk.href, mclass.name.html_escape, lnk.title)
+               tpl.add new Link(lnk.href, mclass.name.html_escape, lnk.title)
                tpl.add "["
                for i in [0..arguments.length[ do
                        tpl.add arguments[i].html_signature
diff --git a/src/doc/templates/templates_html.nit b/src/doc/templates/templates_html.nit
new file mode 100644 (file)
index 0000000..30a2817
--- /dev/null
@@ -0,0 +1,365 @@
+# 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.
+
+# Translate mentities to html blocks.
+module templates_html
+
+import model::model_collect
+import doc::doc_down
+import html::bootstrap
+
+redef class MEntity
+
+       # The MEntity unique ID in the HTML output
+       var html_id: String is lazy do return full_name.to_cmangle
+
+       # The MEntity URL in the HTML output
+       #
+       # You MUST redefine this method.
+       # Depending on your implementation, this URL can be a page URL or an anchor.
+       var html_url: String is lazy do return html_id
+
+       # The MEntity name escaped for HTML
+       var html_name: String is lazy do return name.html_escape
+
+       # The MEntity `full_name` escaped for HTML
+       var html_full_name: String is lazy do return full_name.html_escape
+
+       # Link to the MEntity in the HTML output
+       #
+       # You should redefine this method depending on the organization or your
+       # output.
+       fun html_link: Link do
+               var title = null
+               var mdoc = self.mdoc_or_fallback
+               if mdoc != null then
+                       title = mdoc.synopsis.html_escape
+               end
+               return new Link(html_url, html_name, title)
+       end
+
+       # Returns the complete MEntity declaration decorated with HTML
+       #
+       # Examples:
+       # * MPackage: `package foo`
+       # * MGroup: `group foo`
+       # * MModule: `module foo`
+       # * MClass: `private abstract class Foo[E: Object]`
+       # * MClassDef: `redef class Foo[E]`
+       # * MProperty: `private fun foo(e: Object): Int`
+       # * MPropdef: `redef fun foo(e)`
+       fun html_declaration: Template do
+               var tpl = new Template
+               tpl.add "<span class='signature'>"
+               for modifier in collect_modifiers do
+                       tpl.add "<span class='modifier'>{modifier}</span>&nbsp;"
+               end
+               tpl.add "<span class='name'>{html_link.write_to_string}</span>"
+               tpl.add html_signature(false)
+               tpl.add "</span>"
+               return tpl
+       end
+
+       # Returns the MEntity signature decorated with HTML
+       #
+       # This function only returns the parenthesis and return types.
+       # See `html_declaration` for the full declaration including modifiers and name.
+       fun html_signature(short: nullable Bool): Template do return new Template
+
+       # Returns `full_name` decorated with HTML links
+       fun html_namespace: Template is abstract
+
+       # An icon representative of the mentity
+       fun html_icon: BSIcon do return new BSIcon("tag", ["text-muted"])
+
+       # A li element that can go in a `HTMLList`
+       fun html_list_item: ListItem do
+               var tpl = new Template
+               tpl.add html_namespace
+               var comment = mdoc_or_fallback
+               if comment != null then
+                       tpl.add ": "
+                       tpl.add comment.html_synopsis
+               end
+               return new ListItem(tpl)
+       end
+
+       # CSS classes used to decorate `self`
+       #
+       # Mainly used for icons.
+       var css_classes: Array[String] = collect_modifiers is lazy
+end
+
+redef class MPackage
+       redef fun html_url do return "package_{html_id}.html"
+       redef fun html_namespace do return html_link
+       redef fun html_icon do return new BSIcon("book", ["text-muted"])
+       redef var css_classes = ["public"]
+end
+
+redef class MGroup
+       redef fun html_url do return "group_{html_id}.html"
+       redef fun html_icon do return new BSIcon("folder-close", ["text-muted"])
+
+       redef fun html_namespace do
+               var tpl = new Template
+               var parent = self.parent
+               if parent != null then
+                       tpl.add parent.html_namespace
+                       tpl.add " > "
+               end
+               tpl.add html_link
+               return tpl
+       end
+end
+
+redef class MModule
+       redef fun html_url do return "module_{html_id}.html"
+       redef fun html_icon do return new BSIcon("file", ["text-muted"])
+
+       redef fun html_namespace do
+               var mpackage = self.mpackage
+               var tpl = new Template
+               if mpackage != null then
+                       tpl.add mpackage.html_namespace
+                       tpl.add " :: "
+               end
+               tpl.add html_link
+               return tpl
+       end
+end
+
+redef class MClass
+       redef fun html_url do return "class_{html_id}.html"
+       redef fun html_icon do return new BSIcon("stop", css_classes)
+       redef fun html_signature(short) do return intro.html_signature(short)
+       redef fun css_classes do return super + [visibility.to_s]
+
+       redef fun html_namespace do
+               var mgroup = intro_mmodule.mgroup
+               var tpl = new Template
+               if mgroup != null then
+                       tpl.add mgroup.mpackage.html_namespace
+                       tpl.add " :: "
+               end
+               tpl.add "<span>"
+               tpl.add html_link
+               tpl.add "</span>"
+               return tpl
+       end
+end
+
+redef class MClassDef
+       redef fun html_url do return "{mclass.html_url}#{html_id}"
+       redef fun css_classes do return super + mclass.css_classes
+
+       redef fun html_namespace do
+               var tpl = new Template
+               var mpackage = mmodule.mpackage
+               if mpackage != null and is_intro then
+                       if is_intro then
+                               tpl.add mpackage.html_namespace
+                               tpl.add " $ "
+                       else
+                               tpl.add mmodule.html_namespace
+                               tpl.add " $ "
+                               var intro_mpackage = mclass.intro.mmodule.mpackage
+                               if intro_mpackage != null and mpackage != intro_mpackage then
+                                       tpl.add intro_mpackage.html_namespace
+                                       tpl.add " :: "
+                               end
+                       end
+               else
+                       tpl.add mmodule.html_namespace
+                       tpl.add " $ "
+               end
+               tpl.add html_link
+               return tpl
+       end
+
+       redef fun html_icon do
+               if is_intro then
+                       return new BSIcon("plus", css_classes)
+               end
+               return new BSIcon("asterisk", css_classes)
+       end
+
+       redef fun html_signature(short) do
+               var tpl = new Template
+               var mparameters = mclass.mparameters
+               if not mparameters.is_empty then
+                       tpl.add "["
+                       for i in [0..mparameters.length[ do
+                               tpl.add mparameters[i].html_name
+                               if short == null or not short then
+                                       tpl.add ": "
+                                       tpl.add bound_mtype.arguments[i].html_signature(short)
+                               end
+                               if i < mparameters.length - 1 then tpl.add ", "
+                       end
+                       tpl.add "]"
+               end
+               return tpl
+       end
+end
+
+redef class MProperty
+       redef fun html_url do return "property_{html_id}.html"
+       redef fun html_declaration do return intro.html_declaration
+       redef fun html_signature(short) do return intro.html_signature(short)
+       redef fun html_icon do return new BSIcon("tag", css_classes)
+       redef fun css_classes do return super + [visibility.to_s]
+
+       redef fun html_namespace do
+               var tpl = new Template
+               tpl.add intro_mclassdef.mclass.html_namespace
+               tpl.add " :: "
+               tpl.add intro.html_link
+               return tpl
+       end
+end
+
+redef class MPropDef
+       redef fun html_url do return "{mproperty.html_url}#{html_id}"
+       redef fun css_classes do return super + mproperty.css_classes
+
+       redef fun html_namespace do
+               var tpl = new Template
+               tpl.add mclassdef.html_namespace
+               tpl.add " :: "
+               tpl.add html_link
+               return tpl
+       end
+
+       redef fun html_icon do
+               if is_intro then
+                       return new BSIcon("plus", css_classes)
+               end
+               return new BSIcon("asterisk", css_classes)
+       end
+end
+
+redef class MAttributeDef
+       redef fun html_signature(short) do
+               var static_mtype = self.static_mtype
+               var tpl = new Template
+               if static_mtype != null then
+                       tpl.add ": "
+                       tpl.add static_mtype.html_signature(short)
+               end
+               return tpl
+       end
+end
+
+redef class MMethodDef
+       redef fun html_signature(short) do
+               var new_msignature = self.new_msignature
+               if mproperty.is_root_init and new_msignature != null then
+                       return new_msignature.html_signature(short)
+               end
+               return msignature.as(not null).html_signature(short)
+       end
+end
+
+redef class MVirtualTypeProp
+       redef fun html_link do return mvirtualtype.html_link
+end
+
+redef class MVirtualTypeDef
+       redef fun html_signature(short) do
+               var bound = self.bound
+               var tpl = new Template
+               if bound == null then return tpl
+               tpl.add ": "
+               tpl.add bound.html_signature(short)
+               return tpl
+       end
+end
+
+redef class MType
+       redef fun html_signature(short) do return html_link
+end
+
+redef class MClassType
+       redef fun html_link do return mclass.html_link
+end
+
+redef class MNullableType
+       redef fun html_signature(short) do
+               var tpl = new Template
+               tpl.add "nullable "
+               tpl.add mtype.html_signature(short)
+               return tpl
+       end
+end
+
+redef class MGenericType
+       redef fun html_signature(short) do
+               var lnk = html_link
+               var tpl = new Template
+               tpl.add new Link(lnk.href, mclass.name.html_escape, lnk.title)
+               tpl.add "["
+               for i in [0..arguments.length[ do
+                       tpl.add arguments[i].html_signature(short)
+                       if i < arguments.length - 1 then tpl.add ", "
+               end
+               tpl.add "]"
+               return tpl
+       end
+end
+
+redef class MParameterType
+       redef fun html_link do
+               return new Link("{mclass.html_url}#FT_{name.to_cmangle}", name, "formal type")
+       end
+end
+
+redef class MVirtualType
+       redef fun html_link do return mproperty.intro.html_link
+end
+
+redef class MSignature
+       redef fun html_signature(short) do
+               var tpl = new Template
+               if not mparameters.is_empty then
+                       tpl.add "("
+                       for i in [0..mparameters.length[ do
+                               tpl.add mparameters[i].html_signature(short)
+                               if i < mparameters.length - 1 then tpl.add ", "
+                       end
+                       tpl.add ")"
+               end
+               if short == null or not short then
+                       var return_mtype = self.return_mtype
+                       if return_mtype != null then
+                               tpl.add ": "
+                               tpl.add return_mtype.html_signature(short)
+                       end
+               end
+               return tpl
+       end
+end
+
+redef class MParameter
+       redef fun html_signature(short) do
+               var tpl = new Template
+               tpl.add name
+               if short == null or not short then
+                       tpl.add ": "
+                       tpl.add mtype.html_signature(short)
+               end
+               if is_vararg then tpl.add "..."
+               return tpl
+       end
+end
index 00d52df..61d03a0 100644 (file)
@@ -394,17 +394,6 @@ redef class MModule
 
        redef fun collect_modifiers do return super + ["module"]
 
-       # Collect all module ancestors of `self` (direct and transitive imports)
-       redef fun collect_ancestors(view) do
-               var res = new HashSet[MENTITY]
-               for mentity in in_importation.greaters do
-                       if mentity == self then continue
-                       if not view.accept_mentity(mentity) then continue
-                       res.add mentity
-               end
-               return res
-       end
-
        # Collect all modules directly imported by `self`
        redef fun collect_parents(view) do
                var res = new HashSet[MENTITY]
@@ -880,7 +869,9 @@ redef class MClassDef
                if not is_intro then
                        res.add "redef"
                else
-                       res.add mclass.visibility.to_s
+                       if mclass.visibility != public_visibility then
+                               res.add mclass.visibility.to_s
+                       end
                end
                res.add mclass.kind.to_s
                return res
@@ -895,17 +886,6 @@ redef class MClassDef
                return mclassdefs
        end
 
-       redef fun collect_ancestors(view) do
-               var res = new HashSet[MENTITY]
-               var hierarchy = self.in_hierarchy
-               if hierarchy == null then return res
-               for parent in hierarchy.greaters do
-                       if parent == self or not view.accept_mentity(parent) then continue
-                       res.add parent
-               end
-               return res
-       end
-
        redef fun collect_parents(view) do
                var res = new HashSet[MENTITY]
                var hierarchy = self.in_hierarchy
@@ -1042,7 +1022,9 @@ redef class MPropDef
                if not is_intro then
                        res.add "redef"
                else
-                       res.add mproperty.visibility.to_s
+                       if mproperty.visibility != public_visibility then
+                               res.add mproperty.visibility.to_s
+                       end
                end
                var mprop = self
                if mprop isa MVirtualTypeDef then
index 35c04e8..f8cfb24 100644 (file)
@@ -151,8 +151,7 @@ redef class ModelView
                return index
        end
 
-       # Find mentities by their `name`
-       fun mentities_by_name(name: String): Array[MEntity] do
+       redef fun mentities_by_name(name) do
                if index.name_prefixes.has_key(name) then
                        return index.name_prefixes[name]
                end
index c0b6024..cbf3fe2 100644 (file)
@@ -130,6 +130,15 @@ class ModelView
                return null
        end
 
+       # Searches the MEntities that matches `full_name`.
+       fun mentities_by_name(name: String): Array[MEntity] do
+               var res = new Array[MEntity]
+               for mentity in mentities do
+                       if mentity.name == name then res.add mentity
+               end
+               return res
+       end
+
        # Build an concerns tree with from `self`
        fun to_tree: MEntityTree do
                var v = new ModelTreeVisitor
index 404065b..96d1691 100644 (file)
@@ -13,7 +13,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
@@ -79,7 +79,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 14,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 16,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 17,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract class"],
+       "modifiers": ["abstract class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract class"],
+       "modifiers": ["abstract class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog$Starter",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog$Sys",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 13,
                "column_start": 1,
index a267d33..19495e1 100644 (file)
@@ -13,7 +13,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
@@ -70,7 +70,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 14,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 16,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 17,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract class"],
+       "modifiers": ["abstract class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract class"],
+       "modifiers": ["abstract class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "interface"],
+       "modifiers": ["interface"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog::Starter",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog::Sys",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "class"],
+       "modifiers": ["class"],
        "location": {
                "column_end": 13,
                "column_start": 1,
index 167ec94..426bc6c 100644 (file)
@@ -13,7 +13,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "type"],
+       "modifiers": ["type"],
        "location": {
                "column_end": 28,
                "column_start": 2,
@@ -63,7 +63,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 37,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 55,
                "column_start": 2,
        "full_name": "test_prog$Object$init",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "init"],
+       "modifiers": ["init"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog$Int$unary -",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 21,
                "column_start": 2,
        "full_name": "test_prog$Int$+",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog$Int$-",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog$Int$*",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog$Int$/",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog$Int$>",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 30,
                "column_start": 2,
        "full_name": "test_prog$Int$to_f",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 26,
                "column_start": 2,
        "full_name": "test_prog$Float$+",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog$Float$-",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog$Float$*",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog$Float$/",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog$Float$>",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 32,
                "column_start": 2,
        "full_name": "test_prog$Career$strength_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 24,
                "column_start": 2,
        "full_name": "test_prog$Career$endurance_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 25,
                "column_start": 2,
        "full_name": "test_prog$Career$intelligence_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 28,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 23,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 24,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 15,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 47,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 47,
                "column_start": 2,
        "full_name": "test_prog$Character$quit",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
        "full_name": "test_prog$Character$name",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 17,
                "column_start": 2,
        "full_name": "test_prog$Character$age",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 13,
                "column_start": 2,
        "full_name": "test_prog$Character$sex",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 14,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 51,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
        "full_name": "test_prog$Combatable$hit_points",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 32,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 64,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 71,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 38,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 43,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 51,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 53,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 26,
                "column_start": 2,
        "full_name": "test_prog$Starter$start",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 17,
                "column_start": 2,
        "full_name": "test_prog$Sys$main",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 13,
                "column_start": 1,
index f3c8146..6757986 100644 (file)
@@ -13,7 +13,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "type"],
+       "modifiers": ["type"],
        "location": {
                "column_end": 28,
                "column_start": 2,
@@ -58,7 +58,7 @@
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 37,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 55,
                "column_start": 2,
        "full_name": "test_prog::Object::init",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "init"],
+       "modifiers": ["init"],
        "location": {
                "column_end": 3,
                "column_start": 1,
        "full_name": "test_prog::Int::unary -",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 21,
                "column_start": 2,
        "full_name": "test_prog::Int::+",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog::Int::-",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog::Int::*",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog::Int::/",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
        "full_name": "test_prog::Int::>",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 30,
                "column_start": 2,
        "full_name": "test_prog::Int::to_f",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 26,
                "column_start": 2,
        "full_name": "test_prog::Float::+",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog::Float::-",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog::Float::*",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog::Float::/",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 33,
                "column_start": 2,
        "full_name": "test_prog::Float::>",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "intern", "fun"],
+       "modifiers": ["intern", "fun"],
        "location": {
                "column_end": 32,
                "column_start": 2,
        "full_name": "test_prog::Career::strength_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 24,
                "column_start": 2,
        "full_name": "test_prog::Career::endurance_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 25,
                "column_start": 2,
        "full_name": "test_prog::Career::intelligence_bonus",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 28,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 23,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 24,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 15,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 47,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 47,
                "column_start": 2,
        "full_name": "test_prog::Character::quit",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
        "full_name": "test_prog::Character::name",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 17,
                "column_start": 2,
        "full_name": "test_prog::Character::age",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 13,
                "column_start": 2,
        "full_name": "test_prog::Character::sex",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 14,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 4,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 51,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 29,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
        "full_name": "test_prog::Combatable::hit_points",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 32,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 64,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 71,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 38,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 43,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 51,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 53,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 27,
                "column_start": 2,
                }
        },
        "visibility": "public",
-       "modifiers": ["public", "abstract", "fun"],
+       "modifiers": ["abstract", "fun"],
        "location": {
                "column_end": 26,
                "column_start": 2,
        "full_name": "test_prog::Starter::start",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 17,
                "column_start": 2,
        "full_name": "test_prog::Sys::main",
        "mdoc": null,
        "visibility": "public",
-       "modifiers": ["public", "fun"],
+       "modifiers": ["fun"],
        "location": {
                "column_end": 13,
                "column_start": 1,
index adb9543..f41f9a4 100644 (file)
@@ -36,7 +36,7 @@ class AndroidPlatform
 
        redef fun name do return "android"
 
-       redef fun supports_libgc do return false
+       redef fun supports_libgc do return true
 
        redef fun supports_libunwind do return false
 
@@ -54,22 +54,45 @@ class AndroidToolchain
        do
                var android_project_root = "{root_compile_dir}/android/"
                self.android_project_root = android_project_root
-               return "{android_project_root}/jni/nit_compile/"
+               return "{android_project_root}/app/src/main/cpp/"
        end
 
        redef fun default_outname do return "{super}.apk"
 
+       private fun share_dir: Text
+       do
+               var nit_dir = toolcontext.nit_dir or else "."
+               return (nit_dir/"share").realpath
+       end
+
+       private fun gradlew_dir: Text do return share_dir / "android-gradlew"
+
        redef fun write_files(compile_dir, cfiles)
        do
                var android_project_root = android_project_root.as(not null)
+               var android_app_root = android_project_root/"app"
                var project = new AndroidProject(toolcontext.modelbuilder, compiler.mainmodule)
                var release = toolcontext.opt_release.value
 
+               # Compute the root of the project where could be assets and resources
+               var project_root = "."
+               var mpackage = compiler.mainmodule.first_real_mmodule.mpackage
+               if mpackage != null then
+                       var root = mpackage.root
+                       if root != null then
+                               var filepath = root.filepath
+                               if filepath != null then
+                                       project_root = filepath
+                               end
+                       end
+               end
+
+               # Gather app configs
+               # ---
+
                var app_name = project.name
                if not release then app_name += " Debug"
 
-               var short_project_name = project.short_name
-
                var app_package = project.namespace
                if not release then app_package += "_debug"
 
@@ -82,27 +105,144 @@ class AndroidToolchain
                if app_target_api == null then app_target_api = app_min_api
 
                var app_max_api = ""
-               if project.max_api != null then app_max_api = "android:maxSdkVersion=\"{project.max_api.as(not null)}\""
-
-               # Clear the previous android project, so there is no "existing project warning"
-               # or conflict between Java files of different projects
-               if android_project_root.file_exists then android_project_root.rmdir
-
-               var args = ["android", "-s",
-                       "create", "project",
-                       "--name", short_project_name,
-                       "--target", "android-{app_target_api}",
-                       "--path", android_project_root,
-                       "--package", app_package,
-                       "--activity", short_project_name]
-               toolcontext.exec_and_check(args, "Android project error")
+               if project.max_api != null then app_max_api = "maxSdkVersion  {project.max_api.as(not null)}"
 
-               # create compile_dir
-               var dir = "{android_project_root}/jni/"
-               if not dir.file_exists then dir.mkdir
+               # Create basic directory structure
+               # ---
 
-               dir = compile_dir
-               if not dir.file_exists then dir.mkdir
+               android_project_root.mkdir
+               android_app_root.mkdir
+               (android_app_root/"libs").mkdir
+
+               var android_app_main = android_app_root / "src/main"
+               android_app_main.mkdir
+               (android_app_main / "java").mkdir
+
+               # /app/build.gradle
+               # ---
+
+               # Use the most recent build_tools_version
+               var android_home = "ANDROID_HOME".environ
+               if android_home.is_empty then android_home = "HOME".environ / "Android/Sdk"
+               var build_tools_dir = android_home / "build-tools"
+               var available_versions = build_tools_dir.files
+
+               var build_tools_version
+               if available_versions.is_empty then
+                       print_error "Error: found no Android build-tools, install one or set ANDROID_HOME."
+                       return
+               else
+                       alpha_comparator.sort available_versions
+                       build_tools_version = available_versions.last
+               end
+
+               # Gather ldflags for Android
+               var ldflags = new Array[String]
+               var platform_name = "android"
+               for mmodule in compiler.mainmodule.in_importation.greaters do
+                       if mmodule.ldflags.keys.has(platform_name) then
+                               ldflags.add_all mmodule.ldflags[platform_name]
+                       end
+               end
+
+               # Platform version for OpenGL ES
+               var platform_version = ""
+               if ldflags.has("-lGLESv3") then
+                       platform_version = "def platformVersion = 18"
+               else if ldflags.has("-lGLESv2") then
+                       platform_version = "def platformVersion = 12"
+               end
+
+               # TODO make configurable client-side
+               var compile_sdk_version = app_target_api
+
+               var local_build_gradle = """
+apply plugin: 'com.android.application'
+
+{{{platform_version}}}
+
+android {
+    compileSdkVersion {{{compile_sdk_version}}}
+    buildToolsVersion "{{{build_tools_version}}}"
+
+    defaultConfig {
+        applicationId "{{{app_package}}}"
+        minSdkVersion {{{app_min_api}}}
+        {{{app_max_api}}}
+        targetSdkVersion {{{app_target_api}}}
+        versionCode {{{project.version_code}}}
+        versionName "{{{app_version}}}"
+        ndk {
+            abiFilters 'armeabi', 'armeabi-v7a', 'x86'
+        }
+        externalNativeBuild {
+            cmake {
+                cppFlags ""
+            }
+        }
+    }
+
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+
+    externalNativeBuild {
+        cmake {
+            path "src/main/cpp/CMakeLists.txt"
+        }
+    }
+
+    lintOptions {
+       abortOnError false
+    }
+}
+
+dependencies {
+    implementation fileTree(dir: 'libs', include: ['*.jar'])
+}
+"""
+               local_build_gradle.write_to_file "{android_project_root}/app/build.gradle"
+
+               # TODO add 'arm64-v8a' and 'x86_64' to `abiFilters` when the min API is available
+
+               # ---
+               # Other, smaller files
+
+               # /build.gradle
+               var global_build_gradle = """
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.0.0'
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        jcenter()
+    }
+}
+"""
+               global_build_gradle.write_to_file "{android_project_root}/build.gradle"
+
+               # /settings.gradle
+               var settings_gradle = """
+include ':app'
+"""
+               settings_gradle.write_to_file "{android_project_root}/settings.gradle"
+
+               # /gradle.properties
+               var gradle_properties = """
+org.gradle.jvmargs=-Xmx1536m
+"""
+               gradle_properties.write_to_file "{android_project_root}/gradle.properties"
 
                # Insert an importation of the generated R class to all Java files from the FFI
                for mod in compiler.mainmodule.in_importation.greaters do
@@ -113,30 +253,104 @@ class AndroidToolchain
                # compile normal C files
                super
 
+               # ---
+               # /app/src/main/cpp/CMakeLists.txt
+
                # Gather extra C files generated elsewhere than in super
                for f in compiler.extern_bodies do
                        if f isa ExternCFile then cfiles.add(f.filename.basename)
                end
 
-               var project_root = "."
-               var mpackage = compiler.mainmodule.first_real_mmodule.mpackage
-               if mpackage != null then
-                       var root = mpackage.root
-                       if root != null then
-                               var filepath = root.filepath
-                               if filepath != null then
-                                       project_root = filepath
-                               end
+               # Prepare for the CMakeLists format
+               var target_link_libraries = new Array[String]
+               for flag in ldflags do
+                       if flag.has_prefix("-l") then
+                               target_link_libraries.add flag.substring_from(2)
                        end
                end
 
+               # Download the libgc/bdwgc sources
+               var share_dir = share_dir
+               if not share_dir.file_exists then
+                       print "Android project error: Nit share directory not found, please use the environment variable NIT_DIR"
+                       exit 1
+               end
+
+               var bdwgc_dir = "{share_dir}/android-bdwgc/bdwgc"
+               if not bdwgc_dir.file_exists then
+                       toolcontext.exec_and_check(["{share_dir}/android-bdwgc/setup.sh"], "Android project error")
+               end
+
+               # Compile the native app glue lib if used
+               var add_native_app_glue = ""
+               if target_link_libraries.has("native_app_glue") then
+                       add_native_app_glue = """
+add_library(native_app_glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c)
+"""
+               end
+
+               var cmakelists = """
+cmake_minimum_required(VERSION 3.4.1)
+
+{{{add_native_app_glue}}}
+
+
+# libgc/bdwgc
+
+## The source is in the Nit repo
+set(lib_src_DIR {{{bdwgc_dir}}})
+set(lib_build_DIR ../libgc/outputs)
+file(MAKE_DIRECTORY ${lib_build_DIR})
+
+## Config
+add_definitions("-DGC_PTHREADS")
+set(enable_threads TRUE)
+set(CMAKE_USE_PTHREADS_INIT TRUE)
+
+## link_map is already defined in Android
+add_definitions("-DGC_DONT_DEFINE_LINK_MAP")
+
+## Silence warning
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-tautological-pointer-compare")
+
+add_subdirectory(${lib_src_DIR} ${lib_build_DIR} )
+include_directories(${lib_src_DIR}/include)
+
+
+# Nit generated code
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DANDROID -DWITH_LIBGC")
+
+# Export ANativeActivity_onCreate(),
+# Refer to: https://github.com/android-ndk/ndk/issues/381.
+set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate")
+
+#           name      so       source
+add_library(nit_app   SHARED   {{{cfiles.join("\n\t")}}} )
+
+target_include_directories(nit_app PRIVATE ${ANDROID_NDK}/sources/android/native_app_glue)
+
+
+# Link!
+
+target_link_libraries(nit_app gc-lib
+       {{{target_link_libraries.join("\n\t")}}})
+"""
+               cmakelists.write_to_file "{android_app_main}/cpp/CMakeLists.txt"
+
+               # ---
+               # /app/src/main/res/values/strings.xml for app name
+
                # Set the default pretty application name
+               var res_values_dir = "{android_app_main}/res/values/"
+               res_values_dir.mkdir
 """<?xml version="1.0" encoding="utf-8"?>
 <resources>
     <string name="app_name">{{{app_name}}}</string>
-</resources>""".write_to_file "{android_project_root}/res/values/strings.xml"
+</resources>""".write_to_file res_values_dir/"strings.xml"
 
-               # Copy assets, resources and libs where expected by the SDK
+               # ---
+               # Copy assets, resources in the Android project
 
                ## Collect path to all possible folder where we can find the `android` folder
                var app_files = [project_root]
@@ -147,17 +361,22 @@ class AndroidToolchain
                        var assets_dir = path / "assets"
                        if assets_dir.file_exists then
                                assets_dir = assets_dir.realpath
-                               toolcontext.exec_and_check(["cp", "-r", assets_dir, android_project_root], "Android project error")
+                               toolcontext.exec_and_check(["cp", "-r", assets_dir, android_app_main], "Android project error")
                        end
 
                        # Copy the whole `android` folder
                        var android_dir = path / "android"
                        if android_dir.file_exists then
                                android_dir = android_dir.realpath
-                               toolcontext.exec_and_check(["cp", "-r", android_dir, root_compile_dir], "Android project error")
+                               for f in android_dir.files do
+                                       toolcontext.exec_and_check(["cp", "-r", android_dir / f, android_app_main], "Android project error")
+                               end
                        end
                end
 
+               # ---
+               # Generate AndroidManifest.xml
+
                # Is there an icon?
                var resolutions = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi"]
                var icon_available = false
@@ -174,84 +393,43 @@ class AndroidToolchain
                        icon_declaration = "android:icon=\"@drawable/icon\""
                else icon_declaration = ""
 
-               # Also copy over the java files
-               dir = "{android_project_root}/src/"
+               # TODO android:roundIcon
+
+               # Copy the Java sources files
+               var java_dir = android_app_main / "java/"
+               java_dir.mkdir
                for mmodule in compiler.mainmodule.in_importation.greaters do
                        var extra_java_files = mmodule.extra_java_files
                        if extra_java_files != null then for file in extra_java_files do
                                var path = file.filename
-                               path.file_copy_to(dir/path.basename)
+                               path.file_copy_to(java_dir/path.basename)
                        end
                end
 
-               ## Generate Application.mk
-               dir = "{android_project_root}/jni/"
-               """
-APP_ABI := armeabi armeabi-v7a x86
-APP_PLATFORM := android-{{{app_target_api}}}
-""".write_to_file "{dir}/Application.mk"
+               # ---
+               # /app/src/main/AndroidManifest.xml
 
-               ## Generate delegating makefile
-               """
-include $(call all-subdir-makefiles)
-""".write_to_file "{dir}/Android.mk"
-
-               # Gather ldflags for Android
-               var ldflags = new Array[String]
-               var platform_name = "android"
-               for mmodule in compiler.mainmodule.in_importation.greaters do
-                       if mmodule.ldflags.keys.has(platform_name) then
-                               ldflags.add_all mmodule.ldflags[platform_name]
-                       end
-               end
-
-               ### generate makefile into "{compile_dir}/Android.mk"
-               dir = compile_dir
-               """
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_CFLAGS   := -D ANDROID -D WITH_LIBGC
-LOCAL_MODULE    := main
-LOCAL_SRC_FILES := \\
-{{{cfiles.join(" \\\n")}}}
-LOCAL_LDLIBS    := {{{ldflags.join(" ")}}} $(TARGET_ARCH)/libgc.a
-LOCAL_STATIC_LIBRARIES := android_native_app_glue
-
-include $(BUILD_SHARED_LIBRARY)
-
-$(call import-module,android/native_app_glue)
-               """.write_to_file("{dir}/Android.mk")
-
-               ### generate AndroidManifest.xml
-               dir = android_project_root
-               var manifest_file = new FileWriter.open("{dir}/AndroidManifest.xml")
+               var manifest_file = new FileWriter.open(android_app_main / "AndroidManifest.xml")
                manifest_file.write """
 <?xml version="1.0" encoding="utf-8"?>
-<!-- BEGIN_INCLUDE(manifest) -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="{{{app_package}}}"
-        android:versionCode="{{{project.version_code}}}"
-        android:versionName="{{{app_version}}}">
-
-    <uses-sdk
-        android:minSdkVersion="{{{app_min_api}}}"
-        android:targetSdkVersion="{{{app_target_api}}}"
-        {{{app_max_api}}} />
+        package="{{{app_package}}}">
 
     <application
-               android:label="@string/app_name"
                android:hasCode="true"
-               android:debuggable="{{{not release}}}"
+               android:allowBackup="true"
+               android:label="@string/app_name"
                {{{icon_declaration}}}>
 """
 
                for activity in project.activities do
                        manifest_file.write """
         <activity android:name="{{{activity}}}"
-                android:label="@string/app_name"
                 {{{project.manifest_activity_attributes.join("\n")}}}
                 {{{icon_declaration}}}>
+
+            <meta-data android:name="android.app.lib_name" android:value="nit_app" />
+
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
@@ -268,36 +446,8 @@ $(call import-module,android/native_app_glue)
 {{{project.manifest_lines.join("\n")}}}
 
 </manifest>
-<!-- END_INCLUDE(manifest) -->
 """
                manifest_file.close
-
-               ### Link to png sources
-               # libpng is not available on Android NDK
-               # FIXME make optional when we have alternatives to mnit
-               var nit_dir = toolcontext.nit_dir
-               var share_dir =  nit_dir/"share/"
-               if not share_dir.file_exists then
-                       print "Android project error: Nit share directory not found, please use the environment variable NIT_DIR"
-                       exit 1
-               end
-               share_dir = share_dir.realpath
-
-               # Ensure that android-setup-libgc.sh has been executed
-               if not "{share_dir}/libgc/arm/lib".file_exists then
-                       toolcontext.exec_and_check(["{share_dir}/libgc/android-setup-libgc.sh"], "Android project error")
-               end
-
-               # Copy GC files
-               for arch in ["arm", "x86", "mips"] do
-                       dir = android_project_root/arch
-                       dir.mkdir
-                       toolcontext.exec_and_check(["cp", "{share_dir}/libgc/{arch}/lib/libgc.a",
-                               dir/"libgc.a"], "Android project error")
-               end
-
-               toolcontext.exec_and_check(["ln", "-s", "{share_dir}/libgc/arm/include/gc/",
-                       "{compile_dir}/gc"], "Android project error")
        end
 
        redef fun write_makefile(compile_dir, cfiles)
@@ -308,24 +458,18 @@ $(call import-module,android/native_app_glue)
        redef fun compile_c_code(compile_dir)
        do
                var android_project_root = android_project_root.as(not null)
-               var short_project_name = compiler.mainmodule.name.replace("-", "_")
                var release = toolcontext.opt_release.value
 
-               # Compile C code (and thus Nit)
-               toolcontext.exec_and_check(["ndk-build", "-s", "-j", "-C", android_project_root], "Android project error")
-
-               # Generate the apk
-               var args = ["ant", "-f", android_project_root+"/build.xml"]
-               if release then
-                       args.add "release"
-               else args.add "debug"
+               # Compile C and Java code into an APK file
+               var verb = if release then "assembleRelease" else "assembleDebug"
+               var args = [gradlew_dir/"gradlew", verb, "-p", android_project_root]
+               if toolcontext.opt_verbose.value <= 1 then args.add "-q"
                toolcontext.exec_and_check(args, "Android project error")
 
-               # Move the apk to the target
+               # Move the APK to the target
                var outname = outfile(compiler.mainmodule)
-
                if release then
-                       var apk_path = "{android_project_root}/bin/{short_project_name}-release-unsigned.apk"
+                       var apk_path = "{android_project_root}/app/build/outputs/apk/release/app-release-unsigned.apk"
 
                        # Sign APK
                        var keystore_path= "KEYSTORE".environ
@@ -361,7 +505,7 @@ $(call import-module,android/native_app_glue)
                        toolcontext.exec_and_check(args, "Android project error")
                else
                        # Move to the expected output path
-                       args = ["mv", "{android_project_root}/bin/{short_project_name}-debug.apk", outname]
+                       args = ["mv", "{android_project_root}/app/build/outputs/apk/debug/app-debug.apk", outname]
                        toolcontext.exec_and_check(args, "Android project error")
                end
        end
@@ -370,9 +514,9 @@ end
 redef class JavaClassTemplate
        redef fun write_to_files(compdir)
        do
-               var jni_path = "jni/nit_compile/"
+               var jni_path = "cpp/"
                if compdir.has_suffix(jni_path) then
-                       var path = "{compdir.substring(0, compdir.length-jni_path.length)}/src/"
+                       var path = "{compdir.substring(0, compdir.length-jni_path.length)}/java/"
                        return super(path)
                else return super
        end
index 42391c0..afad834 100644 (file)
@@ -15,7 +15,7 @@
 module api_catalog
 
 import api_model
-import catalog
+import catalog::catalog_json
 
 redef class NitwebConfig
 
@@ -301,73 +301,6 @@ redef class Catalog
        end
 end
 
-redef class MPackageMetadata
-       serialize
-
-       redef fun core_serialize_to(v) do
-               super
-               v.serialize_attribute("license", license)
-               v.serialize_attribute("maintainers", maintainers)
-               v.serialize_attribute("contributors", contributors)
-               v.serialize_attribute("tags", tags)
-               v.serialize_attribute("tryit", tryit)
-               v.serialize_attribute("apk", apk)
-               v.serialize_attribute("homepage", homepage)
-               v.serialize_attribute("browse", browse)
-               v.serialize_attribute("git", git)
-               v.serialize_attribute("issues", issues)
-               v.serialize_attribute("first_date", first_date)
-               v.serialize_attribute("last_date", last_date)
-       end
-end
-
-# Catalog statistics
-redef class CatalogStats
-       serialize
-
-       redef fun core_serialize_to(v) do
-               super
-               v.serialize_attribute("packages", packages)
-               v.serialize_attribute("maintainers", maintainers)
-               v.serialize_attribute("contributors", contributors)
-               v.serialize_attribute("tags", tags)
-               v.serialize_attribute("modules", modules)
-               v.serialize_attribute("classes", classes)
-               v.serialize_attribute("methods", methods)
-               v.serialize_attribute("loc", loc)
-       end
-end
-
-# MPackage statistics for the catalog
-redef class MPackageStats
-       serialize
-
-       redef fun core_serialize_to(v) do
-               super
-               v.serialize_attribute("mmodules", mmodules)
-               v.serialize_attribute("mclasses", mclasses)
-               v.serialize_attribute("mmethods", mmethods)
-               v.serialize_attribute("loc", loc)
-               v.serialize_attribute("errors", errors)
-               v.serialize_attribute("warnings", warnings)
-               v.serialize_attribute("warnings_per_kloc", warnings_per_kloc)
-               v.serialize_attribute("documentation_score", documentation_score)
-               v.serialize_attribute("commits", commits)
-               v.serialize_attribute("score", score)
-       end
-end
-
-redef class Person
-       serialize
-
-       redef fun core_serialize_to(v) do
-               super
-               v.serialize_attribute("name", name)
-               v.serialize_attribute("email", email)
-               v.serialize_attribute("gravatar", gravatar)
-       end
-end
-
 redef class MPackage
        # Serialize the full catalog version of `self` to JSON
        #