Merge: gamnit: new services and a lot of bug fixes and performance improvements
authorJean Privat <jean@pryen.org>
Wed, 29 Nov 2017 15:40:53 +0000 (10:40 -0500)
committerJean Privat <jean@pryen.org>
Wed, 29 Nov 2017 15:40:53 +0000 (10:40 -0500)
This PR groups small fixes to gamnit and related packages. It is a general cleanup in preparation for optimizations to the gamnit depth 3D API.

Intro a few new services:
* `ParticleSystem::clear` to remove all live particles.
* `CustomTexture` can be modified and reloaded in GPU memory after the first call to `load`.
* Don't apply dynamic resolution to UI sprites as they are usually lightweight and any change in resolution is easily noticable.

Optimizations:
* Matrix creation and manipulation, in both gamnit and the matrix package.
* Free pixel data from both Nit and Java memory after loading them from the assets folder into the GPU memory.
* Avoids using mallocs in `realtime` services.

Fixes:
* Fix the left and right anchors of the `UICamera`.
* Fix a constant used to ask for antialiasing in the `egl` package.
* Avoid long attribute names in Blinn-Phong shader in case their name is truncated or the attribute is optimized out.
* Fix pointer to the parent GPU texture name in subtextures.
* Improve a few API doc.

Pull-Request: #2586
Reviewed-by: Romain Chanoir <romain.chanoir@viacesi.fr>
Reviewed-by: Jean Privat <jean@pryen.org>

157 files changed:
clib/gc_chooser.c
contrib/asteronits/src/asteronits.nit
contrib/asteronits/src/touch_ui.nit
contrib/benitlux/src/client/android.nit
contrib/nitcc/src/autom.nit
contrib/nitcc/src/nitcc.nit
contrib/nitin/README.md
contrib/nitin/nitin.nit
contrib/rss_downloader/src/rss_downloader.nit
contrib/tnitter/Makefile
examples/calculator/Makefile
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/app/examples/Makefile
lib/app/examples/http_request_example.nit
lib/curl/curl.nit
lib/dom/dom.nit
lib/dom/parser.nit
lib/gamnit/bmfont.nit
lib/gamnit/depth/cardboard.nit
lib/gamnit/depth/depth.nit
lib/gamnit/depth/depth_core.nit
lib/gamnit/depth/more_materials.nit
lib/gamnit/depth/shadow.nit
lib/gamnit/dynamic_resolution.nit
lib/gamnit/egl.nit
lib/gamnit/examples/fonts_showcase/src/fonts_showcase.nit
lib/gamnit/examples/template/src/template.nit
lib/gamnit/examples/triangle/src/portable_triangle.nit
lib/gamnit/flat/flat_core.nit
lib/gamnit/gamnit.nit
lib/gamnit/gamnit_linux.nit
lib/gamnit/textures.nit
lib/gamnit/virtual_gamepad/virtual_gamepad.nit
lib/html/bootstrap.nit
lib/json/dynamic.nit
lib/markdown/man.nit
lib/markdown/markdown.nit
lib/nlp/README.md
lib/nlp/examples/nlp_index.nit [new file with mode: 0644]
lib/nlp/examples/nlp_server.nit [new file with mode: 0644]
lib/nlp/nitnlp.nit [deleted file]
lib/nlp/nlp.nit
lib/nlp/stanford.nit
lib/popcorn/pop_tests.nit
lib/pthreads/pthreads.nit
lib/vsm/vsm.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]
share/man/nitunit.md
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_base.nit
src/doc/doc_down.nit
src/doc/doc_phases/doc_html.nit
src/doc/doc_phases/doc_readme.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/doc/vim_autocomplete.nit
src/interpreter/naive_interpreter.nit
src/metrics/codesmells_metrics.nit
src/metrics/inheritance_metrics.nit
src/metrics/mclasses_metrics.nit
src/metrics/mendel_metrics.nit
src/metrics/method_analyze_metrics.nit
src/metrics/mmodules_metrics.nit
src/metrics/nullables_metrics.nit
src/metrics/rta_metrics.nit
src/model/model.nit
src/model/model_collect.nit
src/model/model_filters.nit [new file with mode: 0644]
src/model/model_index.nit
src/model/model_json.nit
src/model/model_views.nit
src/model/model_visitor.nit
src/model/test_model_json.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/nitdoc.nit
src/nituml.nit
src/nitweb.nit
src/parser/lexer.nit
src/parser/nit.sablecc3xx
src/parser/parser_abs.nit
src/parser/parser_nodes.nit
src/parser/tables_nit.c
src/platform/android.nit
src/test_model_index.nit
src/test_model_visitor.nit
src/testing/README.md
src/testing/testing_suite.nit
src/web/api_catalog.nit
src/web/api_graph.nit
src/web/api_metrics.nit
src/web/api_model.nit
src/web/web_base.nit
tests/nitdoc.args
tests/nitunit.args
tests/sav/nitunit_args11.res
tests/sav/nitunit_args13.res [new file with mode: 0644]
tests/sav/nitunit_args14.res [new file with mode: 0644]
tests/sav/nlp_index.res [new file with mode: 0644]
tests/sav/test_jvm.res
tests/test_nitunit10.nit [new file with mode: 0644]
tests/test_nitunit11.nit [new file with mode: 0644]
tests/test_nitunit8.nit [new file with mode: 0644]
tests/test_nitunit9.nit [new file with mode: 0644]
tests/tests.sh

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 7155977..628fc69 100644 (file)
@@ -64,7 +64,7 @@ redef class App
        private var fx_explosion_ship = new Sound("sounds/explosion_ship.wav")
        private var fx_explosion_asteroids = new Sound("sounds/explosion_asteroids.wav")
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
index 5cae0d4..2264f28 100644 (file)
@@ -20,7 +20,7 @@ import gamnit::virtual_gamepad
 import asteronits
 
 redef class App
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
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 3248e17..bec0f07 100644 (file)
@@ -367,6 +367,7 @@ class Automaton
                # Keep only the good stuff
                states.clear
                states.add_all(goods)
+               states.add(start)
        end
 
        # Generate a minimal DFA
@@ -385,10 +386,9 @@ class Automaton
 
                # split accept states.
                # An accept state is distinct with a non accept state.
-               for s1 in states do
+               for s1 in accept do
                        for s2 in states do
                                if distincts[s1].has(s2) then continue
-                               if not accept.has(s1) then continue
                                if not accept.has(s2) then
                                        distincts[s1].add(s2)
                                        distincts[s2].add(s1)
@@ -526,7 +526,7 @@ class Automaton
                                                f.append("s{names[s]}->s{names[s2]} [label=\"{labe.escape_to_dot}\"];\n")
                                        end
                                end
-                               if merge_transitions == null or merge_transitions == true then
+                               if merge_transitions or else true then
                                        f.append("s{names[s]}->s{names[s2]} [label=\"{labe.escape_to_c}\"];\n")
                                end
                        end
@@ -626,6 +626,58 @@ class Automaton
                return dfa
        end
 
+       # Transform a NFA to a epsilonless NFA.
+       fun to_nfa_noe: Automaton
+       do
+               assert_valid
+
+               trim
+
+               var dfa = new Automaton.empty
+               var n2d = new ArrayMap[Set[State], State]
+               var seen = new ArraySet[Set[State]]
+               var st = eclosure([start])
+               var todo = [st]
+               n2d[st] = dfa.start
+               seen.add(st)
+               while not todo.is_empty do
+                       var nfa_states = todo.pop
+                       #print "* work on {nfa_states.inspect}={nfa_states} (remains {todo.length}/{seen.length})"
+                       var dfa_state = n2d[nfa_states]
+                       for s in nfa_states do
+                               for t in s.outs do
+                                       if t.symbol == null then continue
+                                       var nfa_dest = eclosure([t.to])
+                                       #print "{nfa_states} -> {sym} -> {nfa_dest}"
+                                       var dfa_dest
+                                       if seen.has(nfa_dest) then
+                                               #print "* reuse {nfa_dest.inspect}={nfa_dest}"
+                                               dfa_dest = n2d[nfa_dest]
+                                       else
+                                               #print "* new {nfa_dest.inspect}={nfa_dest}"
+                                               dfa_dest = new State
+                                               dfa.states.add(dfa_dest)
+                                               n2d[nfa_dest] = dfa_dest
+                                               todo.add(nfa_dest)
+                                               seen.add(nfa_dest)
+                                       end
+                                       dfa_state.add_trans(dfa_dest, t.symbol)
+                               end
+
+                               # Mark accept and tags
+                               if accept.has(s) then
+                                       if tags.has_key(s) then
+                                               for t in tags[s] do
+                                                       dfa.add_tag(dfa_state, t)
+                                               end
+                                       end
+                                       dfa.accept.add(dfa_state)
+                               end
+                       end
+               end
+               return dfa
+       end
+
        # Epsilon-closure on a state of states.
        # Used by `to_dfa`.
        private fun eclosure(states: Collection[State]): Set[State]
index 3456690..a54044b 100644 (file)
@@ -112,8 +112,15 @@ f.close
 var nfa = v2.nfa
 print "NFA automaton: {nfa.states.length} states (see {name}.nfa.dot)"
 nfa.to_dot.write_to_file("{name}.nfa.dot")
+var nfanoe = nfa.to_nfa_noe
+nfanoe.to_dot.write_to_file("{name}.nfanoe.dot")
+print "NFA automaton without epsilon: {nfanoe.states.length} states (see {name}.nfanoe.dot)"
 
-var dfa = nfa.to_dfa.to_minimal_dfa
+var dfa = nfa.to_dfa
+dfa.to_dot.write_to_file("{name}.dfanomin.dot")
+print "DFA automaton (non minimal): {dfa.states.length} states (see {name}.dfanomin.dot)"
+
+dfa = dfa.to_minimal_dfa
 
 dfa.solve_token_inclusion
 
index 5cfad2a..9b9648c 100644 (file)
@@ -12,11 +12,11 @@ This tool is outside src/ because:
 * use importation/refinement to handle incremental execution (so basically everything works out of the box)
 * maintain an interpreter and live objects (the model grows but the interpreter and runtime data are reused)
 * runtime errors/aborts return to the interactive loop
+* top-level variables are preserved but are only visible at the top level
+* Basic FFI
 
-Main missing features
+Main missing feature
 
-* top-level variables are local
-* FFI is strange
 * No model/object inspection
 
 ## Examples
@@ -30,19 +30,26 @@ The rest is the output.
 $ nitin
 --> print 5+2
 7
+--> var a = 5
+--> print a + 3
+8
 ~~~
 
 ### Complex and control statements
 
 ~~~raw
+-->var sum=0
 -->for i in [0..5[ do
 ...print i
+...sum += i
 ...end
 0
 1
 2
 3
 4
+-->print sum
+10
 ~~~
 
 You can use `do` blocks to delay the execution and control the scope of variables.
@@ -93,8 +100,15 @@ bye
 
 ### Class refinement
 
-Already instantiated objects gain the new methods, attributes and specializations.
-However, the new attributes are left uninitialized (default values or init are not recomputed on existing objects)
+Class refinement is available
+
+~~~raw
+-->redef class String
+...fun foo: String do return self + "foo"
+...end
+-->print "hello".foo
+hellofoo
+~~~
 
 Top-level methods automatically refine Sys.
 
@@ -110,15 +124,40 @@ Top-level methods automatically refine Sys.
 I'm sys
 ~~~
 
-You can store global variables as attributes of Sys
+Already instantiated objects gain the new methods, attributes and specializations.
 
 ~~~raw
--->redef class Sys
-...var my_int: Int is writable
+-->class A
 ...end
--->my_int = 5
--->print my_int
-5
+-->var a = new A
+-->print a
+--><A:#561daea16660>
+-->redef class A
+...redef fun to_s do return "A"
+...end
+-->print a
+A
+~~~
+
+However, the new attributes are left uninitialized (default values or init are not recomputed on existing objects)
+
+~~~raw
+-->redef class A
+...var v = "foo"
+...redef fun to_s do return "A:{v}"
+...end
+--->print a
+Runtime error: Uninitialized attribute _v
+       redef fun to_s do return "A:{v}"
+                                    ^
+,---- Stack trace -- - -  -
+| input-6$A$to_s (13,30)
+| file$Sys$print (lib/core/file.nit:1694,19--29)
+| input-7$Sys$main (15,1--7)
+`------------------- - -  -
+-->a.v = "bar"
+-->print a
+A:bar
 ~~~
 
 ### Dynamic importation
@@ -132,3 +171,15 @@ You can store global variables as attributes of Sys
 -->print([0..10[.to_a.to_json)
 [0,1,2,3,4,5,6,7,8,9]
 ~~~
+
+### FFI
+
+The FFI is handled as with the non-interactive interpreter.
+
+~~~raw
+-->fun hello `{
+...puts("Hello, world");
+...`}
+-->hello
+Hello, world
+~~~
index 9e75b28..2497411 100644 (file)
@@ -22,15 +22,19 @@ module nitin
 import nitc::interpreter
 import nitc::frontend
 import nitc::parser_util
+intrude import nitc::scope
 
 redef class ToolContext
 
        # --no-prompt
        var opt_no_prompt = new OptionBool("Disable writing a prompt.", "--no-prompt")
 
+       # --source-name
+       var opt_source_name = new OptionString("Set a name for the input source.", "--source-name")
+
        redef init do
                super
-               option_context.add_option(opt_no_prompt)
+               option_context.add_option(opt_no_prompt, opt_source_name)
        end
 
        # Parse a full module given as a string
@@ -38,7 +42,9 @@ redef class ToolContext
        # Return a AModule or a AError
        fun p_module(string: String): ANode
        do
-               var source = new SourceFile.from_string("", string)
+               var source_name = opt_source_name.value or else ""
+               string = "\n" * last_line + string
+               var source = new SourceFile.from_string(source_name, string)
                var lexer = new Lexer(source)
                var parser = new Parser(lexer)
                var tree = parser.parse
@@ -65,6 +71,9 @@ redef class ToolContext
        # With the default implementation, the history is dropped
        fun readline_add_history(text: String) do end
 
+       # The last line number read by `i_parse`
+       var last_line = 0
+
        # Parse the input of the user as a module
        fun i_parse(prompt: String): nullable ANode
        do
@@ -79,7 +88,14 @@ redef class ToolContext
                                s = readline(prompt)
                        end
                        if s == null then return null
-                       if s == "" then continue
+                       if s == "" then
+                               if oldtext != "" then
+                                       oldtext += "\n"
+                               else
+                                       last_line += 1
+                               end
+                               continue
+                       end
 
                        if s.chars.first == ':' then
                                var res = new TString
@@ -91,19 +107,76 @@ redef class ToolContext
                        oldtext = ""
                        var n = p_module(text)
 
-                       if n isa AParserError and (n.token isa EOF) then
+                       if n isa AParserError and (n.token isa EOF or n.token isa TBadTString or n.token isa TBadExtern) then
                                # Unexpected end of file, thus continuing
                                if oldtext == "" then prompt = "." * prompt.length
                                oldtext = text
                                continue
                        end
 
+                       last_line = n.location.file.line_starts.length - 1
                        readline_add_history(text.chomp)
                        return n
                end
        end
 end
 
+redef class AMethPropdef
+       var injected_variables: nullable Map[Variable, Instance] = null is writable
+       var new_variables: nullable Array[Variable] = null
+
+       redef fun accept_scope_visitor(v)
+       do
+               var injected_variables = self.injected_variables
+               if injected_variables == null then
+                       super
+                       return
+               end
+
+               # Inject main variables in the initial scope
+               var scope = v.scopes.first
+               for variable in injected_variables.keys do
+                       scope.variables[variable.name] = variable
+               end
+
+               super
+
+               # Gather new top-level variables as main variables
+               scope = v.scopes.first
+               var new_variables = new Array[Variable]
+               for variable in scope.variables.values do
+                       if not injected_variables.has_key(variable) then
+                               new_variables.add(variable)
+                       end
+               end
+               self.new_variables = new_variables
+       end
+
+       redef fun call_commons(v, m, a, f)
+       do
+               var injected_variables = self.injected_variables
+               if injected_variables == null then return super
+
+               # Inject main variables in the frame
+               assert f isa InterpreterFrame
+               for variable, i in injected_variables do
+                       f.map[variable] = i
+               end
+
+               var res = super
+
+               # Update the values of the variables
+               for variable in injected_variables.keys do
+                       injected_variables[variable] = f.map[variable]
+               end
+               # Retrieve the values of the new main variables
+               for variable in new_variables.as(not null) do
+                       injected_variables[variable] = f.map[variable]
+               end
+
+               return res
+       end
+end
 
 # Create a tool context to handle options and paths
 var toolcontext = new ToolContext
@@ -139,6 +212,8 @@ var sys_type = mainobj.mtype.as(MClassType)
 var mainprop = mainmodule.try_get_primitive_method("main", sys_type.mclass)
 assert mainprop != null
 
+var main_variables = new Map[Variable, Instance]
+
 var l = 0
 loop
        # Next piece of Nit code
@@ -160,7 +235,8 @@ loop
 
        # An error
        if n isa AError then
-               print "{n.location.colored_line("0;31")}: {n.message}"
+               modelbuilder.error(n, n.message)
+               toolcontext.check_errors
                continue
        end
 
@@ -173,6 +249,14 @@ loop
        l += 1
        var newmodule = modelbuilder.load_rt_module(mainmodule, amodule, "input-{l}")
        if newmodule == null then continue
+
+       var main_method = null
+       if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
+               main_method = amodule.n_classdefs.last.n_propdefs.first
+               assert main_method isa AMethPropdef
+               main_method.injected_variables = main_variables
+       end
+
        modelbuilder.run_phases
        if not toolcontext.check_errors then
                toolcontext.error_count = 0
@@ -183,7 +267,7 @@ loop
        interpreter.mainmodule = mainmodule
 
        # Run the main if the AST contains a main
-       if amodule.n_classdefs.not_empty and amodule.n_classdefs.last isa AMainClassdef then
+       if main_method != null then
                do
                        interpreter.catch_count += 1
                        interpreter.send(mainprop, [mainobj])
index a738c05..022543e 100644 (file)
@@ -274,6 +274,16 @@ redef class Text
                        var title = item[tool_config.as(not null).tag_title].first.as(XMLStartTag).data
                        var link = item[tool_config.as(not null).tag_link].first.as(XMLStartTag).data
 
+                       if title == null then
+                               print_error "RSS Parse Error: title is null"
+                               return elements
+                       end
+
+                       if link == null then
+                               print_error "RSS Parse Error: link is null"
+                               return elements
+                       end
+
                        elements.add new Element(title, link)
                end
 
index 470b2b0..2dd9152 100644 (file)
@@ -6,7 +6,7 @@ bin/tnitter_server: $(shell nitls -M src/tnitter.nit)
        mkdir -p bin/
        nitc -o bin/tnitter_server src/tnitter.nit -D tnitter_interface=$(SERVER)
 
-bin/tnitter: $(shell nitls -M src/tnitter_app.nit)
+bin/tnitter: $(shell nitls -M src/tnitter_app.nit -m linux)
        mkdir -p bin/
        nitc -o bin/tnitter src/tnitter_app.nit -m linux -D tnitter_server_uri=http://$(SERVER)
 
@@ -30,11 +30,11 @@ android/res/: art/icon.svg
 # iOS
 
 ios: bin/tnitter.app
-bin/tnitter.app: $(shell nitls -M src/tnitter_app.nit ios) ios/AppIcon.appiconset/Contents.json
+bin/tnitter.app: $(shell nitls -M src/tnitter_app.nit -m ios) ios/AppIcon.appiconset/Contents.json
        mkdir -p bin/
        nitc -o bin/tnitter.app src/tnitter_app.nit -m ios -D tnitter_server_uri=http://$(SERVER)
 
-ios-release: $(shell nitls -M src/tnitter_app.nit ios) ios/AppIcon.appiconset/Contents.json
+ios-release: $(shell nitls -M src/tnitter_app.nit -m ios) ios/AppIcon.appiconset/Contents.json
        mkdir -p bin/
        nitc -o bin/tnitter.app src/tnitter_app.nit -m ios -D tnitter_server_uri=http://tnitter.xymus.net
 
index d620cb8..c0a2154 100644 (file)
@@ -1,13 +1,13 @@
 NITC=../../bin/nitc
 NITLS=../../bin/nitls
 
-all: bin/calculator bin/test
+all: bin/calculator bin/scientific bin/test
 
-bin/calculator: $(shell ${NITLS} -M src/calculator.nit linux) ${NITC}
+bin/calculator: $(shell ${NITLS} -M src/calculator.nit -m linux) ${NITC}
        mkdir -p bin
        ${NITC} -o $@ src/calculator.nit -m linux
 
-bin/scientific: $(shell ${NITLS} -M scientific linux) ${NITC}
+bin/scientific: $(shell ${NITLS} -M scientific -m linux) ${NITC}
        mkdir -p bin
        ${NITC} -o $@ src/scientific -m linux
 
@@ -28,15 +28,15 @@ bin/calculator21.apk: $(shell ${NITLS} -M src/android21) ${NITC} android/res/dra
        mkdir -p bin
        ${NITC} -o $@ src/android21 -D debug
 
-bin/scientific14.apk: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+bin/scientific14.apk: $(shell ${NITLS} -M src/scientific -m src/android14.nit) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
        mkdir -p bin
        ${NITC} -o $@ src/scientific -m src/android14.nit -D debug
 
-bin/scientific21.apk: $(shell ${NITLS} -M src/scientific src/android21) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
+bin/scientific21.apk: $(shell ${NITLS} -M src/scientific -m src/android21) ${NITC} src/scientific/android/res/drawable-hdpi/icon.png
        mkdir -p bin
        ${NITC} -o $@ src/scientific -m src/android21 -D debug
 
-android-release: $(shell ${NITLS} -M src/scientific src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
+android-release: $(shell ${NITLS} -M src/scientific -m src/android14.nit) ${NITC} android/res/drawable-hdpi/icon.png
        mkdir -p bin
        ${NITC} -o bin/calculator14.apk src/android14.nit --release
        ${NITC} -o bin/calculator21.apk src/android21 --release
@@ -66,7 +66,7 @@ bin/calculator.app: $(shell ${NITLS} -M src/ios.nit) ${NITC} ios/AppIcon.appicon
        mkdir -p bin
        ${NITC} -o $@ src/ios.nit -D debug
 
-bin/scientific.app: $(shell ${NITLS} -M src/scientific src/ios.nit) ${NITC} src/scientific/ios/AppIcon.appiconset/Contents.json
+bin/scientific.app: $(shell ${NITLS} -M src/scientific -m src/ios.nit) ${NITC} src/scientific/ios/AppIcon.appiconset/Contents.json
        mkdir -p bin
        ${NITC} -o $@ src/scientific -m src/ios.nit -D debug
 
index cbe6ad4..2f712f1 100644 (file)
@@ -32,7 +32,7 @@ public class NitActivity extends Activity {
         */
 
        static {
-               System.loadLibrary("main");
+               System.loadLibrary("nit_app");
        }
 
        /*
index b6bcf7e..ffd961c 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.
@@ -79,6 +102,20 @@ Importing `android::landscape` or `android::portrait` locks the generated
 application in the specified orientation. This can be useful for games and
 other multimedia applications.
 
+## Resources and application icon
+
+Resources specific to the Android platform should be placed in an `android/` folder at the root of the project.
+The folder should adopt the structure of a normal Android project, e.g., a custom XML resource file can be placed
+at `android/res/values/color.xml` to be compiled with the Android application.
+
+The application icon should also be placed in the `android/` folder.
+Place the classic bitmap version at `android/res/mipmap-hdpi/ic_launcher.png` (and others),
+and the adaptive version at `android/res/mipmap-anydpi-v26/ic_launcher.xml`.
+The Nit compiler detects these files and uses them as the application icon.
+
+Additional `android/` folders may be placed next to more specific Nit modules to change the Android resources
+for application variants. The more specific resources will have priority over the project level `android/` files.
+
 # Compilation modes
 
 There are two compilation modes for the Android platform, debug and release.
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..1769e0c 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);
        }
 
@@ -194,7 +195,7 @@ redef class App
        # Notification from the Android framework, the system is running low on memory
        #
        # Try to reduce your memory use.
-       fun low_memory do end
+       fun low_memory do force_garbage_collection
 
        # Notification from the Android framework, the current device configuration has changed
        fun config_changed do end
index 1bdf597..f084958 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;
@@ -286,7 +287,7 @@ class Activity
        # Notification from Android, the system is running low on memory
        #
        # Try to reduce your memory use.
-       fun on_low_memory do end
+       fun on_low_memory do force_garbage_collection
 
        # Notification from Android, the current window of the activity has lost or gained focus
        fun on_window_focus_changed(has_focus: Bool) do end
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 2dfc094..946b7a5 100644 (file)
@@ -4,22 +4,22 @@ android: http_request_example.apk ui_example.apk
 
 ios: http_request_example.app ui_example.app
 
-http_request_example: $(shell nitls -M http_request_example.nit linux)
+http_request_example: $(shell nitls -M http_request_example.nit -m linux)
        nitc http_request_example.nit -m linux
 
-http_request_example.apk: $(shell nitls -M http_request_example.nit android)
+http_request_example.apk: $(shell nitls -M http_request_example.nit -m android)
        nitc http_request_example.nit -m android
 
-http_request_example.app: $(shell nitls -M http_request_example.nit ios)
+http_request_example.app: $(shell nitls -M http_request_example.nit -m ios)
        nitc http_request_example.nit -m ios
 
-ui_example: $(shell nitls -M ui_example.nit linux)
+ui_example: $(shell nitls -M ui_example.nit -m linux)
        nitc ui_example.nit -m linux
 
-ui_example.apk: $(shell nitls -M ui_example.nit android)
+ui_example.apk: $(shell nitls -M ui_example.nit -m android)
        nitc ui_example.nit -m android
 
-ui_example.app: $(shell nitls -M ui_example.nit ios)
+ui_example.app: $(shell nitls -M ui_example.nit -m ios)
        nitc ui_example.nit -m ios
 
 clean:
index daa5314..3c61498 100644 (file)
@@ -56,7 +56,7 @@ class MyHttpRequest
        redef fun after do win.button_request.enabled = true
 end
 
-# Simpe window with a label and a button
+# Simple window with a label and a button
 class HttpRequestClientWindow
        super Window
 
@@ -81,11 +81,4 @@ class HttpRequestClientWindow
        end
 end
 
-redef class App
-       redef fun on_create
-       do
-               # Create the main window
-               push_window new HttpRequestClientWindow
-               super
-       end
-end
+redef fun root_window do return new HttpRequestClientWindow
index 6ae0b9a..0e49515 100644 (file)
@@ -100,6 +100,13 @@ class CurlHTTPRequest
        # Data for the body of a POST request
        var data: nullable HeaderMap is writable
 
+       # Raw body string
+       #
+       # Set this value to send raw data instead of the POST formatted `data`.
+       #
+       # If `data` is set, the body will not be sent.
+       var body: nullable String is writable
+
        # Header content of the request
        var headers: nullable HeaderMap is writable
 
@@ -156,6 +163,9 @@ class CurlHTTPRequest
                        var postdatas = data.to_url_encoded(self.curl)
                        err = self.curl.native.easy_setopt(new CURLOption.postfields, postdatas)
                        if not err.is_ok then return answer_failure(err.to_i, err.to_s)
+               else if body != null then
+                       err = self.curl.native.easy_setopt(new CURLOption.postfields, body.as(not null))
+                       if not err.is_ok then return answer_failure(err.to_i, err.to_s)
                end
 
                var err_resp = perform
index 5a1b8c0..0dfee06 100644 (file)
@@ -58,13 +58,13 @@ redef class XMLStartTag
        # var xml = code.to_xml
        # assert xml["animal"].first["tiger"].first.as(XMLStartTag).data == "This is a white tiger!"
        # ~~~
-       fun data: String
+       fun data: nullable String
        do
                for child in children do
                        if child isa PCDATA then return child.content
                        if child isa CDATA then return child.content
                end
-               abort
+               return null
        end
 end
 
index 7ebe6b0..94f718b 100644 (file)
@@ -187,7 +187,6 @@ class XMLProcessor
                                end
                        end
                else
-                       if tag_name.has("xml") then return new XMLError(st_loc, "Forbidden keyword xml in Processing Instruction")
                        var cont_st = pos
                        var cont_end = ignore_until("?>")
                        if cont_end == -1 then
index cdeef2d..d052922 100644 (file)
@@ -283,7 +283,7 @@ end
 #     var pos: Point3d[Float] = ui_camera.top_left.offset(128.0, -128.0, 0.0)
 #     var ui_text = new TextSprites(font, pos)
 #
-#     redef fun on_create
+#     redef fun create_scene
 #     do
 #         super
 #
index ccecf15..35c0f76 100644 (file)
@@ -80,7 +80,7 @@ redef class App
                end
        end
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
                initialize_head_tracker
index eea4123..a7864f9 100644 (file)
@@ -25,14 +25,19 @@ import shadow
 
 redef class App
 
-       redef fun on_create
+       redef fun create_scene
        do
-               super
-
                # Move the camera back a bit
                world_camera.reset_height(10.0)
                world_camera.near = 0.1
 
+               super
+       end
+
+       redef fun create_gamnit
+       do
+               super
+
                # Cull the invisible triangles in the back of the geometries
                glCullFace gl_BACK
 
@@ -65,6 +70,7 @@ redef class App
                for actor in actors do
                        for leaf in actor.model.leaves do
                                leaf.material.draw(actor, leaf, app.world_camera)
+                               assert glGetError == gl_NO_ERROR else print_error "Gamnit error on material {leaf.material.class_name}"
                        end
                end
                perfs["gamnit depth actors"].add frame_core_depth_clock.lapse
@@ -74,7 +80,10 @@ redef class App
 
                # Toggle writing to the depth buffer for particles effects
                glDepthMask false
-               for system in particle_systems do system.draw
+               for system in particle_systems do
+                       system.draw
+                       assert glGetError == gl_NO_ERROR else print_error "OpenGL error in {system}"
+               end
                glDepthMask true
                perfs["gamnit depth particles"].add frame_core_depth_clock.lapse
 
index cc194f7..e7e42de 100644 (file)
@@ -84,7 +84,7 @@ end
 
 # 3D model composed of `Mesh` and `Material`, loaded from the assets folder by default
 #
-# Instances can be created at any time and must be loaded after or at the end of `on_create`.
+# Instances can be created at any time and must be loaded after or at the end of `create_scene`.
 # If loading fails, the model is replaced by `placeholder_model`.
 #
 # ~~~
index 96c1484..6674c04 100644 (file)
@@ -96,6 +96,8 @@ class SmoothMaterial
                else
                        glDrawElements(mesh.draw_mode, mesh.indices.length, gl_UNSIGNED_SHORT, mesh.indices_c.native_array)
                end
+
+               assert glGetError == gl_NO_ERROR
        end
 
        private fun setup_lights(camera: Camera, program: BlinnPhongProgram)
index aa0f137..75178f5 100644 (file)
@@ -52,7 +52,7 @@ redef class App
 
        private var perf_clock_shadow = new Clock is lazy
 
-       redef fun on_create
+       redef fun create_gamnit
        do
                super
 
index ad160a6..0219db4 100644 (file)
@@ -54,7 +54,7 @@ redef class App
 
        private var perf_clock_dynamic_resolution = new Clock is lazy
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
index 59a0aa3..b0f756f 100644 (file)
@@ -110,6 +110,10 @@ redef class GamnitDisplay
 
        redef fun flip
        do
+               assert glGetError == gl_NO_ERROR
+
+               assert egl_display.is_valid
+
                egl_display.swap_buffers(window_surface)
        end
 end
index 33a342c..10ba0b4 100644 (file)
@@ -28,7 +28,7 @@ redef class App
        # Bottom right corner
        var corner = new Texture("corner.png")
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
index a0f37a0..8bd28fd 100644 (file)
@@ -17,7 +17,7 @@ import gamnit::flat # The 2D API, use `gamnit::depth` for 3D
 
 redef class App
 
-       # Texture, loaded automatically at `on_create`
+       # Texture, loaded in `create_scene`
        var texture = new Texture("fighter.png")
 
        # Sound effect, lazy loaded at first use
@@ -26,7 +26,7 @@ redef class App
        # Sprite, must be loaded in or after `on_create`
        var sprite = new Sprite(texture, new Point3d[Float](0.0, 0.0, 0.0)) is lazy
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
@@ -52,7 +52,7 @@ redef class App
                # Scale the ship so it is approximately 5 world units wide.
                sprite.scale = 5.0 / texture.width
 
-               # Move the camera to show 10 world units on the Y axis at Z = 0.
+               # Move the camera to show 20 world units on the Y axis at Z = 0.
                # The `sprite` should take approximately 1/4 of the height of the screen.
                world_camera.reset_height 20.0
 
index 9dfa49c..21492ad 100644 (file)
@@ -39,7 +39,7 @@ redef class App
        # Vertex data for the triangle
        var vertex_array: VertexArray is noautoinit
 
-       redef fun on_create
+       redef fun create_scene
        do
                super
 
index ae3c2cb..22ea7b3 100644 (file)
@@ -417,10 +417,15 @@ redef class App
        # Second performance clock for smaller operations
        private var perf_clock_sprites = new Clock is lazy
 
-       redef fun on_create
+       redef fun create_gamnit
        do
                super
+               create_flat
+       end
 
+       # Prepare the flat framework services
+       fun create_flat
+       do
                var display = display
                assert display != null
 
@@ -459,10 +464,15 @@ redef class App
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_LINEAR)
                        glTexParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_LINEAR)
                end
+
+               sprites.reset
+               ui_sprites.reset
        end
 
        redef fun on_stop
        do
+               super
+
                # Clean up
                simple_2d_program.delete
 
@@ -532,6 +542,8 @@ redef class App
 
                # draw
                sprite_set.draw
+
+               assert glGetError == gl_NO_ERROR
        end
 
        # Draw world sprites from `sprites`
@@ -963,7 +975,7 @@ class SpriteSet
                for sprite in sprites_to_remap do
 
                        # Skip if it was removed from this set after being modified
-                       if sprite.context != self then continue
+                       if sprite.sprite_set != self then continue
 
                        unmap_sprite sprite
                        map_sprite sprite
@@ -1003,6 +1015,23 @@ class SpriteSet
                for c in contexts_items do c.destroy
                contexts_map.clear
                contexts_items.clear
+               sprites_to_remap.clear
+       end
+
+       private fun reset
+       do
+               for sprite in self do
+                       sprite.context = null
+               end
+
+               for c in contexts_items do c.destroy
+               contexts_map.clear
+               contexts_items.clear
+               sprites_to_remap.clear
+
+               for sprite in self do
+                       map_sprite sprite
+               end
        end
 end
 
index bd26cbd..3051553 100644 (file)
@@ -26,13 +26,15 @@ import gamnit_linux is conditional(linux)
 
 redef class App
 
-       # Main `GamnitDisplay` initialized by `on_create`
+       # Main `GamnitDisplay` initialized by `create_gamnit`
        var display: nullable GamnitDisplay = null
 
-       redef fun on_create
+       # Hook to setup the OpenGL context: compiling shaders, creating VBO, reloading textures, etc.
+       #
+       # The gamnit services redefine this method to prepare optimizations and more.
+       # Clients may also refine this method to prepare custom OpenGL resources.
+       fun create_gamnit
        do
-               super
-
                var display = new GamnitDisplay
                display.setup
                self.display = display
@@ -45,6 +47,15 @@ redef class App
                print "GL extensions: {glGetString(gl_EXTENSIONS)}"
        end
 
+       # Hook for client programs to setup the scene
+       #
+       # Refine this method to build the game world or the main menu,
+       # creating instances of `Sprite` and `Actor` as needed.
+       #
+       # This method is called only once per execution of the program and it should
+       # be considered as the entry point of most game logic.
+       fun create_scene do end
+
        # Core of the frame logic, executed only when the display is visible
        #
        # This method should be redefined by user modules to customize the behavior of the game.
index b86f8e5..3818878 100644 (file)
@@ -46,6 +46,13 @@ redef class App
                        accept_event gamnit_event
                end
        end
+
+       redef fun on_create
+       do
+               super
+               create_gamnit
+               create_scene
+       end
 end
 
 redef class GamnitDisplay
index ba9a0d9..4383c2b 100644 (file)
@@ -20,7 +20,7 @@ import display
 # Texture composed of pixels, loaded from the assets folder by default
 #
 # Most textures should be created with `App` (as attributes)
-# for the method `on_create` to load them.
+# for the method `create_scene` to load them.
 #
 # ~~~
 # import gamnit::flat
@@ -29,9 +29,9 @@ import display
 #     # Create the texture object, it will be loaded automatically
 #     var texture = new Texture("path/in/assets.png")
 #
-#     redef fun on_create()
+#     redef fun create_scene()
 #     do
-#         # Let `on_create` load the texture
+#         # Let `create_scene` load the texture
 #         super
 #
 #         # Use the texture
@@ -41,7 +41,7 @@ import display
 # end
 # ~~~
 #
-# Otherwise, they can be loaded and error checked explicitly after `on_create`.
+# Otherwise, they can be loaded and error checked explicitly after `create_scene`.
 #
 # ~~~nitish
 # var texture = new Texture("path/in/assets.png")
index f5b6bec..fb1920d 100644 (file)
@@ -25,7 +25,7 @@
 #
 # ~~~
 # redef class App
-#     redef fun on_create
+#     redef fun create_scene
 #     do
 #         super
 #
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 55821ef..68567ff 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Dynamic interface to read JSON strings.
+# Dynamic interface to read values from JSON strings
 #
-# `String::to_json_value` returns a `JsonValue` which can be queried
-# to get the underlying JSON data.
+# Most services are in `JsonValue`, which is created by `Text::to_json_value`.
 module dynamic
 
 import error
 private import static
 
-# Wraps a JSON value.
+redef class Text
+       # Parse `self` to a `JsonValue`
+       fun to_json_value: JsonValue do return new JsonValue(parse_json)
+end
+
+# Dynamic wrapper of a JSON value, created by `Text::to_json_value`
+#
+# Provides high-level services to explore JSON objects with minimal overhead
+# dealing with static types. Use this class to manipulate the JSON data first
+# and check for errors only before using the resulting data.
 #
-# Offer methods to query the type, to dynamicaly cast the underlying value and
-# to query elements (in case of a JSON object or a JSON array).
+# For example, given:
+# ~~~
+# var json_src = """
+# {
+#   "i": 123,
+#   "m": {
+#          "t": true,
+#          "f": false
+#        },
+#   "a": ["zero", "one", "two"]
+# }"""
+# var json_value = json_src.to_json_value # Parse src to a `JsonValue`
+# ~~~
 #
-# Use `String::to_json_value` to get a `JsonValue` from a string.
+# Access array or map values using their indices.
+# ~~~
+# var target_int = json_value["i"]
+# assert target_int.is_int # Check for error and expected type
+# assert target_int.to_i == 123 # Use the value
+# ~~~
+#
+# Use `get` to reach a value nested in multiple objects.
+# ~~~
+# var target_str = json_value.get("a.0")
+# assert target_str.is_string # Check for error and expected type
+# assert target_str.to_s == "zero" # Use the value
+# ~~~
+#
+# This API is useful for scripts and when you need only a few values from a
+# JSON object. To access many values or check the conformity of the JSON
+# beforehand, use the `json` serialization services.
 class JsonValue
 
        # The wrapped JSON value.
@@ -131,10 +166,7 @@ class JsonValue
        #     assert "123".to_json_value.to_s == "123"
        #     assert "true".to_json_value.to_s == "true"
        #     assert "[1, 2, 3]".to_json_value.to_s == "[1,2,3]"
-       redef fun to_s do
-               if value == null then return "null"
-               return value.to_s
-       end
+       redef fun to_s do return (value or else "null").to_s
 
        ### Objects
 
@@ -195,19 +227,6 @@ class JsonValue
        # require: `self.is_error`
        fun to_error: Error do return value.as(Error)
 
-       ### JsonParseError
-
-       # Is this value a parse error?
-       #
-       #     assert "[".to_json_value.is_parse_error
-       #     assert not "[]".to_json_value.is_parse_error
-       fun is_parse_error: Bool do return value isa JsonParseError
-
-       # Get this value as a `JsonParseError`.
-       #
-       # require: `self.is_parse_error`
-       fun to_parse_error: JsonParseError do return value.as(JsonParseError)
-
        ### Children access
 
        # Iterator over the values of the array `self`
@@ -264,57 +283,104 @@ class JsonValue
                return new JsonValue(result)
        end
 
-       # Advanced query to get a value within the map `self` or it's children.
-       #
-       # A query is composed of the keys to each map seperated by '.'.
-       #
-       # require: `self.is_map`
-       #
-       #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a").is_map
-       #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t").to_bool
-       #     assert not """{"a": {"t": true, "f": false}}""".to_json_value.get("a.f").to_bool
-       #     assert """{"a": {"t": true, "f": false}}""".to_json_value.get("a.t.t").is_error
-       #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.b.c.d").to_i == 123
-       #     assert """{"a": {"b": {"c": {"d": 123}}}}""".to_json_value.get("a.z.c.d").is_error
-       fun get(query: String): JsonValue do
-               var keys = query.split(".")
-               var value = value
+       # Get the value at `query`, a string of map keys and array indices
+       #
+       # The `query` is composed of map keys and array indices separated by "." (by default).
+       # The separator can be set with `sep` to any string.
+       #
+       # Given the following JSON object parsed as a `JsonValue`.
+       # ~~~
+       # var jvalue = """
+       # {
+       #   "a": {
+       #          "i": 123,
+       #          "b": true
+       #        },
+       #   "b": ["zero", "one", "two"]
+       # }""".to_json_value
+       # ~~~
+       #
+       # Access a value in maps by its key, starting from the key in the root object.
+       # ~~~
+       # assert jvalue.get("a").is_map
+       # assert jvalue.get("a.i").to_i == 123
+       # assert jvalue.get("a.b").to_bool
+       # ~~~
+       #
+       # Access an item in an array by its index.
+       # ~~~
+       # assert jvalue.get("b.1").to_s == "one"
+       # ~~~
+       #
+       # Any error at any depth of a query is reported. The client should usually
+       # check for errors before using the returned value.
+       # ~~~
+       # assert jvalue.get("a.b.c").to_error.to_s == "Value at `a.b` is not a map. Got a `map`"
+       # assert jvalue.get("b.3").to_error.to_s == "Index `3` out of bounds at `b`"
+       # ~~~
+       #
+       # Set `sep` to a custom string to access keys containing a dot.
+       # ~~~
+       # jvalue = """
+       # {
+       #   "a.b": { "i": 123 },
+       #   "c/d": [ 456 ]
+       # }""".to_json_value
+       #
+       # assert jvalue.get("a.b/i", sep="/").to_i == 123
+       # assert jvalue.get("c/d:0", sep=":").to_i == 456
+       # ~~~
+       fun get(query: Text, sep: nullable Text): JsonValue
+       do
                if is_error then return self
+
+               sep = sep or else "."
+               var keys = query.split(sep)
+               var value = value
                for i in [0..keys.length[ do
                        var key = keys[i]
                        if value isa MapRead[String, nullable Object] then
                                if value.has_key(key) then
                                        value = value[key]
                                else
-                                       var sub_query = sub_query_to_s(keys, i)
-                                       var e = new JsonKeyError("Key `{key}` not found.",
+                                       var sub_query = sub_query_to_s(keys, i, sep)
+                                       value = new JsonKeyError("Key `{key}` not found.",
                                                        self, sub_query)
-                                       return new JsonValue(e)
+                                       break
+                               end
+                       else if value isa Sequence[nullable Object] then
+                               if key.is_int then
+                                       var index = key.to_i
+                                       if index < value.length then
+                                               value = value[index]
+                                       else
+                                               var sub_query = sub_query_to_s(keys, i, sep)
+                                               value = new JsonKeyError("Index `{key}` out of bounds at `{sub_query}`",
+                                                               self, sub_query)
+                                               break
+                                       end
                                end
                        else
-                               var sub_query = sub_query_to_s(keys, i)
-                               var val_type = (new JsonValue(value)).json_type
-                               var e = new JsonKeyError("Value at `{sub_query}` is not a map. Got type `{val_type}`",
+                               var sub_query = sub_query_to_s(keys, i, sep)
+                               value = new JsonKeyError("Value at `{sub_query}` is not a map. Got a `{json_type}`",
                                                self, sub_query)
-                               return new JsonValue(e)
+                               break
                        end
                end
                return new JsonValue(value)
        end
 
-       # Concatenate all keys up to `last` for debugging purposes.
-       #
-       # Note: This method deletes elements in `keys`.
-       private fun sub_query_to_s(keys: Array[String], last: Int): String do
-               last += 1
-               for j in [last..keys.length[ do keys.pop
-               return keys.join(".")
+       # Concatenate all keys up to `last` for error reports
+       private fun sub_query_to_s(keys: Array[String], last: Int, sep: Text): String
+       do
+               return [for i in [0..last[ do keys[i]].join(sep)
        end
 
        # Return a human-readable description of the type.
        #
        # For debugging purpose only.
-       fun json_type: String do
+       private fun json_type: String
+       do
                if is_array then return "array"
                if is_bool then return "bool"
                if is_float then return "float"
@@ -322,29 +388,20 @@ class JsonValue
                if is_null then return "null"
                if is_map then return "map"
                if is_string then return "string"
-               if is_parse_error then return "parse_error"
                if is_error then return "error"
                return "undefined"
        end
 end
 
-# Keyed access failed.
+# Key access error
 class JsonKeyError
        super Error
 
-       # The value on which the access was requested.
+       # The value on which the access was requested
        var json_value: JsonValue
 
-       # The requested key.
+       # The requested key
        #
-       # In the case of `JsonValue.get`, the sub-query that failed.
+       # In the case of `JsonValue::get`, the sub-query that failed.
        var key: Object
 end
-
-redef class Text
-       # Parse `self` to obtain a `JsonValue`
-       fun to_json_value: JsonValue do
-               var value = parse_json
-               return new JsonValue(value)
-       end
-end
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 9c718a9..3ed2c5e 100644 (file)
@@ -11,7 +11,9 @@ This wrapper needs the Stanford CoreNLP jars that run on Java 1.8+.
 
 See http://nlp.stanford.edu/software/corenlp.shtml.
 
-## Usage
+## NLPProcessor
+
+### Java client
 
 ~~~nitish
 var proc = new NLPProcessor("path/to/StanfordCoreNLP/jars")
@@ -25,52 +27,41 @@ for sentence in doc.sentences do
 end
 ~~~
 
-## Nit API
-
-For ease of use, this wrapper introduce a Nit model to handle CoreNLP XML results.
-
-### NLPDocument
-
-[[doc: NLPDocument]]
-
-[[doc: nlp::NLPDocument::from_xml]]
-[[doc: nlp::NLPDocument::from_xml_file]]
-[[doc: nlp::NLPDocument::sentences]]
-
-### NLPSentence
-
-[[doc: NLPSentence]]
+### NLPServer
 
-[[doc: nlp::NLPSentence::tokens]]
+The NLPServer provides a wrapper around the StanfordCoreNLPServer.
 
-### NLPToken
+See `https://stanfordnlp.github.io/CoreNLP/corenlp-server.html`.
 
-[[doc: NLPToken]]
-
-[[doc: nlp::NLPToken::word]]
-[[doc: nlp::NLPToken::lemma]]
-[[doc: nlp::NLPToken::pos]]
+~~~nitish
+var cp = "/path/to/StanfordCoreNLP/jars"
+var srv = new NLPServer(cp, 9000)
+srv.start
+~~~
 
-### NLP Processor
+### NLPClient
 
-[[doc: NLPProcessor]]
+The NLPClient is used as a NLPProcessor with a NLPServer backend.
 
-[[doc: nlp::NLPProcessor::java_cp]]
+~~~nitish
+var cli = new NLPClient("http://localhost:9000")
+var doc = cli.process("String to analyze")
+~~~
 
-[[doc: nlp::NLPProcessor::process]]
-[[doc: nlp::NLPProcessor::process_file]]
-[[doc: nlp::NLPProcessor::process_files]]
+## NLPIndex
 
-## NitNLP binary
+NLPIndex extends the StringIndex to use a NLPProcessor to tokenize, lemmatize and
+tag the terms of a document.
 
-The `nitnlp` binary is given as an example of NitNLP client.
-It compares two strings and display ther cosine similarity value.
+~~~nitish
+var index = new NLPIndex(proc)
 
-Usage:
+var d1 = index.index_string("Doc 1", "/uri/1", "this is a sample")
+var d2 = index.index_string("Doc 2", "/uri/2", "this and this is another example")
+assert index.documents.length == 2
 
-~~~raw
-nitnlp --cp "/path/to/jars" "sort" "Sorting array data"
-0.577
+matches = index.match_string("this sample")
+assert matches.first.document == d1
 ~~~
 
 ## TODO
diff --git a/lib/nlp/examples/nlp_index.nit b/lib/nlp/examples/nlp_index.nit
new file mode 100644 (file)
index 0000000..72c0b5c
--- /dev/null
@@ -0,0 +1,81 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Example showing how to use a NLPFileIndex.
+module nlp_index
+
+import nlp
+import config
+
+redef class Config
+
+       # --whitelist-exts
+       var opt_white_exts = new OptionArray("Allowed file extensions (default is [])",
+               "-w", "--whitelist-exts")
+
+       # --blacklist-exts
+       var opt_black_exts = new OptionArray("Allowed file extensions (default is [])",
+               "-b", "--blacklist-exts")
+
+       # --server
+       var opt_server = new OptionString("StanfordNLP server URI (default is https://localhost:9000)",
+               "-s", "--server")
+
+       # --lang
+       var opt_lang = new OptionString("Language to use (default is fr)", "-l", "--lang")
+
+       redef init do
+               opts.add_option(opt_server, opt_lang, opt_white_exts, opt_black_exts)
+       end
+end
+
+var config = new Config
+config.tool_description = "usage: example_index <files>"
+config.parse_options(args)
+
+if args.length < 1 then
+       config.usage
+       exit 1
+end
+
+var host = config.opt_server.value
+if host == null then host = "http://localhost:9000"
+var lang = config.opt_lang.value
+if lang == null then lang = "en"
+
+var cli = new NLPClient(host)
+cli.language = lang
+
+var bl = config.opt_black_exts.value
+if bl.is_empty then bl = ["CD", "SYM", "-RRB-", "-LRB-", "''", "``", ".", "#", ":", ",", "$", ""]
+
+var index = new NLPFileIndex(cli)
+index.whitelist_exts = config.opt_white_exts.value
+index.blacklist_exts = bl
+
+print "Building index..."
+index.index_files(args, true)
+
+print "Indexed {index.documents.length} documents"
+
+loop
+       print "\nEnter query:"
+       printn "> "
+       var input = sys.stdin.read_line
+       var matches = index.match_string(input)
+
+       for match in matches do
+               print match
+       end
+end
diff --git a/lib/nlp/examples/nlp_server.nit b/lib/nlp/examples/nlp_server.nit
new file mode 100644 (file)
index 0000000..61dd888
--- /dev/null
@@ -0,0 +1,37 @@
+# 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 nlp_server
+
+import nlp
+import config
+
+redef class Config
+
+       var opt_java_cp = new OptionString("StanfordNLP java classpath", "-c", "--classpath")
+       var opt_port = new OptionInt("Server port on localhost (default is 9000)", 9000, "-p", "--port")
+
+       redef init do
+               opts.add_option(opt_java_cp, opt_port)
+       end
+end
+
+var config = new Config
+config.parse_options(args)
+
+var cp = config.opt_java_cp.value
+if cp == null then cp = "stanfordnlp/*"
+
+var srv = new NLPServer(cp, config.opt_port.value)
+srv.start
diff --git a/lib/nlp/nitnlp.nit b/lib/nlp/nitnlp.nit
deleted file mode 100644 (file)
index 72946bc..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Natural Language Processor based on the StanfordNLP core.
-#
-# This tool provides a document comparison service from command line based on
-# StanfordNLP and NLPVector consine similarity.
-#
-# See http://nlp.stanford.edu/software/corenlp.shtml.
-module nitnlp
-
-import opts
-import nlp
-
-# Option management
-var opt_java_cp = new OptionString("Java classpath for StanfordNLP jars", "--cp")
-var options = new OptionContext
-options.add_option(opt_java_cp)
-options.parse(args)
-var arguments = options.rest
-
-# Processor initialization
-var java_cp = opt_java_cp.value
-if java_cp == null then java_cp = "*"
-var proc = new NLPProcessor(java_cp)
-
-if arguments.length != 2 then
-       print "Usage: nitnlp text1 text2\n"
-       options.usage
-       sys.exit 1
-end
-
-var doc1 = proc.process(arguments.first)
-print doc1.vector.join(":", ",")
-var doc2 = proc.process(arguments.last)
-print doc2.vector.join(":", ",")
-
-print doc1.vector.cosine_similarity(doc2.vector)
index 0e4dba3..5bad0dc 100644 (file)
@@ -20,16 +20,21 @@ module nlp
 import stanford
 import vsm
 
-redef class NLPDocument
+# A StringIndex using a NLPProcessor to parse and vectorize strings
+class NLPIndex
+       super StringIndex
 
-       # `NLPVector` representing `self`.
-       var vector: Vector is lazy do
+       # NLP Processor used to tokenize, lemmatize and POS tag documents
+       var nlp_processor: NLPProcessor
+
+       redef fun parse_string(string) do
                var vector = new Vector
-               for sentence in sentences do
+               if string.trim.is_empty then return vector
+               var doc = nlp_processor.process(string)
+               for sentence in doc.sentences do
                        for token in sentence.tokens do
-                               if not keep_pos_token(token) then continue
+                               if not accept_token(token) then continue
                                var lemma = token.lemma
-                               if lemma_black_list.has(lemma) then continue
                                if not vector.has_key(lemma) then
                                        vector[lemma] = 1.0
                                else
@@ -40,32 +45,33 @@ redef class NLPDocument
                return vector
        end
 
-       # Should we keep `token` when composing the vector?
+       # Is `token` accepted by this index?
        #
-       # Choice is based on the POS tag of the token.
-       # See `allowed_pos_prefixes`.
-       private fun keep_pos_token(token: NLPToken): Bool do
+       # See `whitelist_pos` and `blacklist_pos`.
+       fun accept_token(token: NLPToken): Bool do
                var pos = token.pos
-               for prefix in allowed_pos_prefixes do
-                       if pos.has_prefix(prefix) then return true
-               end
-               return false
+               if whitelist_pos.not_empty and not whitelist_pos.has(pos) then return false
+               if blacklist_pos.has(pos) then return false
+               if stoplist.has(token.lemma) then return false
+               return true
        end
 
-       # Should we keep `lemma` when composing the vector?
+       # Part-Of-Speech whitelist
        #
-       # See `lemma_black_list`.
-       private fun keep_lemma(lemma: String): Bool do
-               return true
-       end
+       # If not empty, the index accept only the POS tags contained in this list.
+       var whitelist_pos = new Array[String] is writable
 
-       # Allowed POS tag prefixes.
+       # Part-Of-Speech blacklist
        #
-       # When building a vector from `self`,  only tokens tagged with one of these
-       # prefixes are kept.
-       # Other tokens are ignored.
-       var allowed_pos_prefixes: Array[String] = ["NN", "VB", "RB"] is writable
+       # Reject POS tags contained in this list.
+       var blacklist_pos = new Array[String] is writable
+
+       # List of lemmas that must not be indexed
+       var stoplist = new Array[String] is writable
+end
 
-       # Ignored lemmas.
-       var lemma_black_list: Array[String] = ["module", "class", "method"] is writable
+# A FileIndex based using a NLPProcessor
+class NLPFileIndex
+       super NLPIndex
+       super FileIndex
 end
index 734a228..a637d53 100644 (file)
@@ -19,20 +19,50 @@ module stanford
 
 import opts
 import dom
+import curl
+import pthreads
 
-# Wrapper around StanfordNLP jar.
+# Natural Language Processor
 #
-# NLPProcessor provides natural language processing of input text files and
-# an API to handle analysis results.
+# NLPProcessor provides natural language processing for input text and files.
+# Analyzed documents can be manipulated through the resulting NLPDocument.
+interface NLPProcessor
+
+       # Creates a new NLPDocument from a string
+       fun process(string: String): NLPDocument is abstract
+
+       # Creates a new NLPDocument from a file content
+       fun process_file(path: String): NLPDocument do
+               var content = path.to_path.read_all
+               return process(content)
+       end
+
+       # Creates a new NLPDocument from a list of files (batch mode)
+       #
+       # Returns a map of file path associated with their NLPDocument.
+       fun process_files(paths: Array[String]): Map[String, NLPDocument] do
+               var res = new HashMap[String, NLPDocument]
+               for file in paths do
+                       res[file] = process_file(file)
+               end
+               return res
+       end
+end
+
+# Wrapper around StanfordNLP jar.
 #
 # FIXME this should use the Java FFI.
-class NLPProcessor
+class NLPJavaProcessor
+       super NLPProcessor
 
        # Classpath to give to Java when loading the StanfordNLP jars.
        var java_cp: String
 
+       # Temp dir used to store batch results
+       var tmp_dir = ".nlp"
+
        # Process a string and return a new NLPDocument from this.
-       fun process(string: String): NLPDocument do
+       redef fun process(string) do
                var tmp_file = ".nlp.in"
                var file = new FileWriter.open(tmp_file)
                file.write string
@@ -43,7 +73,7 @@ class NLPProcessor
        end
 
        # Process the `input` file and return a new NLPDocument from this.
-       fun process_file(input: String): NLPDocument do
+       redef fun process_file(input) do
                # TODO opt annotators
                var tmp_file = "{input.basename}.xml"
                sys.system "java -cp \"{java_cp}\" -Xmx2g edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,pos,lemma -outputFormat xml -file {input}"
@@ -55,7 +85,7 @@ class NLPProcessor
        # Batch mode.
        #
        # Returns a map of file path associated with their NLPDocument.
-       fun process_files(inputs: Collection[String], output_dir: String): Map[String, NLPDocument] do
+       redef fun process_files(inputs) do
                # Prepare the input file list
                var input_file = "inputs.list"
                var fw = new FileWriter.open(input_file)
@@ -63,14 +93,15 @@ class NLPProcessor
                fw.close
 
                # Run Stanford NLP jar
-               sys.system "java -cp \"{java_cp}\" -Xmx2g edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,pos,lemma -outputFormat xml -filelist {input_file} -outputDirectory {output_dir}"
+               sys.system "java -cp \"{java_cp}\" -Xmx2g edu.stanford.nlp.pipeline.StanfordCoreNLP -annotators tokenize,ssplit,pos,lemma -outputFormat xml -filelist {input_file} -outputDirectory {tmp_dir}"
                # Parse output
                var map = new HashMap[String, NLPDocument]
                for input in inputs do
-                       var out_file = output_dir / "{input.basename}.xml"
+                       var out_file = tmp_dir / "{input.basename}.xml"
                        map[input] = new NLPDocument.from_xml_file(out_file)
                end
                input_file.file_delete
+               tmp_dir.rmdir
                return map
        end
 end
@@ -248,11 +279,99 @@ class NLPToken
        # ~~~
        init from_xml(xml: XMLStartTag) do
                var index = xml.attributes.first.as(XMLStringAttr).value.to_i
-               var word = xml["word"].first.as(XMLStartTag).data
-               var lemma = xml["lemma"].first.as(XMLStartTag).data
-               var begin_offset = xml["CharacterOffsetBegin"].first.as(XMLStartTag).data.to_i
-               var end_offset = xml["CharacterOffsetEnd"].first.as(XMLStartTag).data.to_i
-               var pos = xml["POS"].first.as(XMLStartTag).data
+               var word = read_data(xml, "word")
+               var lemma = read_data(xml, "lemma")
+               var begin_offset = read_data(xml, "CharacterOffsetBegin").to_i
+               var end_offset = read_data(xml, "CharacterOffsetEnd").to_i
+               var pos = read_data(xml, "POS")
                init(index, word, lemma, begin_offset, end_offset, pos)
        end
+
+       private fun read_data(xml: XMLStartTag, tag_name: String): String do
+               var res = ""
+               if xml[tag_name].is_empty then return res
+               var first = xml[tag_name].first
+               if not first isa XMLStartTag then return res
+               var data = first.data
+               if data == null then return res
+               return data
+       end
+end
+
+# Stanford web server
+#
+# Runs the server on `port`.
+#
+# For more details about the stanford NLP server see
+# https://stanfordnlp.github.io/CoreNLP/corenlp-server.html
+class NLPServer
+       super Thread
+
+       # Stanford jar classpath
+       #
+       # Classpath to give to Java when loading the StanfordNLP jars.
+       var java_cp: String
+
+       # Port the Java server will listen on
+       var port: Int
+
+       redef fun main do
+               sys.system "java -mx4g -cp \"{java_cp}\" edu.stanford.nlp.pipeline.StanfordCoreNLPServer -port {port.to_s} -timeout 15000"
+               return null
+       end
+end
+
+# A NLPProcessor using a NLPServer as backend
+class NLPClient
+       super NLPProcessor
+
+       # Base uri of the NLP server API
+       #
+       # For examples "http://localhost:9000" or "https://myserver.com"
+       var api_uri: String
+
+       # Annotators to use
+       #
+       # The specified annotators must exist on the server.
+       #
+       # Defaults are: `tokenize`, `ssplit`, `pos` and `lemma`.
+       var annotators: Array[String] = ["tokenize", "ssplit", "pos", "lemma"] is writable
+
+       # Language to process
+       #
+       # The language must be available on the server.
+       #
+       # Default is `en`.
+       var language = "en" is writable
+
+       # Output format to ask.
+       #
+       # Only `xml` is implemented at the moment.
+       private var format = "xml"
+
+       # API uri used to build curl POST requests
+       fun post_uri: String do
+               return "{api_uri}/?properties=%7B%22annotators%22%3A%20%22tokenize%2Cssplit%2Cpos%2clemma%22%2C%22outputFormat%22%3A%22{format}%22%7D&pipelineLanguage={language}"
+       end
+
+       redef fun process(string) do
+               var request = new CurlHTTPRequest(post_uri)
+               request.body = string
+               var response = request.execute
+               if response isa CurlResponseSuccess then
+                       if response.status_code != 200 then
+                               print "Error: {response.body_str}"
+                               return new NLPDocument
+                       end
+                       var xml = response.body_str.to_xml
+                       if xml isa XMLError then
+                               print xml
+                       end
+                       return new NLPDocument.from_xml(response.body_str.to_xml.as(XMLDocument))
+               else if response isa CurlResponseFailed then
+                       print "Error: {response.error_msg}"
+                       return new NLPDocument
+               end
+               return new NLPDocument
+       end
 end
index 5d89e59..9795b01 100644 (file)
@@ -79,9 +79,11 @@ redef class Sys
 
        # Return a new port for each instance
        fun test_port: Int do
-               srand
-               return 10000+20000.rand
+               return testing_id % 20000 + 10000
        end
+
+       # Nitdoc testing ID
+       fun testing_id: Int do return "NIT_TESTING_ID".environ.to_i
 end
 
 # Thread running the App to test.
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 41d5152..4a64f99 100644 (file)
@@ -72,6 +72,11 @@ class Vector
                return cos
        end
 
+       redef fun [](k) do
+               if not has_key(k) then return 0.0
+               return super
+       end
+
        # The norm of the vector.
        #
        # `||x|| = (x1 ** 2 ... + xn ** 2).sqrt`
@@ -203,7 +208,8 @@ class StringIndex
        # See `match_vector`.
        fun match_string(query: String): Array[IndexMatch] do
                var vector = parse_string(query)
-               return match_vector(vector)
+               var doc = new Document("", "", vector)
+               return match_vector(doc.terms_frequency)
        end
 
        # Parse the `string` as a Vector
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
index 2263bd3..728ecdb 100644 (file)
@@ -228,52 +228,113 @@ To helps the management of the expected results, the option `--autosav` can be u
 ## Configuring TestSuites
 
 `TestSuite`s also provide annotations to configure the test run:
-`before` and `after` annotations can be applied to methods that must be called before/after each test case.
+`before` and `after` annotations can be added to methods that must be called before/after each test case.
 They can be used to factorize repetitive tasks:
 
-~~~~
+~~~
 class TestFoo
-    test
+       test
 
-    var subject: Foo
+       var subject: Foo is noinit
 
-    # Mandatory empty init
-    init do end
+       # Method executed before each test
+       fun set_up is before do
+               subject = new Foo
+       end
 
-    # Method executed before each test
-    fun set_up is before do
-        subject = new Foo
-    end
-    fun baz_1 is test do
-        assert subject.baz(1, 2) == 3
-    end
-    fun baz_2 is test do
-        assert subject.baz(1, -2) == -1
-    end
+       fun baz_1 is test do
+               assert subject.baz(1, 2) == 3
+       end
+
+       fun baz_2 is test do
+               assert subject.baz(1, -2) == -1
+       end
 end
-~~~~
+~~~
 
-When using custom test attributes, an empty `init` must be declared to allow automatic test running.
+When using custom test attributes, a empty init must be declared to allow automatic test running.
 
-`before_all` and `after_all` annotations can be applied to methods that must be called before/after each test suite.
-They have to be declared at top level:
+At class level, `before_all` and `after_all` annotations can be set on methods that must be called before/after all the test cases in the class:
 
-~~~~
+~~~
+class TestFoo
+       test
+
+       var subject: Foo is noinit
+
+       # Method executed before all tests in the class
+       fun set_up is before_all do
+               subject = new Foo
+       end
+
+       fun baz_1 is test do
+               assert subject.baz(1, 2) == 3
+       end
+
+       fun baz_2 is test do
+               assert subject.baz(1, -2) == -1
+       end
+end
+~~~
+
+`before_all` and `after_all` annotations can also be set on methods that must be called before/after each test suite when declared at top level:
+
+~~~
 module test_bdd_connector
+
 import bdd_connector
+
 # Testing the bdd_connector
 class TestConnector
-    # test cases using a server
+       test
+       # test cases using a server
 end
+
 # Method executed before testing the module
-fun before_module do
-    # start server before all test cases
+fun setup_db is before_all do
+       # start server before all test cases
 end
+
 # Method executed after testing the module
-fun after_module do
-    # stop server after all test cases
+fun teardown_db is after_all do
+       # stop server after all test cases
 end
-~~~~
+~~~
+
+When dealing with multiple test suites, niunit allows you to import other test suites to factorize your tests:
+
+~~~
+module test_bdd_users
+
+import test_bdd_connector
+
+# Testing the user table
+class TestUsersTable
+       test
+       # test cases using the db server from `test_bdd_connector`
+end
+
+fun setup_table is before_all do
+       # create user table
+end
+
+fun teardown_table is after_all do
+       # drop user table
+end
+~~~
+
+Methods with `before*` and `after*` annotations are linearized and called in different ways.
+
+* `before*` methods are called from the least specific to the most specific
+* `after*` methods are called from the most specific to the least specific
+
+In the previous example, the execution order would be:
+
+1. `test_bdd_connector::setup_db`
+2. `test_bdd_users::setup_table`
+3. `all test cases from test_bdd_users`
+4. `test_bdd_users::teardown_table`
+5. `test_bdd_connector::teardown_db`
 
 ## Accessing the test suite environment
 
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 c77c332..ce36170 100644 (file)
@@ -28,6 +28,8 @@ import model::model_views
 class DocModel
        super ModelView
 
+       autoinit model, mainmodule, filter
+
        # `DocPage` composing the documentation associated to their ids.
        #
        # This is where `DocPhase` store and access pages to produce documentation.
@@ -35,9 +37,6 @@ class DocModel
        # See `add_page`.
        var pages: Map[String, DocPage] = new HashMap[String, DocPage]
 
-       # The entry point of the `model`.
-       var mainmodule: MModule is writable
-
        # Add a `page` to this documentation.
        fun add_page(page: DocPage) do
                if pages.has_key(page.id) then
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 a8d86b6..20cd2f4 100644 (file)
@@ -318,7 +318,9 @@ redef class MModulePage
        redef fun init_topmenu(v, doc) do
                super
                var mpackage = mentity.mpackage
-               topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
+               if mpackage != null then
+                       topmenu.add_li new ListItem(new Link(mpackage.nitdoc_url, mpackage.html_name))
+               end
                topmenu.add_li new ListItem(new Link(mentity.nitdoc_url, mentity.html_name))
                topmenu.active_item = topmenu.items.last
        end
index 9eee60b..b7d2ac8 100644 (file)
@@ -274,7 +274,7 @@ redef class ListCommand
                if mentity isa MModule then
                        v.add_article new MEntitiesListArticle("Classes", null, mentity.mclassdefs)
                else if mentity isa MClass then
-                       var mprops = mentity.collect_intro_mproperties(mentity.public_view)
+                       var mprops = mentity.collect_intro_mproperties(v.phase.doc)
                        v.add_article new MEntitiesListArticle("Methods", null, mprops.to_a)
                else if mentity isa MClassDef then
                        v.add_article new MEntitiesListArticle("Methods", null, mentity.mpropdefs)
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 0846e85..395a57d 100644 (file)
@@ -49,21 +49,11 @@ redef class ToolContext
        end
 end
 
-redef class Model
-
-       # Get a custom view for vimautocomplete.
-       private fun vim_view: ModelView do
-               var view = new ModelView(self)
-               view.min_visibility = protected_visibility
-               return view
-       end
-end
-
 redef class MEntity
        private fun field_separator: String do return "#====#"
        private fun line_separator: String do return "#nnnn#"
 
-       private fun write_doc(mainmodule: MModule, stream: Writer)
+       private fun write_doc(view: ModelView, stream: Writer)
        do
                # 1. Short name for autocompletion
                stream.write complete_name
@@ -90,9 +80,9 @@ redef class MEntity
                        stream.write mdoc.content.join(line_separator)
                end
 
-               write_location(mainmodule, stream)
+               write_location(view.mainmodule, stream)
 
-               write_extra_doc(mainmodule, stream)
+               write_extra_doc(view, stream)
 
                stream.write "\n"
        end
@@ -106,7 +96,7 @@ redef class MEntity
        private fun complete_mdoc: nullable MDoc do return mdoc
 
        # Extra auto documentation to append to the `stream`
-       private fun write_extra_doc(mainmodule: MModule, stream: Writer) do end
+       private fun write_extra_doc(view: ModelView, stream: Writer) do end
 
        # Location (file and line when available) of related declarations
        private fun write_location(mainmodule: MModule, stream: Writer)
@@ -196,13 +186,13 @@ redef class MClassDef
 end
 
 redef class MClassType
-       redef fun write_extra_doc(mainmodule, stream)
+       redef fun write_extra_doc(view, stream)
        do
                # Super classes
                stream.write line_separator*2
                stream.write "## Class hierarchy"
 
-               var direct_supers = [for s in mclass.in_hierarchy(mainmodule).direct_greaters do s.name]
+               var direct_supers = [for s in mclass.in_hierarchy(view.mainmodule).direct_greaters do s.name]
                if not direct_supers.is_empty then
                        alpha_comparator.sort direct_supers
                        stream.write line_separator
@@ -210,7 +200,7 @@ redef class MClassType
                        stream.write direct_supers.join(", ")
                end
 
-               var supers = [for s in mclass.in_hierarchy(mainmodule).greaters do s.name]
+               var supers = [for s in mclass.in_hierarchy(view.mainmodule).greaters do s.name]
                supers.remove mclass.name
                if not supers.is_empty then
                        alpha_comparator.sort supers
@@ -219,7 +209,7 @@ redef class MClassType
                        stream.write supers.join(", ")
                end
 
-               var direct_subs = [for s in mclass.in_hierarchy(mainmodule).direct_smallers do s.name]
+               var direct_subs = [for s in mclass.in_hierarchy(view.mainmodule).direct_smallers do s.name]
                if not direct_subs.is_empty then
                        alpha_comparator.sort direct_subs
                        stream.write line_separator
@@ -227,7 +217,7 @@ redef class MClassType
                        stream.write direct_subs.join(", ")
                end
 
-               var subs = [for s in mclass.in_hierarchy(mainmodule).smallers do s.name]
+               var subs = [for s in mclass.in_hierarchy(view.mainmodule).smallers do s.name]
                subs.remove mclass.name
                if not subs.is_empty then
                        alpha_comparator.sort subs
@@ -240,11 +230,11 @@ redef class MClassType
                stream.write line_separator*2
                stream.write "## Properties"
                stream.write line_separator
-               var props = mclass.collect_accessible_mproperties(model.protected_view).to_a
+               var props = mclass.collect_accessible_mproperties(view).to_a
                alpha_comparator.sort props
                for prop in props do
                        if mclass.name == "Object" or prop.intro.mclassdef.mclass.name != "Object" then
-                               prop.write_synopsis(mainmodule, stream)
+                               prop.write_synopsis(view.mainmodule, stream)
                        end
                end
        end
@@ -281,8 +271,9 @@ private class AutocompletePhase
 
                # Got all known modules
                var model = mainmodule.model
+               var view = new ModelView(model, mainmodule)
                for mmodule in model.mmodules do
-                       mmodule.write_doc(mainmodule, modules_stream)
+                       mmodule.write_doc(view, modules_stream)
                end
 
                # TODO list other modules from the Nit lib
@@ -295,18 +286,18 @@ private class AutocompletePhase
                        # Can it be instantiated?
                        if mclass.kind != interface_kind and mclass.kind != abstract_kind then
 
-                               for prop in mclass.collect_accessible_mproperties(model.public_view) do
+                               for prop in mclass.collect_accessible_mproperties(view) do
                                        if prop isa MMethod and prop.is_init then
                                                mclass_intro.target_constructor = prop.intro
-                                               mclass_intro.write_doc(mainmodule, constructors_stream)
+                                               mclass_intro.write_doc(view, constructors_stream)
                                        end
                                end
                                mclass_intro.target_constructor = null
                        end
 
                        # Always add to types and classes
-                       mclass.mclass_type.write_doc(mainmodule, classes_stream)
-                       mclass.mclass_type.write_doc(mainmodule, types_stream)
+                       mclass.mclass_type.write_doc(view, classes_stream)
+                       mclass.mclass_type.write_doc(view, types_stream)
                end
 
                # Get all known properties
@@ -316,7 +307,7 @@ private class AutocompletePhase
 
                        # Is it a virtual type?
                        if mproperty isa MVirtualTypeProp then
-                               mproperty.intro.write_doc(mainmodule, types_stream)
+                               mproperty.intro.write_doc(view, types_stream)
                                continue
                        end
 
@@ -324,7 +315,7 @@ private class AutocompletePhase
                        var first_letter = mproperty.name.chars.first
                        if first_letter == '@' or first_letter == '_' then continue
 
-                       mproperty.intro.write_doc(mainmodule, properties_stream)
+                       mproperty.intro.write_doc(view, properties_stream)
                end
 
                # Close streams
@@ -341,10 +332,10 @@ private class AutocompletePhase
 end
 
 redef class MModule
-       redef fun write_extra_doc(mainmodule, stream)
+       redef fun write_extra_doc(view, stream)
        do
                # Introduced classes
-               var class_intros = collect_intro_mclasses(model.protected_view).to_a
+               var class_intros = collect_intro_mclasses(view).to_a
                if class_intros.not_empty then
                        alpha_comparator.sort class_intros
                        stream.write line_separator*2
@@ -361,7 +352,7 @@ redef class MModule
                # Introduced properties
                var prop_intros = new Array[MPropDef]
                for c in mclassdefs do
-                       prop_intros.add_all c.collect_intro_mpropdefs(model.protected_view)
+                       prop_intros.add_all c.collect_intro_mpropdefs(view)
                end
 
                if prop_intros.not_empty then
@@ -371,7 +362,7 @@ redef class MModule
                        stream.write line_separator
 
                        for p in prop_intros do
-                               p.mproperty.write_synopsis(mainmodule, stream)
+                               p.mproperty.write_synopsis(view.mainmodule, stream)
                        end
                end
        end
index 616977a..5ccc8da 100644 (file)
@@ -825,7 +825,7 @@ class InterpreterFrame
        super Frame
 
        # Mapping between a variable and the current value
-       private var map: Map[Variable, Instance] = new HashMap[Variable, Instance]
+       var map: Map[Variable, Instance] = new HashMap[Variable, Instance]
 end
 
 redef class ANode
@@ -874,7 +874,10 @@ redef class AMethPropdef
                return res
        end
 
-       private fun call_commons(v: NaiveInterpreter, mpropdef: MMethodDef, arguments: Array[Instance], f: Frame): nullable Instance
+       # Execution of the body of the method
+       #
+       # It handle the common special cases: super, intern, extern
+       fun call_commons(v: NaiveInterpreter, mpropdef: MMethodDef, arguments: Array[Instance], f: Frame): nullable Instance
        do
                v.frames.unshift(f)
 
index e335d8f..cd98e85 100644 (file)
@@ -33,8 +33,11 @@ class CodeSmellsMetricsPhase
 
        redef fun process_mainmodule(mainmodule, given_mmodules) do
                print toolcontext.format_h1("--- Code Smells Metrics ---")
-               self.set_all_average_metrics
-               var mclass_codesmell = new BadConceptonController
+
+               var filter = new ModelFilter(private_visibility)
+               var view = new ModelView(toolcontext.modelbuilder.model, mainmodule, filter)
+               self.set_all_average_metrics(view)
+               var mclass_codesmell = new BadConceptonController(view)
                var collect = new Counter[MClassDef]
                var mclassdefs = new Array[MClassDef]
 
@@ -48,17 +51,19 @@ class CodeSmellsMetricsPhase
                end
        end
 
-       fun set_all_average_metrics do
+       fun set_all_average_metrics(view: ModelView) do
                var model_builder = toolcontext.modelbuilder
-               var model_view = model_builder.model.private_view
-               self.average_number_of_lines = model_view.get_avg_linenumber(model_builder)
-               self.average_number_of_parameter = model_view.get_avg_parameter
-               self.average_number_of_method = model_view.get_avg_method
-               self.average_number_of_attribute = model_view.get_avg_attribut
+               self.average_number_of_lines = view.get_avg_linenumber(model_builder)
+               self.average_number_of_parameter = view.get_avg_parameter
+               self.average_number_of_method = view.get_avg_method
+               self.average_number_of_attribute = view.get_avg_attribut
        end
 end
 
 class BadConceptonController
+
+       var view: ModelView
+
        # Code smell list
        var bad_conception_elements = new Array[BadConceptionFinder]
 
@@ -79,7 +84,7 @@ class BadConceptonController
        # Collect method take Array of mclassdef to find the code smells for every class
        fun collect(mclassdefs: Array[MClassDef],phase: CodeSmellsMetricsPhase) do
                for mclassdef in mclassdefs do
-                       var bad_conception_class = new BadConceptionFinder(mclassdef,phase)
+                       var bad_conception_class = new BadConceptionFinder(mclassdef, phase, view)
                        bad_conception_class.collect
                        bad_conception_elements.add(bad_conception_class)
                end
@@ -112,17 +117,18 @@ class BadConceptionFinder
        var mclassdef: MClassDef
        var array_badconception = new Array[BadConception]
        var phase: CodeSmellsMetricsPhase
+       var view: ModelView
        var score = 0.0
 
        # Collect code smell with selected toolcontext option
        fun collect do
                var bad_conception_elements = new Array[BadConception]
                # Check toolcontext option
-               if phase.toolcontext.opt_feature_envy.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new FeatureEnvy(phase))
-               if phase.toolcontext.opt_long_method.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LongMethod(phase))
-               if phase.toolcontext.opt_long_params.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LongParameterList(phase))
-               if phase.toolcontext.opt_no_abstract_implementation.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new NoAbstractImplementation(phase))
-               if phase.toolcontext.opt_large_class.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LargeClass(phase))
+               if phase.toolcontext.opt_feature_envy.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new FeatureEnvy(phase, view))
+               if phase.toolcontext.opt_long_method.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LongMethod(phase, view))
+               if phase.toolcontext.opt_long_params.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LongParameterList(phase, view))
+               if phase.toolcontext.opt_no_abstract_implementation.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new NoAbstractImplementation(phase, view))
+               if phase.toolcontext.opt_large_class.value or phase.toolcontext.opt_all.value then bad_conception_elements.add(new LargeClass(phase, view))
                # Collected all code smell if their state is true
                for bad_conception_element in bad_conception_elements do
                        if bad_conception_element.collect(self.mclassdef,phase.toolcontext.modelbuilder) then array_badconception.add(bad_conception_element)
@@ -153,6 +159,8 @@ end
 abstract class BadConception
        var phase: CodeSmellsMetricsPhase
 
+       var view: ModelView
+
        var score = 0.0
 
        # Name
@@ -184,9 +192,9 @@ class LargeClass
        redef fun desc do return "Large class"
 
        redef fun collect(mclassdef, model_builder): Bool do
-               self.number_attribut = mclassdef.collect_intro_and_redef_mattributes(model_builder.model.private_view).length
+               self.number_attribut = mclassdef.collect_intro_and_redef_mattributes(view).length
                # Get the number of methods (Accessor include) (subtract the get and set of attibutes with (numberAtribut*2))
-               self.number_method = mclassdef.collect_intro_and_redef_methods(model_builder.model.private_view).length
+               self.number_method = mclassdef.collect_intro_and_redef_methods(view).length
                self.score_rate
                return self.number_method.to_f > phase.average_number_of_method and self.number_attribut.to_f > phase.average_number_of_attribute
        end
@@ -209,7 +217,7 @@ class LongParameterList
        redef fun desc do return "Long parameter list"
 
        redef fun collect(mclassdef, model_builder): Bool do
-               for meth in mclassdef.collect_intro_and_redef_mpropdefs(model_builder.model.private_view) do
+               for meth in mclassdef.collect_intro_and_redef_mpropdefs(view) do
                        var threshold_value = 4
                        # Get the threshold value from the toolcontext command
                        if phase.toolcontext.opt_long_params_threshold.value != 0 then threshold_value = phase.toolcontext.opt_long_params_threshold.value
@@ -250,7 +258,7 @@ class FeatureEnvy
        redef fun desc do return "Feature envy"
 
        redef fun collect(mclassdef, model_builder): Bool do
-               var mmethoddefs = call_analyze_methods(mclassdef,model_builder)
+               var mmethoddefs = call_analyze_methods(mclassdef,model_builder, view)
                for mmethoddef in mmethoddefs do
                        var max_class_call = mmethoddef.class_call.max
                        # Check if the class with the maximum call is >= auto-call and the maximum call class is != of this class
@@ -295,7 +303,7 @@ class LongMethod
        redef fun desc do return "Long method"
 
        redef fun collect(mclassdef, model_builder): Bool do
-               var mmethoddefs = call_analyze_methods(mclassdef,model_builder)
+               var mmethoddefs = call_analyze_methods(mclassdef,model_builder, view)
                var threshold_value = phase.average_number_of_lines.to_i
                # Get the threshold value from the toolcontext command
                if phase.toolcontext.opt_long_method_threshold.value != 0 then threshold_value = phase.toolcontext.opt_long_method_threshold.value
@@ -335,8 +343,8 @@ class NoAbstractImplementation
 
        redef fun collect(mclassdef, model_builder): Bool do
                if not mclassdef.mclass.is_abstract and not mclassdef.mclass.is_interface then
-                       if mclassdef.collect_abstract_methods(model_builder.model.private_view).not_empty then
-                               bad_methods.add_all(mclassdef.collect_not_define_properties(model_builder.model.private_view))
+                       if mclassdef.collect_abstract_methods(view).not_empty then
+                               bad_methods.add_all(mclassdef.collect_not_define_properties(view))
                        end
                end
                self.score_rate
@@ -399,7 +407,7 @@ redef class ModelView
                for mclassdef in mclassdefs do
                        var result = 0
                        var count = 0
-                       for mmethoddef in call_analyze_methods(mclassdef,model_builder) do
+                       for mmethoddef in call_analyze_methods(mclassdef,model_builder, self) do
                                result += mmethoddef.line_number
                                if mmethoddef.line_number == 0 then continue
                                count += 1
index a9a4a6d..29a15ce 100644 (file)
@@ -38,28 +38,28 @@ private class InheritanceMetricsPhase
                out.mkdir
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.private_view
+               var model_view = new ModelView(model, mainmodule)
 
                print toolcontext.format_h1("\n# Inheritance metrics")
 
                var hmetrics = new MetricSet
-               hmetrics.register(new MDUI(mainmodule, model_view))
-               hmetrics.register(new MDUIC(mainmodule, model_view))
-               hmetrics.register(new MDUII(mainmodule, model_view))
-               hmetrics.register(new MIF(mainmodule, model_view))
-               hmetrics.register(new MIFC(mainmodule, model_view))
-               hmetrics.register(new MIFI(mainmodule, model_view))
+               hmetrics.register(new MDUI(model_view))
+               hmetrics.register(new MDUIC(model_view))
+               hmetrics.register(new MDUII(model_view))
+               hmetrics.register(new MIF(model_view))
+               hmetrics.register(new MIFC(model_view))
+               hmetrics.register(new MIFI(model_view))
 
                var cmetrics = new MetricSet
-               cmetrics.register(new CNOAC(mainmodule, model_view))
-               cmetrics.register(new CNOPC(mainmodule, model_view))
-               cmetrics.register(new CNOCC(mainmodule, model_view))
-               cmetrics.register(new CNODC(mainmodule, model_view))
-               cmetrics.register(new CNOPI(mainmodule, model_view))
-               cmetrics.register(new CNOCI(mainmodule, model_view))
-               cmetrics.register(new CNODI(mainmodule, model_view))
-               cmetrics.register(new CDITC(mainmodule, model_view))
-               cmetrics.register(new CDITI(mainmodule, model_view))
+               cmetrics.register(new CNOAC(model_view))
+               cmetrics.register(new CNOPC(model_view))
+               cmetrics.register(new CNOCC(model_view))
+               cmetrics.register(new CNODC(model_view))
+               cmetrics.register(new CNOPI(model_view))
+               cmetrics.register(new CNOCI(model_view))
+               cmetrics.register(new CNODI(model_view))
+               cmetrics.register(new CDITC(model_view))
+               cmetrics.register(new CDITI(model_view))
 
                var mmodules = new HashSet[MModule]
                var mclasses = new HashSet[MClass]
@@ -116,7 +116,7 @@ class MDUI
                for mmodule in mmodules do
                        var count = 0
                        for mclass in mmodule.intro_mclasses do
-                               if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1
+                               if mclass.in_hierarchy(model_view.mainmodule).greaters.length > 2 then count += 1
                        end
                        if mmodule.intro_mclasses.is_empty then
                                values[mmodule] = 0.0
@@ -142,7 +142,7 @@ class MDUIC
                        var nb = 0
                        for mclass in mmodule.intro_mclasses do
                                if mclass.kind == abstract_kind or mclass.kind == concrete_kind or mclass.kind == extern_kind then
-                                       if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1
+                                       if mclass.in_hierarchy(model_view.mainmodule).greaters.length > 2 then count += 1
                                end
                                nb += 1
                        end
@@ -170,7 +170,7 @@ class MDUII
                        var nb = 0
                        for mclass in mmodule.intro_mclasses do
                                if mclass.kind == interface_kind then
-                                       if mclass.in_hierarchy(mainmodule).greaters.length > 2 then count += 1
+                                       if mclass.in_hierarchy(model_view.mainmodule).greaters.length > 2 then count += 1
                                end
                                nb += 1
                        end
@@ -196,7 +196,7 @@ class MIF
                for mmodule in mmodules do
                        var count = 0
                        for mclass in mmodule.intro_mclasses do
-                               if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1
+                               if mclass.in_hierarchy(model_view.mainmodule).direct_smallers.length > 0 then count += 1
                        end
                        if mmodule.intro_mclasses.is_empty then
                                values[mmodule] = 0.0
@@ -222,7 +222,7 @@ class MIFC
                        var nb = 0
                        for mclass in mmodule.intro_mclasses do
                                if mclass.kind == abstract_kind or mclass.kind == concrete_kind or mclass.kind == extern_kind then
-                                       if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1
+                                       if mclass.in_hierarchy(model_view.mainmodule).direct_smallers.length > 0 then count += 1
                                end
                                nb += 1
                        end
@@ -250,7 +250,7 @@ class MIFI
                        var nb = 0
                        for mclass in mmodule.intro_mclasses do
                                if mclass.kind == interface_kind then
-                                       if mclass.in_hierarchy(mainmodule).direct_smallers.length > 0 then count += 1
+                                       if mclass.in_hierarchy(model_view.mainmodule).direct_smallers.length > 0 then count += 1
                                end
                                nb += 1
                        end
@@ -275,7 +275,7 @@ class CNOAC
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).greaters do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).greaters do
                                if parent == mclass then continue
                                if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then
                                        count += 1
@@ -298,7 +298,7 @@ class CNOPC
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).direct_greaters do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).direct_greaters do
                                if parent == mclass then continue
                                if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then
                                        count += 1
@@ -321,7 +321,7 @@ class CNOCC
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).direct_smallers do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).direct_smallers do
                                if parent == mclass then continue
                                if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then
                                        count += 1
@@ -344,7 +344,7 @@ class CNODC
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).smallers do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).smallers do
                                if parent == mclass then continue
                                if parent.kind == abstract_kind or parent.kind == concrete_kind or parent.kind == extern_kind then
                                        count += 1
@@ -367,7 +367,7 @@ class CNOAA
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).greaters do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).greaters do
                                if parent == mclass then continue
                                if parent.kind == abstract_kind then
                                        count += 1
@@ -390,7 +390,7 @@ class CNOAI
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).greaters do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).greaters do
                                if parent == mclass then continue
                                if parent.kind == interface_kind then
                                        count += 1
@@ -413,7 +413,7 @@ class CNOPI
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).direct_greaters do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).direct_greaters do
                                if parent == mclass then continue
                                if parent.kind == interface_kind then
                                        count += 1
@@ -436,7 +436,7 @@ class CNOCI
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).direct_smallers do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).direct_smallers do
                                if parent == mclass then continue
                                if parent.kind == interface_kind then
                                        count += 1
@@ -459,7 +459,7 @@ class CNODI
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var count = 0
-                       for parent in mclass.in_hierarchy(mainmodule).smallers do
+                       for parent in mclass.in_hierarchy(model_view.mainmodule).smallers do
                                if parent == mclass then continue
                                if parent.kind == interface_kind then
                                        count += 1
@@ -481,7 +481,7 @@ class CDITC
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.ditc(mainmodule)
+                       values[mclass] = mclass.ditc(model_view.mainmodule)
                end
        end
 end
@@ -497,7 +497,7 @@ class CDITI
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.diti(mainmodule)
+                       values[mclass] = mclass.diti(model_view.mainmodule)
                end
        end
 end
index 872d66e..158a448 100644 (file)
@@ -37,24 +37,25 @@ private class MClassesMetricsPhase
                out.mkdir
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.private_view
+               var filter = new ModelFilter(private_visibility)
+               var model_view = new ModelView(model, mainmodule, filter)
 
                print toolcontext.format_h1("\n# MClasses metrics")
 
                var metrics = new MetricSet
-               metrics.register(new CNOA(mainmodule, model_view))
-               metrics.register(new CNOP(mainmodule, model_view))
-               metrics.register(new CNOC(mainmodule, model_view))
-               metrics.register(new CNOD(mainmodule, model_view))
-               metrics.register(new CDIT(mainmodule, model_view))
-               metrics.register(new CNBP(mainmodule, model_view))
-               metrics.register(new CNBA(mainmodule, model_view))
-               metrics.register(new CNBI(mainmodule, model_view))
-               metrics.register(new CNBM(mainmodule, model_view))
-               metrics.register(new CNBV(mainmodule, model_view))
-               metrics.register(new CNBIP(mainmodule, model_view))
-               metrics.register(new CNBRP(mainmodule, model_view))
-               metrics.register(new CNBHP(mainmodule, model_view))
+               metrics.register(new CNOA(model_view))
+               metrics.register(new CNOP(model_view))
+               metrics.register(new CNOC(model_view))
+               metrics.register(new CNOD(model_view))
+               metrics.register(new CDIT(model_view))
+               metrics.register(new CNBP(model_view))
+               metrics.register(new CNBA(model_view))
+               metrics.register(new CNBI(model_view))
+               metrics.register(new CNBM(model_view))
+               metrics.register(new CNBV(model_view))
+               metrics.register(new CNBIP(model_view))
+               metrics.register(new CNBRP(model_view))
+               metrics.register(new CNBHP(model_view))
 
                var mclasses = new HashSet[MClass]
                for mpackage in model.mpackages do
@@ -92,9 +93,6 @@ abstract class MClassMetric
        super Metric
        redef type ELM: MClass
 
-       # Main module used for class linearization
-       var mainmodule: MModule
-
        # Model view used to collect and filter entities
        var model_view: ModelView
 end
@@ -108,7 +106,7 @@ class CNOA
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.in_hierarchy(mainmodule).greaters.length - 1
+                       values[mclass] = mclass.in_hierarchy(model_view.mainmodule).greaters.length - 1
                end
        end
 end
@@ -122,7 +120,7 @@ class CNOP
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.in_hierarchy(mainmodule).direct_greaters.length
+                       values[mclass] = mclass.in_hierarchy(model_view.mainmodule).direct_greaters.length
                end
        end
 end
@@ -136,7 +134,7 @@ class CNOC
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.in_hierarchy(mainmodule).direct_smallers.length
+                       values[mclass] = mclass.in_hierarchy(model_view.mainmodule).direct_smallers.length
                end
        end
 end
@@ -150,7 +148,7 @@ class CNOD
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.in_hierarchy(mainmodule).smallers.length - 1
+                       values[mclass] = mclass.in_hierarchy(model_view.mainmodule).smallers.length - 1
                end
        end
 end
@@ -164,7 +162,7 @@ class CDIT
 
        redef fun collect(mclasses) do
                for mclass in mclasses do
-                       values[mclass] = mclass.in_hierarchy(mainmodule).depth
+                       values[mclass] = mclass.in_hierarchy(model_view.mainmodule).depth
                end
        end
 end
index 5bd6f97..e16e75f 100644 (file)
@@ -67,7 +67,8 @@ private class MendelMetricsPhase
                print toolcontext.format_h1("\n# Mendel metrics")
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.protected_view
+               var filter = new ModelFilter(min_visibility = protected_visibility)
+               var model_view = new ModelView(model, mainmodule, filter)
 
                var mclasses = new HashSet[MClass]
                for mclass in model_view.mclasses do
@@ -75,9 +76,9 @@ private class MendelMetricsPhase
                        mclasses.add(mclass)
                end
 
-               var cnblp = new CNBLP(mainmodule, model_view)
-               var cnvi = new CNVI(mainmodule, model_view)
-               var cnvs = new CNVS(mainmodule, model_view)
+               var cnblp = new CNBLP(model_view)
+               var cnvi = new CNVI(model_view)
+               var cnvs = new CNVS(model_view)
 
                var metrics = new MetricSet
                metrics.register(cnblp, cnvi, cnvs)
@@ -140,7 +141,7 @@ class CBMS
        redef fun collect(mclasses) do
                for mclass in mclasses do
                        var totc = mclass.collect_accessible_mproperties(model_view).length
-                       var ditc = mclass.in_hierarchy(mainmodule).depth
+                       var ditc = mclass.in_hierarchy(model_view.mainmodule).depth
                        values[mclass] = totc.to_f / (ditc + 1).to_f
                end
        end
@@ -173,10 +174,10 @@ class CNVI
        redef fun desc do return "class novelty index, contribution of the class to its branch in term of introductions"
 
        redef fun collect(mclasses) do
-               var cbms = new CBMS(mainmodule, model_view)
+               var cbms = new CBMS(model_view)
                for mclass in mclasses do
                        # compute branch mean size
-                       var parents = mclass.in_hierarchy(mainmodule).direct_greaters
+                       var parents = mclass.in_hierarchy(model_view.mainmodule).direct_greaters
                        if parents.length > 0 then
                                cbms.clear
                                cbms.collect(new HashSet[MClass].from(parents))
@@ -199,7 +200,7 @@ class MNVI
        redef fun desc do return "module novelty index, contribution of the module to its branch in term of introductions"
 
        redef fun collect(mmodules) do
-               var mbms = new MBMS(mainmodule, model_view)
+               var mbms = new MBMS(model_view)
                for mmodule in mmodules do
                        # compute branch mean size
                        var parents = mmodule.in_importation.direct_greaters
@@ -226,7 +227,7 @@ class CNVS
        redef fun desc do return "class novelty score, importance of the contribution of the class to its branch"
 
        redef fun collect(mclasses) do
-               var cnvi = new CNVI(mainmodule, model_view)
+               var cnvi = new CNVI(model_view)
                cnvi.collect(mclasses)
                for mclass in mclasses do
                        var locc = mclass.collect_local_mproperties(model_view).length
@@ -244,7 +245,7 @@ class MNVS
        redef fun desc do return "module novelty score, importance of the contribution of the module to its branch"
 
        redef fun collect(mmodules) do
-               var mnvi = new MNVI(mainmodule, model_view)
+               var mnvi = new MNVI(model_view)
                mnvi.collect(mmodules)
                for mmodule in mmodules do
                        var locc = mmodule.collect_intro_mclassdefs(model_view).length
index 3402abf..73333a0 100644 (file)
@@ -21,9 +21,9 @@ import nitsmell_toolcontext
 import mclassdef_collect
 
 
-fun call_analyze_methods(mclassdef: MClassDef, model_builder: ModelBuilder): Array[MMethodDef] do
+fun call_analyze_methods(mclassdef: MClassDef, model_builder: ModelBuilder, view: ModelView): Array[MMethodDef] do
        var mmethoddefs = new Array[MMethodDef]
-       for m_prop in mclassdef.collect_intro_and_redef_mpropdefs(model_builder.model.private_view) do
+       for m_prop in mclassdef.collect_intro_and_redef_mpropdefs(view) do
                var n_prop = model_builder.mpropdef2node(m_prop)
                #Check if the property is a method definition
                if n_prop isa AMethPropdef and m_prop isa MMethodDef then
index 1e9d190..3a6227f 100644 (file)
@@ -37,21 +37,21 @@ private class MModulesMetricsPhase
                out.mkdir
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.private_view
+               var model_view = new ModelView(model, mainmodule)
 
                print toolcontext.format_h1("\n# MModules metrics")
 
                var metrics = new MetricSet
-               metrics.register(new MNOA(mainmodule, model_view))
-               metrics.register(new MNOP(mainmodule, model_view))
-               metrics.register(new MNOC(mainmodule, model_view))
-               metrics.register(new MNOD(mainmodule, model_view))
-               metrics.register(new MDIT(mainmodule, model_view))
-               metrics.register(new MNBI(mainmodule, model_view))
-               metrics.register(new MNBR(mainmodule, model_view))
-               metrics.register(new MNBCC(mainmodule, model_view))
-               metrics.register(new MNBAC(mainmodule, model_view))
-               metrics.register(new MNBIC(mainmodule, model_view))
+               metrics.register(new MNOA(model_view))
+               metrics.register(new MNOP(model_view))
+               metrics.register(new MNOC(model_view))
+               metrics.register(new MNOD(model_view))
+               metrics.register(new MDIT(model_view))
+               metrics.register(new MNBI(model_view))
+               metrics.register(new MNBR(model_view))
+               metrics.register(new MNBCC(model_view))
+               metrics.register(new MNBAC(model_view))
+               metrics.register(new MNBIC(model_view))
 
                var mmodules = new HashSet[MModule]
                for mpackage in model.mpackages do
@@ -85,9 +85,6 @@ abstract class MModuleMetric
        super Metric
        redef type ELM: MModule
 
-       # Main module used for linearization
-       var mainmodule: MModule
-
        # Model view used to collect and filter entities
        var model_view: ModelView
 end
index f88c3ba..a7634f1 100644 (file)
@@ -40,11 +40,12 @@ private class NullablesMetricsPhase
                print toolcontext.format_h1("\n# Nullable metrics")
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.private_view
+               var filter = new ModelFilter(private_visibility)
+               var model_view = new ModelView(model, mainmodule, filter)
 
                var metrics = new MetricSet
-               metrics.register(new CNBA(mainmodule, model_view))
-               metrics.register(new CNBNA(mainmodule, model_view))
+               metrics.register(new CNBA(model_view))
+               metrics.register(new CNBNA(model_view))
 
                var mclasses = new HashSet[MClass]
                for mpackage in model.mpackages do
index a4c3e28..94a4dca 100644 (file)
@@ -38,25 +38,26 @@ private class RTAMetricsPhase
                out.mkdir
 
                var model = toolcontext.modelbuilder.model
-               var model_view = model.protected_view
+               var filter = new ModelFilter(min_visibility = protected_visibility)
+               var model_view = new ModelView(model, mainmodule, filter)
 
                print toolcontext.format_h1("\n# RTA metrics")
 
                print toolcontext.format_h2("\n ## Live instances by mainmodules")
                var mmetrics = new MetricSet
-               mmetrics.register(new MNLC(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNLT(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNCT(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNLI(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNLM(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNLMD(mainmodule, model_view, toolcontext.modelbuilder))
-               mmetrics.register(new MNLDD(mainmodule, model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLC(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLT(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNCT(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLI(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLM(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLMD(model_view, toolcontext.modelbuilder))
+               mmetrics.register(new MNLDD(model_view, toolcontext.modelbuilder))
                mmetrics.collect(new HashSet[MModule].from([mainmodule]))
                mmetrics.to_console(1, not toolcontext.opt_nocolors.value)
                if csv then mmetrics.to_csv.write_to_file("{out}/{mainmodule}.csv")
 
                var mtypes = new HashSet[MType]
-               var analysis = new RapidTypeAnalysis(toolcontext.modelbuilder, mainmodule)
+               var analysis = new MetricsRapidTypeAnalysis(toolcontext.modelbuilder, mainmodule, model_view)
                analysis.run_analysis
                mtypes.add_all(analysis.live_types)
                mtypes.add_all(analysis.live_cast_types)
@@ -139,7 +140,7 @@ class MNLI
 
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        values[mainmodule] = analysis.tnli.sum
                end
@@ -155,7 +156,7 @@ class MNLT
 
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        values[mainmodule] = analysis.live_types.length
                end
@@ -171,7 +172,7 @@ class MNCT
 
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        values[mainmodule] = analysis.live_cast_types.length
                end
@@ -188,7 +189,7 @@ class MNLC
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
                        var live = new HashSet[MClass]
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        for mtype in analysis.live_types do
                                live.add(mtype.mclass)
@@ -207,7 +208,7 @@ class MNLM
 
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        values[mainmodule] = analysis.live_methods.length
                end
@@ -223,7 +224,7 @@ class MNLMD
 
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        values[mainmodule] = analysis.live_methoddefs.length
                end
@@ -240,7 +241,7 @@ class MNLDD
        redef fun collect(mainmodules) do
                for mainmodule in mainmodules do
                        var dead = 0
-                       var analysis = new RapidTypeAnalysis(modelbuilder, mainmodule)
+                       var analysis = new MetricsRapidTypeAnalysis(modelbuilder, mainmodule, model_view)
                        analysis.run_analysis
                        for mmethod in analysis.live_methods do
                                for mdef in mmethod.mpropdefs do
@@ -351,13 +352,18 @@ end
 
 # rta redef
 
-redef class RapidTypeAnalysis
+# Custom RTA analyzer
+class MetricsRapidTypeAnalysis
+       super RapidTypeAnalysis
+
+       # Model view used to linearize classes
+       var view: ModelView
 
        # Class Live Instances
-       var cnli: CNLI is lazy do return new CNLI(mainmodule, modelbuilder.model.protected_view)
+       var cnli: CNLI is lazy do return new CNLI(view)
 
        # Class Live Casts
-       var cnlc: CNLC is lazy do return new CNLC(mainmodule, modelbuilder.model.protected_view)
+       var cnlc: CNLC is lazy do return new CNLC(view)
 
        # Type Live Instances
        var tnli = new TNLI
index dc9b978..3ceb9f3 100644 (file)
@@ -159,6 +159,14 @@ class ConcernsTree
        super OrderedTree[MConcern]
 end
 
+redef class MGroup
+       redef var is_test is lazy do
+               var parent = self.parent
+               if parent != null and parent.is_test then return true
+               return name == "tests"
+       end
+end
+
 redef class MModule
        # All the classes introduced in the module
        var intro_mclasses = new Array[MClass]
index 627f982..61d03a0 100644 (file)
@@ -22,7 +22,7 @@
 # First setup you view from a Model:
 #
 # ~~~nitih
-# var view = new ModelView(model)
+# var view = new ModelView(model, mainmodule)
 # ~~~
 #
 # Then ask question using the view:
@@ -117,22 +117,30 @@ redef class MEntity
        # * `MProperty`: property definitions graph (all propdefs flattened)
        # * `MPropDef`: property definitions graph
        fun hierarchy_poset(view: ModelView): POSet[MENTITY] do
-               var done = new HashSet[MENTITY]
-               var mentities = new Array[MENTITY]
-               mentities.add self
                var poset = new POSet[MENTITY]
-               while mentities.not_empty do
-                       var mentity = mentities.pop
-                       if done.has(mentity) then continue
-                       done.add mentity
+               var parents_done = new HashSet[MENTITY]
+               var parents = new Array[MENTITY]
+               parents.add self
+               while parents.not_empty do
+                       var mentity = parents.pop
+                       if parents_done.has(mentity) then continue
+                       parents_done.add mentity
                        poset.add_node mentity
                        for parent in mentity.collect_parents(view) do
                                poset.add_edge(mentity, parent)
-                               mentities.add parent
+                               parents.add parent
                        end
+               end
+               var children_done = new HashSet[MEntity]
+               var children = new Array[MEntity]
+               children.add self
+               while children.not_empty do
+                       var mentity = children.pop
+                       if children_done.has(mentity) then continue
+                       children_done.add mentity
                        for child in mentity.collect_children(view) do
                                poset.add_edge(child, mentity)
-                               mentities.add child
+                               children.add child
                        end
                end
                return poset
@@ -386,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]
@@ -571,24 +568,9 @@ redef class MClass
        # This method uses a flattened hierarchy containing all the mclassdefs.
        redef fun collect_parents(view) do
                var res = new HashSet[MENTITY]
-               for mclassdef in mclassdefs do
-                       for parent in mclassdef.collect_parents(view) do
-                               var mclass = parent.mclass
-                               if mclass == self or not view.accept_mentity(parent) then continue
-                               res.add mclass
-                       end
-               end
-               return res
-       end
-
-       # Collect all ancestors of `self`
-       redef fun collect_ancestors(view) do
-               var res = new HashSet[MENTITY]
-               for mclassdef in mclassdefs do
-                       for parent in mclassdef.collect_parents(view) do
-                               if not view.accept_mentity(parent) then continue
-                               res.add parent.mclass
-                       end
+               for mclass in in_hierarchy(view.mainmodule).direct_greaters do
+                       if mclass == self or not view.accept_mentity(mclass) then continue
+                       res.add mclass
                end
                return res
        end
@@ -598,12 +580,9 @@ redef class MClass
        # This method uses a flattened hierarchy containing all the mclassdefs.
        redef fun collect_children(view) do
                var res = new HashSet[MENTITY]
-               for mclassdef in mclassdefs do
-                       for child in mclassdef.collect_children(view) do
-                               var mclass = child.mclass
-                               if mclass == self or not view.accept_mentity(child) then continue
-                               res.add mclass
-                       end
+               for mclass in in_hierarchy(view.mainmodule).direct_smallers do
+                       if mclass == self or not view.accept_mentity(mclass) then continue
+                       res.add mclass
                end
                return res
        end
@@ -890,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
@@ -905,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
@@ -1052,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
diff --git a/src/model/model_filters.nit b/src/model/model_filters.nit
new file mode 100644 (file)
index 0000000..4cceddd
--- /dev/null
@@ -0,0 +1,198 @@
+# 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 model_filters
+
+import model
+
+# A list of filters that can be applied on a MEntity
+#
+#
+# By default ModelFilter accepts all mentity.
+#
+# ~~~nitish
+# var filter = new ModelFilter
+# assert filter.accept_mentity(my_mentity) == true
+# ~~~
+#
+# To quickly configure the filters, options can be passed to the constructor:
+# ~~~
+# var filter = new ModelFilter(
+#      min_visibility = protected_visibility,
+#      accept_fictive = false,
+#      accept_test = false,
+#      accept_redef = false,
+#      accept_extern = false,
+#      accept_attribute = false,
+#      accept_empty_doc = false
+# )
+# ~~~
+class ModelFilter
+
+       # Accept `mentity` based on all the options from `self`?
+       #
+       # If one of the filter returns `false` then the `mentity` is not accepted.
+       fun accept_mentity(mentity: MEntity): Bool do
+               if not accept_mentity_visibility(mentity) then return false
+               if not accept_mentity_fictive(mentity) then return false
+               if not accept_mentity_test(mentity) then return false
+               if not accept_mentity_redef(mentity) then return false
+               if not accept_mentity_extern(mentity) then return false
+               if not accept_mentity_attribute(mentity) then return false
+               if not accept_mentity_empty_doc(mentity) then return false
+               if not accept_mentity_inherited(mentity) then return false
+               if not accept_mentity_full_name(mentity) then return false
+               return true
+       end
+
+       # Minimum visibility an entity must have to be accepted
+       #
+       # Default is `private_visibility`.
+       var min_visibility: MVisibility = private_visibility is optional, writable
+
+       # Accept `mentity` if its visibility is above `min_visibility`
+       fun accept_mentity_visibility(mentity: MEntity): Bool do
+               return mentity.visibility >= min_visibility
+       end
+
+       # Accept fictive entities?
+       #
+       # Default is `true`.
+       var accept_fictive = true is optional, writable
+
+       # Accept only non-fictive entities
+       #
+       # See `MEntity::is_fictive`.
+       fun accept_mentity_fictive(mentity: MEntity): Bool do
+               if accept_fictive then return true
+               return not mentity.is_fictive
+       end
+
+       # Accept nitunit test suites?
+       #
+       # Default is `true`.
+       var accept_test = true is optional, writable
+
+       # Accept only entities that are not `nitunit` related
+       fun accept_mentity_test(mentity: MEntity): Bool do
+               if accept_test then return true
+               if mentity isa MProperty then return accept_mentity(mentity.intro)
+               if mentity isa MMethodDef then
+                       if mentity.is_before then return false
+                       if mentity.is_before_all then return false
+                       if mentity.is_after then return false
+                       if mentity.is_after_all then return false
+               end
+               return not mentity.is_test
+       end
+
+       # Accept redef classdefs and propdefs?
+       #
+       # Default is `true`.
+       var accept_redef = true is optional, writable
+
+       # Accept a MClassDefs and MPropeDefs onyl if they are an introduction
+       #
+       # See `MClassDef::is_intro` and `MPropDef::is_intro`.
+       fun accept_mentity_redef(mentity: MEntity): Bool do
+               if accept_redef then return true
+               if mentity isa MClassDef then
+                       return mentity.is_intro
+               else if mentity isa MPropDef then
+                       return mentity.is_intro
+               end
+               return true
+       end
+
+       # Accept extern entities?
+       #
+       # Default is `true`.
+       var accept_extern = true is optional, writable
+
+       # Accept only non- extern entities
+       #
+       # See `MEntity::is_extern`.
+       fun accept_mentity_extern(mentity: MEntity): Bool do
+               if accept_extern then return true
+               if mentity isa MMethodDef then
+                       return not mentity.is_extern
+               end
+               return true
+       end
+
+       # Accept `MAttribute` and `MAttributeDef` instances?
+       #
+       # Default is `true`.
+       var accept_attribute = true is optional, writable
+
+       # Accept only entities that are not a `MAttribute` or `MAttributeDef`
+       fun accept_mentity_attribute(mentity: MEntity): Bool do
+               if accept_attribute then return true
+               if mentity isa MAttribute then return false
+               if mentity isa MAttributeDef then return false
+               return true
+       end
+
+       # Accept entities with empty documentation?
+       #
+       # Default is `true`.
+       var accept_empty_doc = true is optional, writable
+
+       # Accept only entities with documentation
+       fun accept_mentity_empty_doc(mentity: MEntity): Bool do
+               if accept_empty_doc then return true
+               return mentity.mdoc_or_fallback != null
+       end
+
+       # If set, accept only entities local to `accept_inherited`
+       var accept_inherited: nullable MEntity = null is optional
+
+       # Accept only entities local to `accept_inherited`
+       #
+       # This means no imported or inherited entities.
+       fun accept_mentity_inherited(mentity: MEntity): Bool do
+               var context = accept_inherited
+               if context == null then return true
+               if context isa MPackage then
+                       if mentity isa MGroup then return mentity.mpackage == context
+                       if mentity isa MModule then return mentity.mpackage == context
+               end
+               if context isa MGroup then
+                       if mentity isa MModule then return mentity.mgroup == context
+               end
+               if context isa MModule then
+                       if mentity isa MClass then return mentity.intro.mmodule == context
+                       if mentity isa MClassDef then return mentity.mmodule == context
+               end
+               if context isa MClass then
+                       if mentity isa MProperty then return mentity.intro_mclassdef.mclass == context
+                       if mentity isa MPropDef then return mentity.mclassdef.mclass == context
+               end
+               if context isa MClassDef then
+                       if mentity isa MProperty then return mentity.intro_mclassdef == context
+                       if mentity isa MPropDef then return mentity.mclassdef == context
+               end
+               return true
+       end
+
+       # If set, accept only entities where `MEntity::full_name` contains `string`
+       var accept_full_name: nullable String = null is optional, writable
+
+       # Accept only entities where `MEntity::full_name` contains `string`
+       fun accept_mentity_full_name(mentity: MEntity): Bool do
+               var string = accept_full_name
+               if string == null then return true
+               return mentity.full_name.has(string)
+       end
+end
index 4136243..f8cfb24 100644 (file)
@@ -29,8 +29,8 @@
 #
 # ~~~nitish
 # var index = new ModelIndex
-#
-# for mentity in model.private_view.mentities do
+# var view = new ModelView(model, mainmodule)
+# for mentity in view.mentities do
 #      index.index(mentity)
 # end
 # ~~~
@@ -55,8 +55,8 @@
 #
 # ~~~nitish
 # var index = new ModelIndex
-#
-# for mentity in model.private_view.mentities do
+# var view = new ModelView(model, mainmodule)
+# for mentity in view.mentities do
 #      # We don't really care about definitions
 #      if mentity isa MClassDef or mentity isa MPropDef then continue
 #      index.index(mentity)
@@ -135,7 +135,7 @@ redef class ModelView
        # Keep a direct link to mentities by full name to speed up `mentity_from_uri`
        var mentities_by_full_name: HashMap[String, MEntity] is lazy do
                var mentities_by_full_name = new HashMap[String, MEntity]
-               for mentity in model.private_view.mentities do
+               for mentity in mentities do
                        mentities_by_full_name[mentity.full_name] = mentity
                end
                return mentities_by_full_name
@@ -144,15 +144,14 @@ redef class ModelView
        # ModelIndex used to perform searches
        var index: ModelIndex is lazy do
                var index = new ModelIndex
-               for mentity in model.private_view.mentities do
+               for mentity in mentities do
                        if mentity isa MClassDef or mentity isa MPropDef then continue
                        index.index mentity
                end
                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
@@ -229,7 +228,8 @@ end
 # ~~~nitish
 # # Build index
 # var index = new ModelIndex
-# for mentity in model.private_view.mentities do
+# var view = new ModelView(model, mainmodule)
+# for mentity in view.mentities do
 #      if mentity isa MClassDef or mentity isa MPropDef then continue
 #      index.index(mentity)
 # end
index 7c43e51..a48cef2 100644 (file)
@@ -57,9 +57,9 @@ redef class MEntity
        # Serialize the full version of `self` to JSON
        #
        # See: `FullJsonSerializer`
-       fun serialize_to_full_json(plain, pretty: nullable Bool): String do
+       fun serialize_to_full_json(mainmodule: MModule, plain, pretty: nullable Bool): String do
                var stream = new StringWriter
-               var serializer = new FullJsonSerializer(stream)
+               var serializer = new FullJsonSerializer(stream, mainmodule)
                serializer.plain_json = plain or else false
                serializer.pretty_json = pretty or else false
                serializer.serialize self
@@ -72,10 +72,14 @@ redef class MEntity
        # By default, every reference to another MEntity is replaced by a pointer
        # to the MEntity::json_id.
        # Use this method to obtain a full object with mentities instead of pointers.
-       fun to_full_json: String do return serialize_to_full_json(plain=true)
+       fun to_full_json(mainmodule: MModule): String do
+               return serialize_to_full_json(mainmodule, plain=true)
+       end
 
        # Same as `to_full_json` but with pretty json.
-       fun to_pretty_full_json: String do return serialize_to_full_json(plain=true, pretty=true)
+       fun to_pretty_full_json(mainmodule: MModule): String do
+               return serialize_to_full_json(mainmodule, plain=true, pretty=true)
+       end
 
        # Sort mentities by name
        private fun sort_entities(mentities: Collection[MEntity]): Array[MEntity] do
@@ -141,7 +145,7 @@ redef class MModule
        redef fun core_serialize_to(v) do
                super
                if v isa FullJsonSerializer then
-                       var view = private_view
+                       var view = new ModelView(model, v.mainmodule)
                        v.serialize_attribute("mpackage", to_mentity_ref(mpackage))
                        v.serialize_attribute("mgroup", to_mentity_ref(mgroup))
                        v.serialize_attribute("intro_mclasses", to_mentity_refs(sort_entities(intro_mclasses)))
@@ -158,7 +162,8 @@ redef class MClass
                super
                v.serialize_attribute("mparameters", mparameters)
                if v isa FullJsonSerializer then
-                       var view = private_view
+                       var filter = new ModelFilter(private_visibility)
+                       var view = new ModelView(model, v.mainmodule, filter)
                        v.serialize_attribute("intro", to_mentity_ref(intro))
                        v.serialize_attribute("intro_mmodule", to_mentity_ref(intro_mmodule))
                        v.serialize_attribute("mpackage", to_mentity_ref(intro_mmodule.mpackage))
@@ -177,7 +182,8 @@ redef class MClassDef
                v.serialize_attribute("is_intro", is_intro)
                v.serialize_attribute("mparameters", mclass.mparameters)
                if v isa FullJsonSerializer then
-                       var view = private_view
+                       var filter = new ModelFilter(private_visibility)
+                       var view = new ModelView(model, v.mainmodule, filter)
                        v.serialize_attribute("mmodule", to_mentity_ref(mmodule))
                        v.serialize_attribute("mclass", to_mentity_ref(mclass))
                        v.serialize_attribute("mpropdefs", to_mentity_refs(sort_entities(mpropdefs)))
@@ -311,4 +317,7 @@ end
 # See MEntity::to_full_json.
 class FullJsonSerializer
        super JsonSerializer
+
+       # FIXME tmp use of the mainmodule, a PR is comming to clean all the JSON mess
+       var mainmodule: MModule
 end
index 8b0f122..cbf3fe2 100644 (file)
@@ -25,9 +25,14 @@ import model_visitor
 class ModelView
        super ModelVisitor
 
+       autoinit(model, mainmodule, filter)
+
        # The model to view through `self`.
        var model: Model
 
+       # MModule used to flatten mclass hierarchy
+       var mainmodule: MModule
+
        # MPackages visible through `self`.
        var mpackages: Set[MPackage] is lazy do
                var mpackages = new HashSet[MPackage]
@@ -117,14 +122,6 @@ class ModelView
                return res
        end
 
-       private fun init_visitor(v: ModelVisitor) do
-               v.min_visibility = self.min_visibility
-               v.include_fictive = self.include_fictive
-               v.include_empty_doc = self.include_empty_doc
-               v.include_attribute = self.include_attribute
-               v.include_test = self.include_test
-       end
-
        # Searches the MEntity that matches `full_name`.
        fun mentity_by_full_name(full_name: String): nullable MEntity do
                for mentity in mentities do
@@ -133,10 +130,19 @@ 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
-               init_visitor(v)
+               v.filter = self.filter
                for mpackage in mpackages do
                        v.enter_visit(mpackage)
                end
@@ -175,28 +181,6 @@ class ModelTreeVisitor
 end
 
 redef class MEntity
-
-       # Get a public view of the model
-       fun public_view: ModelView do
-               var view = new ModelView(self.model)
-               view.min_visibility = public_visibility
-               return view
-       end
-
-       # Get a public view of the model
-       fun protected_view: ModelView do
-               var view = new ModelView(self.model)
-               view.min_visibility = protected_visibility
-               return view
-       end
-
-       # Get a public view of the model
-       fun private_view: ModelView do
-               var view = new ModelView(self.model)
-               view.min_visibility = private_visibility
-               return view
-       end
-
        private fun accept_namespace_visitor(v: LookupNamespaceVisitor) do
                if v.parts.is_empty then return
                if name != v.parts.first then return
index 9244055..e5a7409 100644 (file)
 # ~~~
 module model_visitor
 
-import model
+import model_filters
 
 # The abstract model visitor template.
 #
 # Specific visitor must implement the `visit` method to perform the work.
 abstract class ModelVisitor
+
        # Visit the entity `e`.
        #
        # This method setups `current_entity` and call `visit`.
@@ -68,81 +69,30 @@ abstract class ModelVisitor
        # It should not be called directly but used by `enter_visit`
        protected fun visit(e: MEntity) is abstract
 
-       # Filter classes and method on the visibility.
-       #
-       # If set, only the classes and method with at least the given
-       # visibility level will be visited.
-       var min_visibility: nullable MVisibility = null is writable
-
-       # Can we accept this `mentity` in the view regarding its visibility?
-       fun accept_visibility(mentity: MEntity): Bool do
-               return mentity.accept_visibility(min_visibility)
-       end
-
-       # Include fictive entities?
-       #
-       # By default, fictive entities (see `MEntity::is_fictive`) are not visited.
-       var include_fictive = false is writable
-
-       # Can we accept this `mentity` in the view regarding its fictivity?
-       fun accept_fictive(mentity: MEntity): Bool do
-               if include_fictive then return true
-               return not mentity.is_fictive
-       end
-
-       # Should we accept mentities with empty documentation?
+       # Filters to apply when visiting the model.
        #
-       # Default is `true`.
-       var include_empty_doc = true is writable
-
-       # Can we accept this `mentity` regarding its documentation?
-       fun accept_empty_doc(mentity: MEntity): Bool do
-               if include_empty_doc then return true
-               return mentity.mdoc != null
+       # See ModelFilters for configuration.
+       var filter: ModelFilter is lazy, writable, optional do
+               return new ModelFilter(
+                       min_visibility = protected_visibility,
+                       accept_fictive = false,
+                       accept_test = false,
+                       accept_redef = true,
+                       accept_extern = true,
+                       accept_attribute = true,
+                       accept_empty_doc = true
+               )
        end
 
-       # Should we accept nitunit test suites?
+       # Should we accept this `mentity` from the view?
        #
-       # Default is `false`.
-       var include_test = false is writable
-
-       # Can we accept this `mentity` regarding its test suite status?
-       fun accept_test(mentity: MEntity): Bool do
-               if include_test then return true
-               if mentity isa MProperty then
-                       if mentity.is_before or mentity.is_before_all then return false
-                       if mentity.is_after or mentity.is_after_all then return false
+       # If no `override_filter` is passed then use `self.filter`.
+       fun accept_mentity(mentity: MEntity, override_filter: nullable ModelFilter): Bool do
+               if override_filter != null then
+                       return override_filter.accept_mentity(mentity)
                end
-               if mentity isa MPropDef then
-                       if mentity.is_before or mentity.is_before_all then return false
-                       if mentity.is_after or mentity.is_after_all then return false
-               end
-               return not mentity.is_test
-       end
-
-       # Should we accept `MAttribute` instances?
-       #
-       # Default is `true`.
-       var include_attribute = true is writable
-
-       # Can we accept this `mentity` regarding its type?
-       fun accept_attribute(mentity: MEntity): Bool do
-               if include_attribute then return true
-               if mentity isa MAttribute then return false
-               if mentity isa MAttributeDef then return false
-               return true
+               return filter.accept_mentity(mentity)
        end
-
-       # Should we accept this `mentity` from the view?
-       fun accept_mentity(mentity: MEntity): Bool do
-               if not accept_visibility(mentity) then return false
-               if not accept_fictive(mentity) then return false
-               if not accept_empty_doc(mentity) then return false
-               if not accept_test(mentity) then return false
-               if not accept_attribute(mentity) then return false
-               return true
-       end
-
 end
 
 redef class MEntity
@@ -150,11 +100,6 @@ redef class MEntity
        #
        # See the specific implementation in the subclasses.
        fun visit_all(v: ModelVisitor) do end
-
-       private fun accept_visibility(min_visibility: nullable MVisibility): Bool do
-               if min_visibility == null then return true
-               return visibility >= min_visibility
-       end
 end
 
 redef class Model
index c544446..cc10f48 100644 (file)
@@ -23,6 +23,8 @@ class TestModelSerialization
        var suite_path: String = "NIT_TESTING_PATH".environ
        var lib_path: String = "{suite_path.dirname}/../../tests/test_prog"
 
+       var mainmodule: MModule is noinit
+
        private var model: Model do
                var toolcontext = new ToolContext
                var model = new Model
@@ -31,6 +33,7 @@ class TestModelSerialization
                if mmodules.is_empty then return model
                mbuilder.run_phases
                toolcontext.run_global_phases(mmodules)
+               mainmodule = mmodules.first
                return model
        end
 
@@ -40,54 +43,54 @@ class TestModelSerialization
                mentities.add model.mmodules.first
                mentities.add model.mclasses.first
                for mentity in mentities do
-                       print ((new MEntityRef(mentity)).to_pretty_full_json)
+                       print ((new MEntityRef(mentity)).to_pretty_full_json(mainmodule))
                end
        end
 
        fun test_packages_to_full_json is test do
                for mentity in model.mpackages do
-                       print mentity.to_pretty_full_json
+                       print mentity.to_pretty_full_json(mainmodule)
                end
        end
 
        fun test_groups_to_full_json is test do
                for mpackage in model.mpackages do
                        for mentity in mpackage.mgroups do
-                               print mentity.to_pretty_full_json
+                               print mentity.to_pretty_full_json(mainmodule)
                        end
                end
        end
 
        fun test_modules_to_full_json is test do
                for mentity in model.mmodules do
-                       print mentity.to_pretty_full_json
+                       print mentity.to_pretty_full_json(mainmodule)
                end
        end
 
        fun test_classes_to_full_json is test do
                for mentity in model.mclasses do
-                       print mentity.to_pretty_full_json
+                       print mentity.to_pretty_full_json(mainmodule)
                end
        end
 
        fun test_classdefs_to_full_json is test do
                for mclass in model.mclasses do
                        for mentity in mclass.mclassdefs do
-                               print mentity.to_pretty_full_json
+                               print mentity.to_pretty_full_json(mainmodule)
                        end
                end
        end
 
        fun test_props_to_full_json is test do
                for mentity in model.mproperties do
-                       print mentity.to_pretty_full_json
+                       print mentity.to_pretty_full_json(mainmodule)
                end
        end
 
        fun test_propdefs_to_full_json is test do
                for mprop in model.mproperties do
                        for mentity in mprop.mpropdefs do
-                               print mentity.to_pretty_full_json
+                               print mentity.to_pretty_full_json(mainmodule)
                        end
                end
        end
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 d5eb364..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,
        }],
        "parents": [{
                "full_name": "test_prog::Combatable"
-       }, {
-               "full_name": "test_prog::Object"
        }]
 }
 {
                }
        },
        "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 801e715..159c771 100644 (file)
@@ -41,9 +41,16 @@ private class Nitdoc
        super Phase
        redef fun process_mainmodule(mainmodule, mmodules)
        do
-               var doc = new DocModel(mainmodule.model, mainmodule)
-               if not toolcontext.opt_private.value then doc.min_visibility = protected_visibility
-               if not toolcontext.opt_no_attributes.value then doc.include_attribute = false
+               var min_visibility = private_visibility
+               if not toolcontext.opt_private.value then min_visibility = protected_visibility
+               var accept_attribute = true
+               if toolcontext.opt_no_attributes.value then accept_attribute = false
+
+               var filters = new ModelFilter(
+                       min_visibility,
+                       accept_attribute = accept_attribute,
+                       accept_fictive = false)
+               var doc = new DocModel(mainmodule.model, mainmodule, filters)
 
                var phases = [
                        new IndexingPhase(toolcontext, doc),
index 330370f..0b9bf72 100644 (file)
@@ -41,11 +41,12 @@ private class UMLPhase
        super Phase
        redef fun process_mainmodule(mainmodule, mmodules)
        do
-               var view = new ModelView(mainmodule.model)
+               var filters = new ModelFilter
                if not toolcontext.opt_privacy.value then
-                       view.min_visibility = protected_visibility
+                       filters.min_visibility = protected_visibility
                end
 
+               var view = new ModelView(mainmodule.model, mainmodule, filters)
                var d = new UMLModel(view, mainmodule)
                if toolcontext.opt_gen.value == 0 then
                        print d.generate_class_uml.write_to_string
index 370e2a2..2b61ab7 100644 (file)
@@ -30,12 +30,28 @@ redef class ToolContext
        # Port number to bind on (will overwrite the config one).
        var opt_port = new OptionInt("Port number to use", -1, "--port")
 
+       # --no-private
+       var opt_no_private = new OptionBool("Do not show private entities", "--no-private")
+
+       # --no-fictive
+       var opt_no_fictive = new OptionBool("Do not show fictive entities", "--no-fictive")
+
+       # --no-test
+       var opt_no_test = new OptionBool("Do not show test related entities", "--no-test")
+
+       # --no-attribute
+       var opt_no_attribute = new OptionBool("Do not show attributes", "--no-attribute")
+
+       # --no-empty-doc
+       var opt_no_empty_doc = new OptionBool("Do not undocumented entities", "--no-empty-doc")
+
        # Web rendering phase.
        var webphase: Phase = new NitwebPhase(self, null)
 
        init do
                super
-               option_context.add_option(opt_config, opt_host, opt_port)
+               option_context.add_option(opt_config, opt_host, opt_port, opt_no_private,
+                       opt_no_fictive, opt_no_test, opt_no_attribute, opt_no_empty_doc)
        end
 end
 
@@ -45,10 +61,20 @@ private class NitwebPhase
 
        # Build the nitweb config from `toolcontext` options.
        fun build_config(toolcontext: ToolContext, mainmodule: MModule): NitwebConfig do
-               var config = new NitwebConfig(
-                       toolcontext.modelbuilder.model,
-                       mainmodule,
-                       toolcontext.modelbuilder)
+
+               var model = toolcontext.modelbuilder.model
+
+               var filter = new ModelFilter(
+                       if toolcontext.opt_no_private.value then protected_visibility else private_visibility,
+                       accept_fictive = not toolcontext.opt_no_fictive.value,
+                       accept_empty_doc = not toolcontext.opt_no_empty_doc.value,
+                       accept_test = not toolcontext.opt_no_test.value,
+                       accept_attribute = not toolcontext.opt_no_attribute.value
+               )
+
+               var view = new ModelView(model, mainmodule, filter)
+
+               var config = new NitwebConfig(model, mainmodule, toolcontext.modelbuilder, view)
                var config_file = toolcontext.opt_config.value
                if config_file == null then config.default_config_file = "nitweb.ini"
                config.parse_options(args)
index dc10ac9..f8431a0 100644 (file)
@@ -1326,7 +1326,7 @@ redef class TBadString
     end
 end
 
-redef class TBadChar
+redef class TBadTString
     redef fun parser_index: Int
     do
        return 110
@@ -1338,7 +1338,7 @@ redef class TBadChar
     end
 end
 
-redef class TExternCodeSegment
+redef class TBadChar
     redef fun parser_index: Int
     do
        return 111
@@ -1350,11 +1350,35 @@ redef class TExternCodeSegment
     end
 end
 
+redef class TExternCodeSegment
+    redef fun parser_index: Int
+    do
+       return 112
+    end
+
+    init init_tk(loc: Location)
+    do
+               _location = loc
+    end
+end
+
+redef class TBadExtern
+    redef fun parser_index: Int
+    do
+       return 113
+    end
+
+    init init_tk(loc: Location)
+    do
+               _location = loc
+    end
+end
+
 
 redef class EOF
     redef fun parser_index: Int
     do
-       return 112
+       return 114
     end
 end
 
@@ -1692,11 +1716,17 @@ redef class Lexer
                        return new TBadString.init_tk(location)
                end
                if accept_token == 111 then
-                       return new TBadChar.init_tk(location)
+                       return new TBadTString.init_tk(location)
                end
                if accept_token == 112 then
+                       return new TBadChar.init_tk(location)
+               end
+               if accept_token == 113 then
                        return new TExternCodeSegment.init_tk(location)
                end
+               if accept_token == 114 then
+                       return new TBadExtern.init_tk(location)
+               end
                abort # unknown token index `accept_token`
        end
 end
index f626841..1931792 100644 (file)
@@ -218,10 +218,12 @@ start_string = id? '"' str_body '{' | id? '"' '"' '"' long_str_body lsend2;
 mid_string = '}' str_body '{' | '}' '}' '}' long_str_body lsend2;
 end_string = '}' str_body '"' id? | '}' '}' '}' long_str_body lsend1 id? ;
 char = id? ((''' [[any - '''] - '\'] ''') | (''' '\' any ''')) id?;
-bad_string = ('"'|'}') str_body | '"' '"' '"' long_str_body | ''' ''' ''' long_sstr_body;
+bad_string = ('"'|'}') str_body ;
+bad_t_string = '"' '"' '"' long_str_body | ''' ''' ''' long_sstr_body;
 bad_char = ''' '\'? any;
 
 extern_code_segment = '`' '{' extern_code_body '`' '}';
+bad_extern = '`' '{' extern_code_body;
 
 /*****************************************************************************/
 Ignored Tokens
index b6d7676..2f42bfc 100644 (file)
@@ -334,12 +334,18 @@ end
 class TBadString
        super Token
 end
+class TBadTString
+       super Token
+end
 class TBadChar
        super Token
 end
 class TExternCodeSegment
        super Token
 end
+class TBadExtern
+       super Token
+end
 class EOF
        super Token
 end
index 9726c3d..0c535c2 100644 (file)
@@ -1078,6 +1078,11 @@ class TBadString
        end
 end
 
+# A malformed triple quoted string
+class TBadTString
+       super TBadString
+end
+
 # A malformed char
 class TBadChar
        super Token
@@ -1092,6 +1097,15 @@ class TExternCodeSegment
        super Token
 end
 
+# A malformed extern code block
+class TBadExtern
+       super Token
+       redef fun to_s
+       do
+               do return "malformed extern segment {text}"
+       end
+end
+
 # A end of file
 class EOF
        super Token
index fe36ace..e424dc6 100644 (file)
@@ -3025,7 +3025,7 @@ const int* const lexer_goto_table[] = {
 };
 
 const int lexer_accept_table[] = {
-       -1,0,1,1,0,97,110,2,83,86,-1,56,57,80,78,60,79,77,82,103,103,61,99,90,63,93,98,100,58,59,85,-1,-1,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,84,110,87,1,89,110,105,-1,106,2,2,2,68,72,111,111,111,81,66,64,65,76,104,67,-1,103,-1,-1,-1,-1,103,-1,-1,-1,-1,-1,-1,62,92,91,88,94,95,100,100,100,100,71,-1,102,-1,-1,-1,101,101,101,101,101,101,48,101,101,101,101,17,101,101,101,101,101,101,24,101,30,16,101,101,101,101,101,101,101,32,101,101,101,101,101,101,101,101,101,101,101,101,101,101,70,110,108,-1,107,110,105,110,105,110,2,109,110,111,69,75,103,103,103,-1,-1,104,103,103,103,103,103,103,103,-1,-1,103,-1,-1,103,73,96,74,-1,102,102,102,102,-1,-1,-1,-1,105,-1,-1,-1,-1,101,101,31,101,101,101,101,101,101,11,101,101,101,29,12,101,101,101,41,101,101,101,101,40,33,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,18,101,101,101,108,110,110,110,110,110,-1,-1,-1,105,105,105,105,109,110,110,110,-1,-1,109,103,103,103,103,-1,-1,-1,112,-1,-1,-1,-1,101,101,101,101,101,101,101,26,9,101,101,101,101,14,101,101,101,101,28,101,47,42,101,101,101,101,101,101,44,101,101,25,45,13,101,101,52,101,108,108,108,108,110,-1,-1,108,-1,107,-1,-1,110,-1,-1,109,109,109,109,110,110,110,-1,-1,110,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,38,101,101,37,55,6,101,101,46,101,101,101,101,50,51,101,101,101,101,101,101,15,101,101,43,101,27,54,-1,-1,-1,-1,-1,108,-1,110,-1,-1,105,-1,-1,106,110,110,110,105,-1,110,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,101,39,101,19,101,5,101,101,4,101,101,101,101,20,35,10,101,-1,108,108,108,108,108,-1,-1,107,105,106,105,110,-1,-1,-1,-1,-1,-1,-1,-1,101,101,34,101,23,101,3,22,101,101,108,108,107,105,105,105,105,-1,-1,7,36,101,49,101,101,108,108,108,108,53,8,21,9
+       -1,0,1,1,0,97,110,2,83,86,-1,56,57,80,78,60,79,77,82,103,103,61,99,90,63,93,98,100,58,59,85,-1,-1,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,84,110,87,1,89,110,105,-1,106,2,2,2,68,72,112,112,112,81,66,64,65,76,104,67,-1,103,-1,-1,-1,-1,103,-1,-1,-1,-1,-1,-1,62,92,91,88,94,95,100,100,100,100,71,-1,102,114,-1,-1,101,101,101,101,101,101,48,101,101,101,101,17,101,101,101,101,101,101,24,101,30,16,101,101,101,101,101,101,101,32,101,101,101,101,101,101,101,101,101,101,101,101,101,101,70,110,108,-1,107,110,105,111,105,110,2,109,111,112,69,75,103,103,103,-1,-1,104,103,103,103,103,103,103,103,-1,-1,103,-1,-1,103,73,96,74,-1,102,102,102,102,114,-1,-1,-1,105,-1,-1,-1,-1,101,101,31,101,101,101,101,101,101,11,101,101,101,29,12,101,101,101,41,101,101,101,101,40,33,101,101,101,101,101,101,101,101,101,101,101,101,101,101,101,18,101,101,101,108,110,110,111,111,111,-1,-1,-1,105,105,105,105,109,111,111,111,-1,-1,109,103,103,103,103,-1,114,114,113,-1,-1,-1,-1,101,101,101,101,101,101,101,26,9,101,101,101,101,14,101,101,101,101,28,101,47,42,101,101,101,101,101,101,44,101,101,25,45,13,101,101,52,101,108,108,108,108,110,-1,-1,108,-1,107,-1,-1,111,-1,-1,109,109,109,109,111,111,111,-1,-1,111,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,38,101,101,37,55,6,101,101,46,101,101,101,101,50,51,101,101,101,101,101,101,15,101,101,43,101,27,54,-1,-1,-1,-1,-1,108,-1,110,-1,-1,105,-1,-1,106,111,111,111,105,-1,111,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,101,39,101,19,101,5,101,101,4,101,101,101,101,20,35,10,101,-1,108,108,108,108,108,-1,-1,107,105,106,105,111,-1,-1,-1,-1,-1,-1,-1,-1,101,101,34,101,23,101,3,22,101,101,108,108,107,105,105,105,105,-1,-1,7,36,101,49,101,101,108,108,108,108,53,8,21,9
 };
 
 static int parser_action_row1[] = {
@@ -3035,8 +3035,8 @@ static int parser_action_row1[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row2[] = {
        1,
@@ -3056,7 +3056,7 @@ static int parser_action_row4[] = {
 static int parser_action_row5[] = {
        2,
        -1, 3, 4,
-       112, 2, -1
+       114, 2, -1
 };
 static int parser_action_row6[] = {
        7,
@@ -3065,8 +3065,8 @@ static int parser_action_row6[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row7[] = {
        1,
@@ -3095,7 +3095,7 @@ static int parser_action_row12[] = {
 static int parser_action_row13[] = {
        2,
        -1, 1, 496,
-       112, 1, 23
+       114, 1, 23
 };
 static int parser_action_row14[] = {
        33,
@@ -3137,7 +3137,7 @@ static int parser_action_row15[] = {
        3,
        -1, 3, 14,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row16[] = {
        1,
@@ -3161,8 +3161,8 @@ static int parser_action_row19[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row20[] = {
        7,
@@ -3171,8 +3171,8 @@ static int parser_action_row20[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row21[] = {
        5,
@@ -3180,7 +3180,7 @@ static int parser_action_row21[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row22[] = {
        7,
@@ -3190,7 +3190,7 @@ static int parser_action_row22[] = {
        10, 1, 1075,
        29, 1, 1075,
        98, 0, 104,
-       111, 1, 1075
+       112, 1, 1075
 };
 static int parser_action_row23[] = {
        7,
@@ -3200,7 +3200,7 @@ static int parser_action_row23[] = {
        10, 1, 1077,
        29, 1, 1077,
        98, 0, 105,
-       111, 1, 1077
+       112, 1, 1077
 };
 static int parser_action_row24[] = {
        1,
@@ -3267,8 +3267,8 @@ static int parser_action_row29[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row30[] = {
        7,
@@ -3277,8 +3277,8 @@ static int parser_action_row30[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row31[] = {
        5,
@@ -3286,7 +3286,7 @@ static int parser_action_row31[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row32[] = {
        26,
@@ -3757,7 +3757,7 @@ static int parser_action_row86[] = {
 static int parser_action_row87[] = {
        2,
        -1, 3, 86,
-       111, 0, 263
+       112, 0, 263
 };
 static int parser_action_row88[] = {
        1,
@@ -3794,8 +3794,8 @@ static int parser_action_row95[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row96[] = {
        5,
@@ -3803,7 +3803,7 @@ static int parser_action_row96[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row97[] = {
        1,
@@ -3855,7 +3855,7 @@ static int parser_action_row100[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row101[] = {
        1,
@@ -3899,7 +3899,7 @@ static int parser_action_row108[] = {
        10, 1, 1076,
        29, 1, 1076,
        98, 0, 271,
-       111, 1, 1076
+       112, 1, 1076
 };
 static int parser_action_row109[] = {
        4,
@@ -3927,8 +3927,8 @@ static int parser_action_row112[] = {
        1, 0, 2,
        29, 1, 497,
        98, 0, 3,
-       111, 1, 497,
-       112, 1, 22
+       112, 1, 497,
+       114, 1, 22
 };
 static int parser_action_row113[] = {
        5,
@@ -3936,7 +3936,7 @@ static int parser_action_row113[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row114[] = {
        1,
@@ -3948,7 +3948,7 @@ static int parser_action_row115[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row116[] = {
        1,
@@ -5359,7 +5359,7 @@ static int parser_action_row266[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row267[] = {
        1,
@@ -5414,7 +5414,7 @@ static int parser_action_row275[] = {
        0, 0, 1,
        1, 0, 2,
        98, 0, 3,
-       112, 1, 22
+       114, 1, 22
 };
 static int parser_action_row276[] = {
        1,
@@ -7425,7 +7425,7 @@ static int parser_action_row522[] = {
        56, 1, 451,
        59, 1, 451,
        98, 1, 451,
-       112, 1, 451
+       114, 1, 451
 };
 static int parser_action_row523[] = {
        4,
@@ -9729,7 +9729,7 @@ static int parser_action_row726[] = {
        56, 1, 449,
        59, 1, 449,
        98, 1, 449,
-       112, 1, 449
+       114, 1, 449
 };
 static int parser_action_row727[] = {
        1,
@@ -10896,7 +10896,7 @@ static int parser_action_row866[] = {
        29, 0, 84,
        57, 0, 1015,
        98, 0, 3,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row867[] = {
        1,
@@ -12318,7 +12318,7 @@ static int parser_action_row1012[] = {
        3,
        -1, 3, 1011,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1013[] = {
        1,
@@ -14167,7 +14167,7 @@ static int parser_action_row1188[] = {
        3,
        -1, 3, 1187,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1189[] = {
        1,
@@ -15489,7 +15489,7 @@ static int parser_action_row1350[] = {
        16, 1, 83,
        29, 1, 83,
        98, 0, 3,
-       111, 1, 83
+       112, 1, 83
 };
 static int parser_action_row1351[] = {
        1,
@@ -17077,7 +17077,7 @@ static int parser_action_row1500[] = {
        1, 0, 2,
        29, 0, 84,
        98, 0, 3,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1501[] = {
        3,
@@ -19168,7 +19168,7 @@ static int parser_action_row1694[] = {
        3,
        -1, 3, 1693,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1695[] = {
        1,
@@ -19821,7 +19821,7 @@ static int parser_action_row1759[] = {
        3,
        -1, 3, 1758,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1760[] = {
        1,
@@ -20021,7 +20021,7 @@ static int parser_action_row1773[] = {
        3,
        -1, 3, 1772,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1774[] = {
        1,
@@ -20074,7 +20074,7 @@ static int parser_action_row1777[] = {
        3,
        -1, 3, 1776,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1778[] = {
        1,
@@ -20514,7 +20514,7 @@ static int parser_action_row1819[] = {
        3,
        -1, 3, 1818,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1820[] = {
        1,
@@ -20710,7 +20710,7 @@ static int parser_action_row1835[] = {
        3,
        -1, 3, 1834,
        29, 0, 84,
-       111, 0, 85
+       112, 0, 85
 };
 static int parser_action_row1836[] = {
        1,
index adb9543..98ac187 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,25 +54,44 @@ 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
 
-               var app_name = project.name
-               if not release then app_name += " Debug"
+               # 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
 
-               var short_project_name = project.short_name
+               # Gather app configs
+               # ---
 
+               var app_name = project.name
                var app_package = project.namespace
-               if not release then app_package += "_debug"
-
                var app_version = project.version
 
                var app_min_api = project.min_api
@@ -82,27 +101,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 basic directory structure
+               # ---
 
-               # create compile_dir
-               var dir = "{android_project_root}/jni/"
-               if not dir.file_exists then dir.mkdir
+               android_project_root.mkdir
+               android_app_root.mkdir
+               (android_app_root/"libs").mkdir
 
-               dir = compile_dir
-               if not dir.file_exists then dir.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 +249,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("-DALL_INTERIOR_POINTERS -DGC_THREADS -DUSE_MMAP -DUSE_MUNMAP -DJAVA_FINALIZATION -DNO_EXECUTE_PERMISSION -DGC_DONT_REGISTER_MAIN_STATIC_DATA")
+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,111 +357,97 @@ 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
+               var resolutions = ["ldpi", "mdpi", "hdpi", "xhdpi", "xxhdpi", "xxxhdpi", "anydpi", "anydpi-v26"]
+               var icon_name = null
+               var has_round = false
+
                for res in resolutions do
-                       var path = project_root / "android/res/drawable-{res}/icon.png"
-                       if path.file_exists then
-                               icon_available = true
+                       # New style mipmap
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher_round.png".file_exists then
+                               has_round = true
+                       end
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher.png".file_exists then
+                               icon_name = "@mipmap/ic_launcher"
                                break
                        end
+                       if "{project_root}/android/res/mipmap-{res}/ic_launcher.xml".file_exists then
+                               icon_name = "@mipmap/ic_launcher"
+                               break
+                       end
+               end
+               if icon_name == null then
+                       # Old style drawable-hdpi/icon.png
+                       for res in resolutions do
+                               var path = project_root / "android/res/drawable-{res}/icon.png"
+                               if path.file_exists then
+                                       icon_name = "@drawable/icon"
+                                       break
+                               end
+                       end
                end
 
                var icon_declaration
-               if icon_available then
-                       icon_declaration = "android:icon=\"@drawable/icon\""
+               if icon_name != null then
+                       icon_declaration = "android:icon=\"{icon_name}\""
+                       if app_target_api >= 25 and has_round then
+                               icon_declaration += "\n\t\tandroid:roundIcon=\"@mipmap/ic_launcher_round\""
+                       end
                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"
-
-               ## Generate delegating makefile
-               """
-include $(call all-subdir-makefiles)
-""".write_to_file "{dir}/Android.mk"
+               # ---
+               # /app/src/main/AndroidManifest.xml
 
-               # 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 +464,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 +476,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 +523,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 +532,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 38f72c8..74e0b4c 100644 (file)
@@ -62,10 +62,16 @@ var mmodules = mbuilder.parse_full([args.first])
 if mmodules.is_empty then return
 mbuilder.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)
+var view = new ModelView(model, mainmodule, filters)
 var index = new ModelIndex
-for mentity in model.private_view.mentities do
+for mentity in view.mentities do
        if mentity isa MClassDef or mentity isa MPropDef then continue
        index.index(mentity)
 end
index d7cc947..9596132 100644 (file)
@@ -55,42 +55,39 @@ do
        var model = modelbuilder.model
 
        print "All entities, including fictive ones:"
-       var v = new TestModelVisitor
-       v.min_visibility = private_visibility
-       v.include_fictive = true
+       var filters = new ModelFilter(private_visibility, accept_fictive = true)
+       var v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
        var names = v.names
 
        print "All entities:"
-       v = new TestModelVisitor
-       v.min_visibility = private_visibility
+       filters = new ModelFilter(private_visibility)
+       v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
        print "\nAll non-private entities:"
-       v = new TestModelVisitor
-       v.min_visibility = protected_visibility
+       filters = new ModelFilter(protected_visibility)
+       v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
        print "\nAll documented non-private entities:"
-       v = new TestModelVisitor
-       v.min_visibility = protected_visibility
-       v.include_empty_doc = false
+       filters = new ModelFilter(protected_visibility, accept_empty_doc = false)
+       v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
        print "\nAll public entities:"
-       v = new TestModelVisitor
-       v.min_visibility = public_visibility
+       filters = new ModelFilter(public_visibility)
+       v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
        print "\nAll documented public entities:"
-       v = new TestModelVisitor
-       v.min_visibility = public_visibility
-       v.include_empty_doc = false
+       filters = new ModelFilter(public_visibility, accept_empty_doc = false)
+       v = new TestModelVisitor(filters)
        v.enter_visit(model)
        v.cpt.print_elements(10)
 
index a0addf1..20935c2 100644 (file)
@@ -38,12 +38,14 @@ To test a method you have to instanciate its class:
        class Foo
                #    var foo = new Foo
                #    assert foo.baz(1, 2) == 3
-               fun baz(a, b: Int) do return a + b
+               fun baz(a, b: Int): Int do return a + b
        end
 
 `nitunit` is used to test Nit files:
 
+~~~sh
        $ nitunit foo.nit
+~~~
 
 ## Working with `TestSuites`
 
@@ -52,104 +54,175 @@ TestSuites are Nit files that define a set of TestCase for a particular module.
 The test suite module must be declared using the `test` annotation.
 The structure of a test suite is the following:
 
-       # test suite for module `foo`
-       module test_foo is test
+~~~nitish
+# test suite for module `foo`
+module test_foo is test
 
-       import foo # can be intrude to test private things
+import foo # can be intrude to test private things
 
-       class TestFoo
-               test
+class TestFoo
+       test
 
-               # test case for `foo::Foo::baz`
-               fun baz is test do
-                       var subject = new Foo
-                       assert subject.baz(1, 2) == 3
-               end
+       # test case for `foo::Foo::baz`
+       fun baz is test do
+               var subject = new Foo
+               assert subject.baz(1, 2) == 3
        end
+end
+~~~
 
 Test suite can be executed using the same `nitunit` command:
 
+~~~sh
        $ nitunit foo.nit
+~~~
 
 To be started automatically with nitunit, the module must be called `test_`
 followed by the name of the module to test.
 So for the module `foo.nit` the test suite will be called `test_foo.nit`.
 Otherwise, you can use the `-t` option to specify the test suite module name:
 
+~~~sh
        $ nitunit foo.nit -t my_test_suite.nit
+~~~
 
 `nitunit` will execute a test for each method annotated with `test` in a class also annotated with `test`
 so multiple tests can be executed for a single method:
 
-       class TestFoo
-               test
+~~~nitish
+class TestFoo
+       test
 
-               fun baz_1 is test do
-                       var subject = new Foo
-                       assert subject.baz(1, 2) == 3
-               end
+       fun baz_1 is test do
+               var subject = new Foo
+               assert subject.baz(1, 2) == 3
+       end
 
-               fun baz_2 is test do
-                       var subject = new Foo
-                       assert subject.baz(1, -2) == -1
-               end
+       fun baz_2 is test do
+               var subject = new Foo
+               assert subject.baz(1, -2) == -1
        end
+end
+~~~
 
 `TestSuites` also provide methods to configure the test run:
 
 `before` and `after` annotations can be added to methods that must be called before/after each test case.
 They can be used to factorize repetitive tasks:
 
-       class TestFoo
-               test
+~~~nitish
+class TestFoo
+       test
 
-               var subject: Foo is noinit
+       var subject: Foo is noinit
 
-               # Method executed before each test
-               redef fun set_up is before do
-                       subject = new Foo
-               end
+       # Method executed before each test
+       fun set_up is before do
+               subject = new Foo
+       end
 
-               fun baz_1 is test do
-                       assert subject.baz(1, 2) == 3
-               end
+       fun baz_1 is test do
+               assert subject.baz(1, 2) == 3
+       end
 
-               fun baz_2 is test do
-                       assert subject.baz(1, -2) == -1
-               end
+       fun baz_2 is test do
+               assert subject.baz(1, -2) == -1
        end
+end
+~~~
 
 When using custom test attributes, a empty init must be declared to allow automatic test running.
 
-`before_all` and `after_all` annotations can be set on methods that must be called before/after each test suite.
-They have to be declared at top level:
+At class level, `before_all` and `after_all` annotations can be set on methods that must be called before/after all the test cases in the class:
 
-       module test_bdd_connector
+~~~nitish
+class TestFoo
+       test
 
-       import bdd_connector
+       var subject: Foo is noinit
 
-       # Testing the bdd_connector
-       class TestConnector
-               test
-               # test cases using a server
+       # Method executed before all tests in the class
+       fun set_up is before_all do
+               subject = new Foo
        end
 
-       # Method executed before testing the module
-       fun before_module is before_all do
-               # start server before all test cases
+       fun baz_1 is test do
+               assert subject.baz(1, 2) == 3
        end
 
-       # Method executed after testing the module
-       fun after_module is after_all do
-               # stop server after all test cases
+       fun baz_2 is test do
+               assert subject.baz(1, -2) == -1
        end
+end
+~~~
+
+`before_all` and `after_all` annotations can also be set on methods that must be called before/after each test suite when declared at top level:
+
+~~~nitish
+module test_bdd_connector
+
+import bdd_connector
+
+# Testing the bdd_connector
+class TestConnector
+       test
+       # test cases using a server
+end
+
+# Method executed before testing the module
+fun setup_db is before_all do
+       # start server before all test cases
+end
+
+# Method executed after testing the module
+fun teardown_db is after_all do
+       # stop server after all test cases
+end
+~~~
+
+When dealing with multiple test suites, niunit allows you to import other test suites to factorize your tests:
+
+~~~nitish
+module test_bdd_users
+
+import test_bdd_connector
+
+# Testing the user table
+class TestUsersTable
+       test
+       # test cases using the db server from `test_bdd_connector`
+end
+
+fun setup_table is before_all do
+       # create user table
+end
+
+fun teardown_table is after_all do
+       # drop user table
+end
+~~~
+
+Methods with `before*` and `after*` annotations are linearized and called in different ways.
+
+* `before*` methods are called from the least specific to the most specific
+* `after*` methods are called from the most specific to the least specific
+
+In the previous example, the execution order would be:
+
+1. `test_bdd_connector::setup_db`
+2. `test_bdd_users::setup_table`
+3. `all test cases from test_bdd_users`
+4. `test_bdd_users::teardown_table`
+5. `test_bdd_connector::teardown_db`
 
 ## Generating test suites
 
 Write test suites for big modules can be a pepetitive and boring task...
 To make it easier, `nitunit` can generate test skeletons for Nit modules:
 
+~~~sh
        $ nitunit --gen-suite foo.nit
+~~~
 
 This will generate the test suite `test_foo` containing test case stubs for all public
 methods found in `foo.nit`.
index 43ac8f5..13cf20f 100644 (file)
@@ -48,6 +48,14 @@ class NitUnitTester
                        if not suite_match_pattern(mclassdef) then continue
                        toolcontext.modelbuilder.total_classes += 1
 
+                       var test_class = new TestClass
+
+                       # method to execute before all tests in the class
+                       for mmethod in mclassdef.before_all do
+                               toolcontext.modelbuilder.total_tests += 1
+                               test_class.before_all.add new TestCase(suite, mmethod, toolcontext)
+                       end
+
                        var before = mclassdef.before
                        var after = mclassdef.after
 
@@ -58,8 +66,16 @@ class NitUnitTester
                                var test = new TestCase(suite, mpropdef, toolcontext)
                                test.before = before
                                test.after = after
-                               suite.test_cases.add test
+                               test_class.test_cases.add test
                        end
+
+                       # method to execute after all tests in the class
+                       for mmethod in mclassdef.after_all do
+                               toolcontext.modelbuilder.total_tests += 1
+                               test_class.after_all.add new TestCase(suite, mmethod, toolcontext)
+                       end
+
+                       suite.test_classes.add test_class
                end
                # method to execute after all tests in the module
                for mmethod in mmodule.after_all do
@@ -115,7 +131,7 @@ class TestSuite
        var toolcontext: ToolContext
 
        # List of `TestCase` to be executed in this suite.
-       var test_cases = new Array[TestCase]
+       var test_classes = new Array[TestClass]
 
        # Tests to be executed before the whole test suite.
        var before_all = new Array[TestCase]
@@ -125,7 +141,12 @@ class TestSuite
 
        # Display test suite status in std-out.
        fun show_status do
-               var test_cases = self.test_cases.to_a
+               var test_cases = new Array[TestCase]
+               for test_class in test_classes do
+                       test_cases.add_all test_class.before_all
+                       test_cases.add_all test_class.test_cases
+                       test_cases.add_all test_class.after_all
+               end
                test_cases.add_all before_all
                test_cases.add_all after_all
                toolcontext.show_unit_status("Test-suite of module " + mmodule.full_name, test_cases)
@@ -141,11 +162,13 @@ class TestSuite
                write_to_nit
                compile
                if failure != null then
-                       for case in test_cases do
-                               case.fail "Compilation Error"
-                               case.raw_output = failure
-                               toolcontext.clear_progress_bar
-                               toolcontext.show_unit(case)
+                       for test_class in test_classes do
+                               for case in test_class.test_cases do
+                                       case.fail "Compilation Error"
+                                       case.raw_output = failure
+                                       toolcontext.clear_progress_bar
+                                       toolcontext.show_unit(case)
+                               end
                        end
                        show_status
                        print ""
@@ -158,13 +181,25 @@ class TestSuite
                        toolcontext.clear_progress_bar
                        toolcontext.show_unit(before_module)
                        if before_module.error != null then
-                               for case in test_cases do
-                                       case.fail "Nitunit Error: before_module test failed"
-                                       toolcontext.clear_progress_bar
-                                       toolcontext.show_unit(case)
+                               for test_class in test_classes do
+                                       for case in test_class.before_all do
+                                               case.fail "Nitunit Error: before module test failed"
+                                               toolcontext.clear_progress_bar
+                                               toolcontext.show_unit(case)
+                                       end
+                                       for case in test_class.test_cases do
+                                               case.fail "Nitunit Error: before module test failed"
+                                               toolcontext.clear_progress_bar
+                                               toolcontext.show_unit(case)
+                                       end
+                                       for case in test_class.after_all do
+                                               case.fail "Nitunit Error: before module test failed"
+                                               toolcontext.clear_progress_bar
+                                               toolcontext.show_unit(case)
+                                       end
                                end
                                for after_module in after_all do
-                                       after_module.fail "Nitunit Error: before_module test failed"
+                                       after_module.fail "Nitunit Error: before module test failed"
                                        toolcontext.clear_progress_bar
                                        toolcontext.show_unit(after_module)
                                end
@@ -174,11 +209,39 @@ class TestSuite
                        end
                end
 
-               for case in test_cases do
-                       case.run
-                       toolcontext.clear_progress_bar
-                       toolcontext.show_unit(case)
-                       show_status
+               for test_class in test_classes do
+                       for case in test_class.before_all do
+                               case.run
+                               toolcontext.clear_progress_bar
+                               toolcontext.show_unit(case)
+                               if case.error != null then
+                                       for scase in test_class.test_cases do
+                                               scase.fail "Nitunit Error: before class test failed"
+                                               toolcontext.clear_progress_bar
+                                               toolcontext.show_unit(scase)
+                                       end
+                                       for scase in test_class.after_all do
+                                               scase.fail "Nitunit Error: before class test failed"
+                                               toolcontext.clear_progress_bar
+                                               toolcontext.show_unit(scase)
+                                       end
+                                       show_status
+                                       print ""
+                                       return
+                               end
+                       end
+                       for case in test_class.test_cases do
+                               case.run
+                               toolcontext.clear_progress_bar
+                               toolcontext.show_unit(case)
+                               show_status
+                       end
+                       for after_class in test_class.after_all do
+                               after_class.run
+                               toolcontext.clear_progress_bar
+                               toolcontext.show_unit(after_class)
+                               show_status
+                       end
                end
 
                for after_module in after_all do
@@ -201,8 +264,16 @@ class TestSuite
                for before_module in before_all do
                        before_module.write_to_nit(file)
                end
-               for case in test_cases do
-                       case.write_to_nit(file)
+               for test_class in test_classes do
+                       for case in test_class.before_all do
+                               case.write_to_nit(file)
+                       end
+                       for case in test_class.test_cases do
+                               case.write_to_nit(file)
+                       end
+                       for case in test_class.after_all do
+                               case.write_to_nit(file)
+                       end
                end
                for after_module in after_all do
                        after_module.write_to_nit(file)
@@ -215,7 +286,9 @@ class TestSuite
        fun to_xml: HTMLTag do
                var n = new HTMLTag("testsuite")
                n.attr("package", mmodule.name)
-               for test in test_cases do n.add test.to_xml
+               for test_class in test_classes do
+                       for test in test_class.test_cases do n.add test.to_xml
+               end
                return n
        end
 
@@ -258,6 +331,20 @@ class TestSuite
        var failure: nullable String = null
 end
 
+# A test class contains multiple test cases
+#
+# For each test class, methods can be executed before and after all cases.
+class TestClass
+       # List of `TestCase` to be executed in this suite.
+       var test_cases = new Array[TestCase]
+
+       # Tests to be executed before the whole test suite.
+       var before_all = new Array[TestCase]
+
+       # Tests to be executed after the whole test suite.
+       var after_all = new Array[TestCase]
+end
+
 # A test case is a unit test considering only a `MMethodDef`.
 class TestCase
        super UnitTest
@@ -383,84 +470,110 @@ end
 redef class MClassDef
        # Methods tagged with `before` in this class definition
        private fun before: Array[MMethodDef] do
-               var res = new Array[MMethodDef]
+               var res = new ArraySet[MMethodDef]
                for mpropdef in mpropdefs do
                        if mpropdef isa MMethodDef and mpropdef.is_before then
                                res.add mpropdef
                        end
                end
                var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return res
+               if in_hierarchy == null then return res.to_a
                for mclassdef in in_hierarchy.direct_greaters do
                        res.add_all mclassdef.before
                end
-               return res
+               var lin = res.to_a
+               mmodule.linearize_mpropdefs(lin)
+               return lin
        end
 
        # Methods tagged with `before_all` in this class definition
        private fun before_all: Array[MMethodDef] do
-               var res = new Array[MMethodDef]
+               var res = new ArraySet[MMethodDef]
                for mpropdef in mpropdefs do
                        if mpropdef isa MMethodDef and mpropdef.is_before_all then
                                res.add mpropdef
                        end
                end
                var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return res
+               if in_hierarchy == null then return res.to_a
                for mclassdef in in_hierarchy.direct_greaters do
                        res.add_all mclassdef.before_all
                end
-               return res
+               var lin = res.to_a
+               mmodule.linearize_mpropdefs(lin)
+               return lin
        end
 
        # Methods tagged with `after` in this class definition
        private fun after: Array[MMethodDef] do
-               var res = new Array[MMethodDef]
+               var res = new ArraySet[MMethodDef]
                for mpropdef in mpropdefs do
                        if mpropdef isa MMethodDef and mpropdef.is_after then
                                res.add mpropdef
                        end
                end
                var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return res
+               if in_hierarchy == null then return res.to_a
                for mclassdef in in_hierarchy.direct_greaters do
                        res.add_all mclassdef.after
                end
-               return res
+               var lin = res.to_a
+               mmodule.linearize_mpropdefs(lin)
+               return lin.reversed
        end
 
        # Methods tagged with `after_all` in this class definition
        private fun after_all: Array[MMethodDef] do
-               var res = new Array[MMethodDef]
+               var res = new ArraySet[MMethodDef]
                for mpropdef in mpropdefs do
                        if mpropdef isa MMethodDef and mpropdef.is_after_all then
                                res.add mpropdef
                        end
                end
                var in_hierarchy = self.in_hierarchy
-               if in_hierarchy == null then return res
+               if in_hierarchy == null then return res.to_a
                for mclassdef in in_hierarchy.direct_greaters do
                        res.add_all mclassdef.after_all
                end
-               return res
+               var lin = res.to_a
+               mmodule.linearize_mpropdefs(lin)
+               return lin.reversed
        end
 end
 
 redef class MModule
        # Methods tagged with `before_all` at the module level (in `Sys`)
        private fun before_all: Array[MMethodDef] do
-               for mclassdef in mclassdefs do
-                       if mclassdef.name == "Sys" then return mclassdef.before_all
+               var res = new Array[MMethodDef]
+               for mmodule in in_importation.greaters do
+                       for mclassdef in mmodule.mclassdefs do
+                               if mclassdef.name != "Sys" then continue
+                               for mpropdef in mclassdef.mpropdefs do
+                                       if not mpropdef isa MMethodDef or not mpropdef.is_before_all then continue
+                                       res.add mpropdef
+                               end
+                       end
                end
-               return new Array[MMethodDef]
+               var lin = res.to_a
+               linearize_mpropdefs(lin)
+               return lin
        end
 
        # Methods tagged with `after_all` at the module level (in `Sys`)
        private fun after_all: Array[MMethodDef] do
-               for mclassdef in mclassdefs do
-                       if mclassdef.name == "Sys" then return mclassdef.after_all
+               var res = new Array[MMethodDef]
+               for mmodule in in_importation.greaters do
+                       for mclassdef in mmodule.mclassdefs do
+                               if mclassdef.name != "Sys" then continue
+                               for mpropdef in mclassdef.mpropdefs do
+                                       if not mpropdef isa MMethodDef or not mpropdef.is_after_all then continue
+                                       res.add mpropdef
+                               end
+                       end
                end
-               return new Array[MMethodDef]
+               var lin = res.to_a
+               linearize_mpropdefs(lin)
+               return lin.reversed
        end
 end
 
index 4d4eac9..afad834 100644 (file)
@@ -15,7 +15,7 @@
 module api_catalog
 
 import api_model
-import catalog
+import catalog::catalog_json
 
 redef class NitwebConfig
 
@@ -213,9 +213,9 @@ redef class APIEntity
 
                # Special case for packages (catalog view)
                if mentity isa MPackage then
-                       res.raw_json mentity.to_full_catalog_json(plain=true, config.catalog)
+                       res.raw_json mentity.to_full_catalog_json(plain=true, config.mainmodule, config.catalog)
                else
-                       res.raw_json mentity.to_full_json
+                       res.raw_json mentity.to_full_json(config.mainmodule)
                end
        end
 end
@@ -301,80 +301,13 @@ 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
        #
        # See: `FullCatalogSerializer`
-       fun to_full_catalog_json(catalog: Catalog, plain, pretty: nullable Bool): String do
+       fun to_full_catalog_json(mainmodule: MModule, catalog: Catalog, plain, pretty: nullable Bool): String do
                var stream = new StringWriter
-               var serializer = new FullCatalogSerializer(stream, catalog)
+               var serializer = new FullCatalogSerializer(stream, mainmodule, catalog)
                serializer.plain_json = plain or else false
                serializer.pretty_json = pretty or else false
                serializer.serialize self
index 13a2177..33ff7f8 100644 (file)
@@ -46,6 +46,8 @@ end
 class InheritanceGraph
        super ModelVisitor
 
+       autoinit center, view, filter
+
        # MEntity at the center of this graph
        var center: MEntity
 
index 03518e4..21f84b4 100644 (file)
@@ -28,41 +28,39 @@ class APIStructuralMetrics
        super APIHandler
 
        private fun mclasses_metrics: MetricSet do
-               var mainmodule = config.mainmodule
                var metrics = new MetricSet
-               metrics.register(new CNOA(mainmodule, config.view))
-               metrics.register(new CNOP(mainmodule, config.view))
-               metrics.register(new CNOC(mainmodule, config.view))
-               metrics.register(new CNOD(mainmodule, config.view))
-               metrics.register(new CNOAC(mainmodule, config.view))
-               metrics.register(new CNOAA(mainmodule, config.view))
-               metrics.register(new CNOAI(mainmodule, config.view))
-               metrics.register(new CDIT(mainmodule, config.view))
-               metrics.register(new CNBP(mainmodule, config.view))
-               metrics.register(new CNBA(mainmodule, config.view))
-               metrics.register(new CNBM(mainmodule, config.view))
-               metrics.register(new CNBI(mainmodule, config.view))
-               metrics.register(new CNBV(mainmodule, config.view))
-               metrics.register(new CNBIP(mainmodule, config.view))
-               metrics.register(new CNBRP(mainmodule, config.view))
-               metrics.register(new CNBHP(mainmodule, config.view))
-               metrics.register(new CNBLP(mainmodule, config.view))
+               metrics.register(new CNOA(config.view))
+               metrics.register(new CNOP(config.view))
+               metrics.register(new CNOC(config.view))
+               metrics.register(new CNOD(config.view))
+               metrics.register(new CNOAC(config.view))
+               metrics.register(new CNOAA(config.view))
+               metrics.register(new CNOAI(config.view))
+               metrics.register(new CDIT(config.view))
+               metrics.register(new CNBP(config.view))
+               metrics.register(new CNBA(config.view))
+               metrics.register(new CNBM(config.view))
+               metrics.register(new CNBI(config.view))
+               metrics.register(new CNBV(config.view))
+               metrics.register(new CNBIP(config.view))
+               metrics.register(new CNBRP(config.view))
+               metrics.register(new CNBHP(config.view))
+               metrics.register(new CNBLP(config.view))
                return metrics
        end
 
        private fun mmodules_metrics: MetricSet do
-               var mainmodule = config.mainmodule
                var metrics = new MetricSet
-               metrics.register(new MNOA(mainmodule, config.view))
-               metrics.register(new MNOP(mainmodule, config.view))
-               metrics.register(new MNOC(mainmodule, config.view))
-               metrics.register(new MNOD(mainmodule, config.view))
-               metrics.register(new MDIT(mainmodule, config.view))
-               metrics.register(new MNBD(mainmodule, config.view))
-               metrics.register(new MNBI(mainmodule, config.view))
-               metrics.register(new MNBR(mainmodule, config.view))
-               metrics.register(new MNBCC(mainmodule, config.view))
-               metrics.register(new MNBAC(mainmodule, config.view))
+               metrics.register(new MNOA(config.view))
+               metrics.register(new MNOP(config.view))
+               metrics.register(new MNOC(config.view))
+               metrics.register(new MNOD(config.view))
+               metrics.register(new MDIT(config.view))
+               metrics.register(new MNBD(config.view))
+               metrics.register(new MNBI(config.view))
+               metrics.register(new MNBR(config.view))
+               metrics.register(new MNBCC(config.view))
+               metrics.register(new MNBAC(config.view))
                return metrics
        end
 
index 3d09a83..b7c35c0 100644 (file)
@@ -70,10 +70,19 @@ class APIList
                return mentities
        end
 
+       # Filter mentities based on the config view filters
+       fun filter_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
+               var res = new Array[MEntity]
+               for mentity in mentities do
+                       if config.view.filter.accept_mentity(mentity) then res.add mentity
+               end
+               return res
+       end
+
        # Sort mentities by lexicographic order
        #
        # TODO choose order from request
-       fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]) : Array[MEntity] do
+       fun sort_mentities(req: HttpRequest, mentities: Array[MEntity]): Array[MEntity] do
                var sorted = mentities.to_a
                var sorter = new MEntityNameSorter
                sorter.sort(sorted)
@@ -135,6 +144,7 @@ class APIRandom
 
        redef fun get(req, res) do
                var mentities = list_mentities(req)
+               mentities = filter_mentities(req, mentities)
                mentities = randomize_mentities(req, mentities)
                mentities = limit_mentities(req, mentities)
                res.json new JsonArray.from(mentities)
@@ -150,7 +160,7 @@ class APIEntity
        redef fun get(req, res) do
                var mentity = mentity_from_uri(req, res)
                if mentity == null then return
-               res.raw_json mentity.to_full_json
+               res.raw_json mentity.to_full_json(config.view.mainmodule)
        end
 end
 
@@ -235,6 +245,7 @@ class APIEntityDefs
                        res.api_error(404, "No definition list for mentity `{mentity.full_name}`")
                        return
                end
+               mentities = filter_mentities(req, mentities)
                mentities = sort_mentities(req, mentities)
                mentities = limit_mentities(req, mentities)
                res.json new JsonArray.from(mentities)
index ac63f53..4aad814 100644 (file)
@@ -38,18 +38,8 @@ class NitwebConfig
        # Modelbuilder used to access sources.
        var modelbuilder: ModelBuilder
 
-       # The JSON API does not filter anything by default.
-       #
-       # So we can cache the model view.
-       var view: ModelView is lazy do
-               var view = new ModelView(model)
-               view.min_visibility = private_visibility
-               view.include_fictive = true
-               view.include_empty_doc = true
-               view.include_attribute = true
-               view.include_test = true
-               return view
-       end
+       # ModelView used to access model.
+       var view: ModelView
 end
 
 # Specific handler for the nitweb API.
@@ -62,7 +52,10 @@ abstract class APIHandler
        # Find the MEntity ` with `full_name`.
        fun find_mentity(model: ModelView, full_name: nullable String): nullable MEntity do
                if full_name == null then return null
-               return model.mentity_by_full_name(full_name.from_percent_encoding)
+               var mentity = model.mentity_by_full_name(full_name.from_percent_encoding)
+               if mentity == null then return null
+               if config.view.accept_mentity(mentity) then return mentity
+               return null
        end
 
        # Try to load the mentity from uri with `/:id`.
index ee5fd03..c643d3d 100644 (file)
@@ -1,4 +1,4 @@
 module_1.nit -d $WRITE
 base_attr_nullable.nit -d $WRITE
---private base_attr_nullable.nit -d $WRITE
+--private base_attr_nullable.nit --no-attributes -d $WRITE
 --no-render --test test_prog -d $WRITE
index f11b0b1..74ce65f 100644 (file)
@@ -10,3 +10,5 @@ test_nitunit4 --no-color -o $WRITE
 test_nitunit5.nit --no-color -o $WRITE
 test_nitunit6.nit --no-color -o $WRITE
 test_nitunit7.nit --no-color -o $WRITE
+test_nitunit8.nit --no-color -o $WRITE
+test_nitunit11.nit --no-color -o $WRITE
index 42ee33e..0d1829d 100644 (file)
@@ -5,12 +5,12 @@
        Runtime error: Assert failed (test_nitunit6.nit:26)
 
 [KO] test_nitunit6$TestNitunit6$test_foo
-     test_nitunit6.nit:20,2--22,4: Nitunit Error: before_module test failed
+     test_nitunit6.nit:20,2--22,4: Nitunit Error: before module test failed
 [KO] test_nitunit6::test_nitunit6$core::Sys$after_module
-     test_nitunit6.nit:29,1--31,3: Nitunit Error: before_module test failed
+     test_nitunit6.nit:29,1--31,3: Nitunit Error: before module test failed
 
 Docunits: Entities: 5; Documented ones: 0; With nitunits: 0
 Test suites: Classes: 1; Test Cases: 3; Failures: 3
 [FAILURE] 3/3 tests failed.
 `nitunit.out` is not removed for investigation.
-<testsuites><testsuite package="test_nitunit6::test_nitunit6"></testsuite><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before_module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
+<testsuites><testsuite package="test_nitunit6::test_nitunit6"></testsuite><testsuite package="test_nitunit6"><testcase classname="nitunit.test_nitunit6.TestNitunit6" name="test_foo" time="0.0"><failure message="Nitunit Error: before module test failed"></failure></testcase></testsuite></testsuites>
\ No newline at end of file
diff --git a/tests/sav/nitunit_args13.res b/tests/sav/nitunit_args13.res
new file mode 100644 (file)
index 0000000..8c8d3f9
--- /dev/null
@@ -0,0 +1,14 @@
+==== Test-suite of module test_nitunit8::test_nitunit8 | tests: 3
+[OK] test_nitunit7::test_nitunit7$core::Sys$before_module
+[OK] test_nitunit8$TestNitunit8$test_foo
+[KO] test_nitunit7::test_nitunit7$core::Sys$after_module
+     test_nitunit7.nit:29,1--31,3: Runtime Error in file nitunit.out/gen_test_nitunit8.nit
+     Output
+       Runtime error: Assert failed (test_nitunit7.nit:30)
+
+
+Docunits: Entities: 3; Documented ones: 0; With nitunits: 0
+Test suites: Classes: 1; Test Cases: 3; Failures: 1
+[FAILURE] 1/3 tests failed.
+`nitunit.out` is not removed for investigation.
+<testsuites><testsuite package="test_nitunit8::test_nitunit8"></testsuite><testsuite package="test_nitunit8"><testcase classname="nitunit.test_nitunit8.TestNitunit8" name="test_foo" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
diff --git a/tests/sav/nitunit_args14.res b/tests/sav/nitunit_args14.res
new file mode 100644 (file)
index 0000000..c0f1e23
--- /dev/null
@@ -0,0 +1,18 @@
+==== Test-suite of module test_nitunit11::test_nitunit11 | tests: 7
+[OK] test_nitunit9$TestNitunit9$before_class
+[OK] test_nitunit10$TestNitunit10$before_class2
+[OK] test_nitunit11$TestNitunit11$before_class3
+[OK] test_nitunit11$TestNitunit11$test_baz
+[OK] test_nitunit11$TestNitunit11$after_class3
+[OK] test_nitunit10$TestNitunit10$after_class2
+[KO] test_nitunit9$TestNitunit9$after_class
+     test_nitunit9.nit:36,2--38,4: Runtime Error in file nitunit.out/gen_test_nitunit11.nit
+     Output
+       Runtime error: Assert failed (test_nitunit9.nit:37)
+
+
+Docunits: Entities: 7; Documented ones: 0; With nitunits: 0
+Test suites: Classes: 1; Test Cases: 7; Failures: 1
+[FAILURE] 1/7 tests failed.
+`nitunit.out` is not removed for investigation.
+<testsuites><testsuite package="test_nitunit11::test_nitunit11"></testsuite><testsuite package="test_nitunit11"><testcase classname="nitunit.test_nitunit11.TestNitunit11" name="test_baz" time="0.0"><system-err></system-err></testcase></testsuite></testsuites>
\ No newline at end of file
diff --git a/tests/sav/nlp_index.res b/tests/sav/nlp_index.res
new file mode 100644 (file)
index 0000000..b041485
--- /dev/null
@@ -0,0 +1,6 @@
+usage: example_index <files>
+  -h, --help             Show this help message
+  -s, --server           StanfordNLP server URI (default is https://localhost:9000)
+  -l, --lang             Language to use (default is fr)
+  -w, --whitelist-exts   Allowed file extensions (default is [])
+  -b, --blacklist-exts   Allowed file extensions (default is [])
index 9fb5305..57d863d 100644 (file)
@@ -2,13 +2,13 @@ Compilation des classes Java ...
 Initialisation de la JVM ...
 ---------------------Test 1----------------------
 From java, pushing premier
-From java, pushing deuxi?me
-From java, pushing troisi?me
+From java, pushing deuxième
+From java, pushing troisième
 From java, popping premier
 premier
-From java, popping deuxi?me
+From java, popping deuxième
 deuxième
-From java, popping troisi?me
+From java, popping troisième
 troisième
 --------------------Test 2---------------------
 true
diff --git a/tests/test_nitunit10.nit b/tests/test_nitunit10.nit
new file mode 100644 (file)
index 0000000..9166f24
--- /dev/null
@@ -0,0 +1,42 @@
+# 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_nitunit10 is test
+
+import test_nitunit9
+
+class TestNitunit10
+       super TestNitunit9
+       test
+
+       fun before_class2 is before_all do
+               assert true
+       end
+
+       fun before2 is before do
+               assert true
+       end
+
+       fun test_bar is test do
+               assert true
+       end
+
+       fun after2 is after do
+               assert true
+       end
+
+       fun after_class2 is after_all do
+               assert true
+       end
+end
diff --git a/tests/test_nitunit11.nit b/tests/test_nitunit11.nit
new file mode 100644 (file)
index 0000000..1eb67c3
--- /dev/null
@@ -0,0 +1,42 @@
+# 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_nitunit11 is test
+
+import test_nitunit10
+
+class TestNitunit11
+       super TestNitunit10
+       test
+
+       fun before_class3 is before_all do
+               assert true
+       end
+
+       fun before3 is before do
+               assert true
+       end
+
+       fun test_baz is test do
+               assert true
+       end
+
+       fun after3 is after do
+               assert true
+       end
+
+       fun after_class3 is after_all do
+               assert true
+       end
+end
diff --git a/tests/test_nitunit8.nit b/tests/test_nitunit8.nit
new file mode 100644 (file)
index 0000000..92ef0bc
--- /dev/null
@@ -0,0 +1,24 @@
+# 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_nitunit8 is test
+import test_nitunit7
+
+class TestNitunit8
+       test
+
+       fun test_foo is test do
+               assert true
+       end
+end
diff --git a/tests/test_nitunit9.nit b/tests/test_nitunit9.nit
new file mode 100644 (file)
index 0000000..1959919
--- /dev/null
@@ -0,0 +1,39 @@
+# 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_nitunit9 is test
+
+class TestNitunit9
+       test
+
+       fun before_class is before_all do
+               assert true
+       end
+
+       fun before is before do
+               assert true
+       end
+
+       fun test_foo is test do
+               assert true
+       end
+
+       fun after is after do
+               assert true
+       end
+
+       fun after_class is after_all do
+               assert false
+       end
+end
index a402eb2..9587665 100755 (executable)
@@ -18,8 +18,8 @@
 # This shell script compile, run and verify Nit program files
 
 # Set lang do default to avoid failed tests because of locale
-export LANG=C
-export LC_ALL=C
+export LANG=C.UTF-8
+export LC_ALL=C.UTF-8
 export NIT_TESTING=true
 # Use the pid as a collision prevention
 export NIT_TESTING_ID=$$
@@ -636,7 +636,7 @@ END
                        echo 0.0 > "$ff.time.out"
                elif [ -n "$isinteractive" ]; then
                        cat > "$ff.bin" <<END
-exec $NITC --no-color --no-prompt $OPT $includes < $(printf '%q' "$i") "\$@"
+exec $NITC --no-color --no-prompt --source-name $(printf '%q' "$i") $OPT $includes < $(printf '%q' "$i") "\$@"
 END
                        chmod +x "$ff.bin"
                        > "$ff.cmp.err"
@@ -708,7 +708,7 @@ END
                        if [ -f "$ff.write" ]; then
                                cat -- "$ff.write" >> "$ff.res"
                        elif [ -d "$ff.write" ]; then
-                               LANG=C /bin/ls -F "$ff.write" >> "$ff.res"
+                               /bin/ls -F "$ff.write" >> "$ff.res"
                        fi
                        cp -- "$ff.res"  "$ff.res2"
                        cat -- "$ff.cmp.err" "$ff.err" "$ff.res2" > "$ff.res"
@@ -750,7 +750,7 @@ END
                                        if [ -f "$fff.write" ]; then
                                                cat -- "$fff.write" >> "$fff.res"
                                        elif [ -d "$fff.write" ]; then
-                                               LANG=C /bin/ls -F -- "$fff.write" >> "$fff.res"
+                                               /bin/ls -F -- "$fff.write" >> "$fff.res"
                                        fi
                                        if [ -s "$fff.err" ]; then
                                                cp -- "$fff.res"  "$fff.res2"