Merge: Binary over network
authorJean Privat <jean@pryen.org>
Tue, 26 May 2015 10:12:35 +0000 (06:12 -0400)
committerJean Privat <jean@pryen.org>
Tue, 26 May 2015 12:38:00 +0000 (08:38 -0400)
Add services to write strings in binary streams and better support for binary in network modules.

Pull-Request: #1390
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>
Reviewed-by: Jean Privat <jean@pryen.org>

39 files changed:
contrib/nitiwiki/src/wiki_base.nit
contrib/nitiwiki/src/wiki_html.nit
contrib/nitiwiki/tests/res/wiki3_nitiwiki_render.res [new file with mode: 0644]
contrib/nitiwiki/tests/res/wiki3_nitiwiki_status.res [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/config.ini [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/pages/contact.mdwn [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/pages/index.mdwn [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/pages/other_page.mdwn [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/templates/footer.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/templates/header.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/templates/menu.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3/templates/template.html [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3_nitiwiki_render.args [new file with mode: 0644]
contrib/nitiwiki/tests/wiki3_nitiwiki_status.args [new file with mode: 0644]
contrib/online_ide/sources/nit/pnacl_nit.nit
examples/calculator/src/calculator.nit
examples/calculator/src/calculator_logic.nit
lib/a_star.nit
lib/bitmap/bitmap.nit
lib/markdown/decorators.nit
lib/more_collections.nit
lib/pnacl.nit
lib/serialization/serialization.nit
lib/socket/socket.nit
lib/socket/socket_c.nit
lib/standard/file.nit
lib/standard/stream.nit
lib/websocket/websocket.nit
share/man/nitx.md
src/doc/console_templates/console_model.nit
src/doc/doc_phases/doc_console.nit
src/interpreter/debugger.nit
src/nitx.nit
tests/nitx.args
tests/sav/nitserial_args1.res
tests/sav/nitx.res
tests/sav/nitx_args1.res
tests/sav/nitx_args2.res
tests/sav/nitx_args3.res

index 56cbbcb..b250071 100644 (file)
@@ -115,11 +115,11 @@ class Nitiwiki
        # List markdown source files from a directory.
        fun list_md_files(dir: String): Array[String] do
                var files = new Array[String]
-               var pipe = new ProcessReader("find", dir, "-name", "*.md")
+               var pipe = new ProcessReader("find", dir, "-name", "*.{config.md_ext}")
                while not pipe.eof do
                        var file = pipe.read_line
                        if file == "" then break # last line
-                       var name = file.basename(".md")
+                       var name = file.basename(".{config.md_ext}")
                        if name == "header" or name == "footer" or name == "menu" then continue
                        files.add file
                end
@@ -188,7 +188,10 @@ class Nitiwiki
        #
        # REQUIRE: `has_template`
        fun load_template(name: String): TemplateString do
-               assert has_template(name)
+               if not has_template(name) then
+                       message("Error: can't load template `{name}`", 0)
+                       exit 1
+               end
                var file = expand_path(config.root_dir, config.templates_dir, name)
                var tpl = new TemplateString.from_file(file)
                if tpl.has_macro("ROOT_URL") then
@@ -520,7 +523,7 @@ class WikiArticle
        # Create a new article using a markdown source file.
        init from_source(wiki: Nitiwiki, md_file: String) do
                src_full_path = md_file
-               init(wiki, md_file.basename(".md"))
+               init(wiki, md_file.basename(".{wiki.config.md_ext}"))
                content = md
        end
 
@@ -599,6 +602,14 @@ class WikiConfig
        # * default: `http://localhost/`
        var root_url: String is lazy do return value_or_default("wiki.root_url", "http://localhost/")
 
+       # Markdown extension recognized by this wiki.
+       #
+       # We allow only one kind of extension per wiki.
+       # Files with other markdown extensions will be treated as resources.
+       #
+       # * key: `wiki.md_ext`
+       # * default: `md`
+       var md_ext: String is lazy do return value_or_default("wiki.md_ext", "md")
 
        # Root directory of the wiki.
        #
index 461e9e5..46518aa 100644 (file)
@@ -16,6 +16,7 @@
 module wiki_html
 
 import wiki_links
+import markdown::decorators
 
 redef class Nitiwiki
 
@@ -45,6 +46,18 @@ redef class Nitiwiki
                sitemap.is_dirty = true
                return sitemap
        end
+
+       # Markdown processor used for inline element such as titles in TOC.
+       private var inline_processor: MarkdownProcessor is lazy do
+               var proc = new MarkdownProcessor
+               proc.emitter.decorator = new InlineDecorator
+               return proc
+       end
+
+       # Inline markdown (remove h1, p, ... elements).
+       private fun inline_md(md: Writable): Writable do
+               return inline_processor.process(md.write_to_string)
+       end
 end
 
 redef class WikiEntry
@@ -218,8 +231,7 @@ redef class WikiArticle
                while iter.is_ok do
                        var hl = iter.item
                        # parse title as markdown
-                       var title = hl.title.md_to_html.to_s
-                       title = title.substring(3, title.length - 8)
+                       var title = wiki.inline_md(hl.title)
                        tpl.add "<li><a href=\"#{hl.id}\">{title}</a>"
                        iter.next
                        if iter.is_ok then
diff --git a/contrib/nitiwiki/tests/res/wiki3_nitiwiki_render.res b/contrib/nitiwiki/tests/res/wiki3_nitiwiki_render.res
new file mode 100644 (file)
index 0000000..adf25a7
--- /dev/null
@@ -0,0 +1 @@
+Render section out
diff --git a/contrib/nitiwiki/tests/res/wiki3_nitiwiki_status.res b/contrib/nitiwiki/tests/res/wiki3_nitiwiki_status.res
new file mode 100644 (file)
index 0000000..2451916
--- /dev/null
@@ -0,0 +1,12 @@
+nitiWiki
+name: wiki3
+config: wiki3/config.ini
+url: http://localhost/
+
+There is modified files:
+ + pages
+ + /pages/contact.mdwn
+ + /pages/index.mdwn
+ + /pages/other_page.mdwn
+
+Use nitiwiki --render to render modified files
diff --git a/contrib/nitiwiki/tests/wiki3/config.ini b/contrib/nitiwiki/tests/wiki3/config.ini
new file mode 100644 (file)
index 0000000..111f22b
--- /dev/null
@@ -0,0 +1,3 @@
+wiki.name=wiki3
+wiki.root_dir=wiki3
+wiki.md_ext=mdwn
diff --git a/contrib/nitiwiki/tests/wiki3/pages/contact.mdwn b/contrib/nitiwiki/tests/wiki3/pages/contact.mdwn
new file mode 100644 (file)
index 0000000..09f7129
--- /dev/null
@@ -0,0 +1 @@
+# Contact
diff --git a/contrib/nitiwiki/tests/wiki3/pages/index.mdwn b/contrib/nitiwiki/tests/wiki3/pages/index.mdwn
new file mode 100644 (file)
index 0000000..8b013d6
--- /dev/null
@@ -0,0 +1 @@
+# Index
diff --git a/contrib/nitiwiki/tests/wiki3/pages/other_page.mdwn b/contrib/nitiwiki/tests/wiki3/pages/other_page.mdwn
new file mode 100644 (file)
index 0000000..a1ca7d9
--- /dev/null
@@ -0,0 +1 @@
+# Other Page
diff --git a/contrib/nitiwiki/tests/wiki3/templates/footer.html b/contrib/nitiwiki/tests/wiki3/templates/footer.html
new file mode 100644 (file)
index 0000000..7506b39
--- /dev/null
@@ -0,0 +1,10 @@
+<div class="row footer">
+       <div class="container-fluid">
+               <div class="well well-sm">
+                       <p><strong>%TITLE% &copy; %YEAR%</strong></p>
+                       <p class="text-muted"><em>last modification %GEN_TIME%</em></p>
+                       <p class="text-muted">Proudly powered by
+                               <a href="http://nitlanguage.org">nit</a>!</p>
+               </div>
+       </div>
+</div>
diff --git a/contrib/nitiwiki/tests/wiki3/templates/header.html b/contrib/nitiwiki/tests/wiki3/templates/header.html
new file mode 100644 (file)
index 0000000..1cb06bf
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="container-fluid header">
+       <div class="container">
+               <div class="header">
+                       <a href="http://uqam.ca"><img src="%ROOT_URL%/%LOGO%" alt="logo" /></a>
+                       <h2>%SUBTITLE%</h2>
+                       <h1>%TITLE%</h1>
+               </div>
+       </div>
+</div>
diff --git a/contrib/nitiwiki/tests/wiki3/templates/menu.html b/contrib/nitiwiki/tests/wiki3/templates/menu.html
new file mode 100644 (file)
index 0000000..1ff832e
--- /dev/null
@@ -0,0 +1,20 @@
+<nav class="menu" role="navigation">
+       <div class="container">
+               <!-- Brand and toggle get grouped for better mobile display -->
+               <div class="navbar-header">
+                       <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
+                               <span class="sr-only">Toggle navigation</span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                               <span class="icon-bar"></span>
+                       </button>
+                       <a class="navbar-brand" href="%ROOT_URL%index.html">%TITLE%</a>
+               </div>
+               <!-- Collect the nav links, forms, and other content for toggling -->
+               <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
+                       <ul class="nav navbar-nav">
+                       %MENUS%
+                       </ul>
+               </div><!-- /.navbar-collapse -->
+       </div>
+</nav>
diff --git a/contrib/nitiwiki/tests/wiki3/templates/template.html b/contrib/nitiwiki/tests/wiki3/templates/template.html
new file mode 100644 (file)
index 0000000..6b3b126
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html lang="en">
+       <head>
+               <meta charset="utf-8">
+               <meta http-equiv="X-UA-Compatible" content="IE=edge">
+               <meta name="viewport" content="width=device-width, initial-scale=1">
+               <title>%TITLE%</title>
+
+               <link href="%ROOT_URL%/assets/vendors/bootstrap/bootstrap-3.2.0-dist/css/bootstrap.min.css" rel="stylesheet">
+               <link href="%ROOT_URL%/assets/css/main.css" rel="stylesheet">
+
+               <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
+               <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
+               <!--[if lt IE 9]>
+               <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
+               <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
+               <![endif]-->
+       </head>
+       <body>
+               %HEADER%
+               %TOP_MENU%
+               <div class="container">
+                       <div class="row">
+                               %BODY%
+                       </div>
+                       %FOOTER%
+               </div>
+
+               <script src="%ROOT_URL%/vendors/jquery/jquery-1.11.1.min.js"></script>
+               <script src="%ROOT_URL%/vendors/bootstrap/bootstrap-3.2.0-dist/js/bootstrap.min.js"></script>
+       </body>
+</html>
diff --git a/contrib/nitiwiki/tests/wiki3_nitiwiki_render.args b/contrib/nitiwiki/tests/wiki3_nitiwiki_render.args
new file mode 100644 (file)
index 0000000..75ceb1c
--- /dev/null
@@ -0,0 +1 @@
+../bin/nitiwiki --config wiki3/config.ini --clean --render -v
diff --git a/contrib/nitiwiki/tests/wiki3_nitiwiki_status.args b/contrib/nitiwiki/tests/wiki3_nitiwiki_status.args
new file mode 100644 (file)
index 0000000..c148644
--- /dev/null
@@ -0,0 +1 @@
+../bin/nitiwiki --config wiki3/config.ini --clean --status
index 74c7be0..2017a0b 100644 (file)
@@ -72,7 +72,7 @@ redef class FileReader
                self.path = path
                var file = sys.files[path]
                prepare_buffer(file.length)
-               _buffer.append(file)
+               path.copy_to_native(_buffer, file.length, 0, 0)
        end
 
        redef fun close
@@ -82,7 +82,7 @@ redef class FileReader
 
        redef fun fill_buffer
        do
-               _buffer.clear
+               buffer_reset
                end_reached = true
        end
 
index 2e2f437..6ed8be7 100644 (file)
@@ -96,7 +96,7 @@ class CalculatorWindow
 
        redef fun on_save_state
        do
-               app.data_store["context"] = context.to_json
+               app.data_store["context"] = context
                super
        end
 
@@ -104,11 +104,10 @@ class CalculatorWindow
        do
                super
 
-               var save = app.data_store["context"]
-               if save == null then return
-               assert save isa String
+               var context = app.data_store["context"]
+               if not context isa CalculatorContext then return
 
-               self.context = new CalculatorContext.from_json(save)
+               self.context = context
                display.text = context.display_text
        end
 end
index be55201..ea45a99 100644 (file)
 # Business logic of a calculator
 module calculator_logic
 
-import json::dynamic
+import serialization
 
 # Hold the state of the calculator and its services
 class CalculatorContext
+       auto_serializable
+
        # Result of the last operation
        var result: nullable Numeric = null
 
@@ -120,52 +122,6 @@ class CalculatorContext
                self.result = result
                self.current = null
        end
-
-       # Serialize calculator state to Json
-       fun to_json: String
-       do
-               # Do not save NaN nor inf
-               var result = self.result
-               if result != null and (result.to_f.is_nan or result.to_f.is_inf != 0) then result = null
-
-               var self_last_op = self.last_op
-               var last_op
-               if self_last_op == null then
-                       last_op = "null"
-               else last_op = "\"{self_last_op}\""
-
-               var self_current = self.current
-               var current
-               if self_current == null then
-                       current = "null"
-               else current = "\"{self_current}\""
-
-               return """
-{
-       "result": {{{result or else "null"}}},
-       "last_op": {{{last_op}}},
-       "current": {{{current}}}
-}"""
-       end
-
-       # Load calculator state from Json
-       init from_json(json_string: String)
-       do
-               var json = json_string.to_json_value
-               if json.is_error then
-                       print "Loading state failed: {json.to_error}"
-                       return
-               end
-
-               var result = json["result"]
-               if result.is_numeric then self.result = result.to_numeric
-
-               var last_op = json["last_op"]
-               if last_op.is_string then self.last_op = last_op.to_s.chars.first
-
-               var current = json["current"]
-               if current.is_string then self.current = new FlatBuffer.from(current.to_s)
-       end
 end
 
 redef universal Float
index 9735d90..a0b1b48 100644 (file)
 # ~~~
 module a_star
 
+import serialization
+
 # General graph node
 class Node
+       super Serializable
+
        # Type of the others nodes in the `graph`
        type N: Node
 
@@ -183,10 +187,32 @@ class Node
                        end
                end
        end
+
+       # We customize the serialization process to avoid problems with recursive
+       # serialization engines. These engines, such as `JsonSerializer`,
+       # are at danger to serialize the graph as a very deep tree.
+       # With a large graph it can cause a stack overflow.
+       #
+       # Instead, we serialize the nodes first and then the links.
+       redef fun core_serialize_to(serializer: Serializer)
+       do
+               serializer.serialize_attribute("graph", graph)
+       end
+
+       redef init from_deserializer(deserializer)
+       do
+               deserializer.notify_of_creation self
+
+               var graph = deserializer.deserialize_attribute("graph")
+               assert graph isa Graph[N, Link]
+               self.graph = graph
+       end
 end
 
 # Link between two nodes and associated to a graph
 class Link
+       auto_serializable
+
        # Type of the nodes in `graph`
        type N: Node
 
@@ -210,6 +236,8 @@ end
 
 # General graph
 class Graph[N: Node, L: Link]
+       super Serializable
+
        # Nodes in this graph
        var nodes: Set[N] = new HashSet[N]
 
@@ -236,15 +264,35 @@ class Graph[N: Node, L: Link]
 
        # Used to check if nodes have been searched in one pathfinding
        private var pathfinding_current_evocation: Int = 0
+
+       redef fun core_serialize_to(serializer: Serializer)
+       do
+               serializer.serialize_attribute("nodes", nodes)
+               serializer.serialize_attribute("links", links)
+       end
+
+       redef init from_deserializer(deserializer)
+       do
+               deserializer.notify_of_creation self
+
+               var nodes = deserializer.deserialize_attribute("nodes")
+               assert nodes isa HashSet[N]
+               self.nodes = nodes
+
+               var links = deserializer.deserialize_attribute("links")
+               assert links isa HashSet[L]
+               for link in links do add_link link
+       end
 end
 
 # Result from path finding and a walkable path
 class AStarPath[N]
+       auto_serializable
 
-       # The total cost of this path
+       # Total cost of this path
        var total_cost: Int
 
-       # The list of nodes composing this path
+       # Nodes composing this path
        var nodes = new List[N]
 
        private var at: Int = 0
@@ -269,6 +317,8 @@ end
 
 # Context related to an evocation of pathfinding
 class PathContext
+       auto_serializable
+
        # Type of the nodes in `graph`
        type N: Node
 
@@ -302,6 +352,7 @@ end
 # Warning: A* is not optimize for such a case
 class ConstantPathContext
        super PathContext
+       auto_serializable
 
        redef fun worst_cost do return 1
        redef fun cost(l) do return 1
@@ -313,6 +364,7 @@ end
 # A `PathContext` for graphs with `WeightedLink`
 class WeightedPathContext
        super PathContext
+       auto_serializable
 
        redef type L: WeightedLink
 
@@ -341,6 +393,7 @@ end
 # A `Link` with a `weight`
 class WeightedLink
        super Link
+       auto_serializable
 
        # The `weight`, or cost, of this link
        var weight: Int
@@ -348,6 +401,8 @@ end
 
 # Advanced path conditions with customizable accept states
 class TargetCondition[N: Node]
+       auto_serializable
+
        # Should the pathfinding accept `node` as a goal?
        fun accept(node: N): Bool is abstract
 
index 0343b8e..9be1bc2 100644 (file)
@@ -123,14 +123,20 @@ class Bitmap
 
                # =============== Bitmap header ================
                for x in [0..13] do
-                       bitmap_header[x] = fileReader.read(1)[0].ascii
+                       var b = fileReader.read_byte
+                       if b == null then
+                               return
+                       end
+                       bitmap_header[x] = b
                end
                self.file_size = get_value(bitmap_header.subarray(2, 4))
                self.data_offset = get_value(bitmap_header.subarray(10, 4))
 
                # =============== DIB header ================
                for x in [0..39] do
-                       dib_header[x] = fileReader.read(1)[0].ascii
+                       var b = fileReader.read_byte
+                       if b == null then return
+                       dib_header[x] = b
                end
                var dib_size = get_value(dib_header.subarray(0, 4))
                # only support BITMAPINFOHEADER
@@ -159,9 +165,11 @@ class Bitmap
                                var row = new Array[Int].with_capacity(self.width)
                                for y in [0..self.width[
                                do
-                                       var red = fileReader.read(1)[0].ascii * 256 * 256
-                                       var green = fileReader.read(1)[0].ascii * 256
-                                       var blue = fileReader.read(1)[0].ascii
+                                       var bts = fileReader.read_bytes(3)
+                                       if bts.length != 3 then return
+                                       var red = bts[0] << 16
+                                       var green = bts[1] << 8
+                                       var blue = bts[2]
                                        row.add(red + green + blue)
                                end
                                self.data.add(row)
@@ -170,18 +178,6 @@ class Bitmap
                fileReader.close
        end #end of load_from_file method
 
-       # Reads in a series of bytes from the FileReader when loading a Bitmap from a file
-       # FileReader.read(1) is used due to https://github.com/privat/nit/issues/1264
-       private fun read_chars(fr: FileReader, howMany: Int): String
-       do
-               var s = ""
-               for x in [1..howMany]
-               do
-                       s += fr.read(1)
-               end
-               return s
-       end
-
        # Converts the value contained in two or four bytes into an Int. Since Nit
        # does not have a byte type, Int is used
        private fun get_value(array: Array[Int]): Int
index 1a83394..f33302f 100644 (file)
@@ -1,4 +1,5 @@
 # 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
@@ -184,3 +185,19 @@ class MdDecorator
 
        private var allowed_id_chars: Array[Char] = ['-', '_', ':', '.']
 end
+
+# Decorator for span elements.
+#
+# InlineDecorator does not decorate things like paragraphs or headers.
+class InlineDecorator
+       super HTMLDecorator
+
+       redef fun add_paragraph(v, block) do v.emit_in block
+       redef fun add_headline(v, block) do v.emit_in block
+
+       redef fun add_code(v, block) do
+               v.add "<code>"
+               v.emit_in block
+               v.add "</code>"
+       end
+end
index f9b55af..4cafb1d 100644 (file)
@@ -15,6 +15,8 @@
 # Highly specific, but useful, collections-related classes.
 module more_collections
 
+import serialization
+
 # Simple way to store an `HashMap[K, Array[V]]`
 #
 # Unlike standard HashMap, MultiHashMap provides a new
@@ -30,6 +32,7 @@ module more_collections
 #     assert m["four"] == ['i', 'i', 'i', 'i']
 #     assert m["zzz"] == new Array[Char]
 class MultiHashMap[K, V]
+       auto_serializable
        super HashMap[K, Array[V]]
 
        # Add `v` to the array associated with `k`.
@@ -61,6 +64,8 @@ end
 # assert hm2[2, "not-two"] == null
 # ~~~~
 class HashMap2[K1, K2, V]
+       auto_serializable
+
        private var level1 = new HashMap[K1, HashMap[K2, V]]
 
        # Return the value associated to the keys `k1` and `k2`.
@@ -108,6 +113,8 @@ end
 # assert hm3[2, "not-two", 22] == null
 # ~~~~
 class HashMap3[K1, K2, K3, V]
+       auto_serializable
+
        private var level1 = new HashMap[K1, HashMap2[K2, K3, V]]
 
        # Return the value associated to the keys `k1`, `k2`, and `k3`.
@@ -186,6 +193,7 @@ end
 # assert dma.default == [65]
 # ~~~~
 class DefaultMap[K, V]
+       auto_serializable
        super HashMap[K, V]
 
        # The default value.
index 516c860..248a4d4 100644 (file)
@@ -653,9 +653,11 @@ class PnaclStream
        # fill_buffer now checks for a message in the message queue which is filled by user inputs.
        redef fun fill_buffer
        do
-               _buffer.clear
                _buffer_pos = 0
-               _buffer.append check_message.to_s
+               var nns = check_message
+               var nslen = nns.cstring_length
+               _buffer_length = nslen
+               nns.copy_to(buffer, nslen, 0, 0)
        end
 end
 
index 6e5ce12..3ba9968 100644 (file)
@@ -164,3 +164,37 @@ redef class NativeString super DirectSerializable end
 redef class String super DirectSerializable end
 redef class SimpleCollection[E] super Serializable end
 redef class Map[K, V] super Serializable end
+
+redef class Couple[F, S]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var first = v.deserialize_attribute("first")
+               var second = v.deserialize_attribute("second")
+               init(first, second)
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("first", first)
+               v.serialize_attribute("second", second)
+       end
+end
+
+redef class Container[E]
+       super Serializable
+
+       redef init from_deserializer(v)
+       do
+               v.notify_of_creation self
+               var item = v.deserialize_attribute("item")
+               init item
+       end
+
+       redef fun core_serialize_to(v)
+       do
+               v.serialize_attribute("item", first)
+       end
+end
index b3cb2f9..d553540 100644 (file)
@@ -57,7 +57,7 @@ class TCPStream
        # Creates a socket connection to host `host` on port `port`
        init connect(host: String, port: Int)
        do
-               _buffer = new FlatBuffer
+               _buffer = new NativeString(1024)
                _buffer_pos = 0
                socket = new NativeSocket.socket(new NativeSocketAddressFamilies.af_inet,
                        new NativeSocketTypes.sock_stream, new NativeSocketProtocolFamilies.pf_null)
@@ -84,7 +84,7 @@ class TCPStream
        # Creates a client socket, this is meant to be used by accept only
        private init server_side(h: SocketAcceptResult)
        do
-               _buffer = new FlatBuffer
+               _buffer = new NativeString(1024)
                _buffer_pos = 0
                socket = h.socket
                addrin = h.addr_in
@@ -110,7 +110,7 @@ class TCPStream
        # timeout : Time in milliseconds before stopping to wait for events
        fun ready_to_read(timeout: Int): Bool
        do
-               if _buffer_pos < _buffer.length then return true
+               if _buffer_pos < _buffer_length then return true
                if end_reached then return false
                var events = [new NativeSocketPollValues.pollin]
                return pollin(events, timeout).length != 0
@@ -153,6 +153,11 @@ class TCPStream
                socket.write_byte value
        end
 
+       redef fun write_bytes(s) do
+               if closed then return
+               socket.write(s.to_s)
+       end
+
        fun write_ln(msg: Text)
        do
                if end_reached then return
@@ -162,7 +167,7 @@ class TCPStream
 
        redef fun fill_buffer
        do
-               _buffer.clear
+               _buffer_length = 0
                _buffer_pos = 0
                if not connected then return
                var read = socket.read
@@ -170,7 +175,17 @@ class TCPStream
                        close
                        end_reached = true
                end
-               _buffer.append(read)
+               enlarge(_buffer_capacity + read.length)
+               read.copy_to_native(_buffer, read.length, 0, 0)
+               _buffer_length = read.length
+       end
+
+       fun enlarge(len: Int) do
+               if _buffer_capacity >= len then return
+               while _buffer_capacity < len do _buffer_capacity *= 2
+               var ns = new NativeString(_buffer_capacity)
+               _buffer.copy_to(ns, _buffer_length - _buffer_pos, _buffer_pos, 0)
+               _buffer = ns
        end
 
        redef fun close
index 518fcb5..fbf2923 100644 (file)
@@ -138,16 +138,14 @@ extern class NativeSocket `{ int* `}
                return write(*recv, &byt, 1);
        `}
 
-       fun read: String import NativeString.to_s_with_length `{
+       fun read: String import NativeString.to_s_with_length, NativeString.to_s_with_copy `{
                static char c[1024];
-               int n = read(*recv, c, 1024);
+               int n = read(*recv, c, 1023);
                if(n < 0) {
                        return NativeString_to_s_with_length("",0);
                }
-               char* ret = malloc(n + 1);
-               memcpy(ret, c, n);
-               ret[n] = '\0';
-               return NativeString_to_s_with_length(ret, n);
+               c[n] = 0;
+               return NativeString_to_s_with_copy(c);
        `}
 
        # Sets an option for the socket
index 1490ee7..3548a25 100644 (file)
@@ -113,25 +113,24 @@ class FileReader
                        return
                end
                end_reached = false
-               _buffer_pos = 0
-               _buffer.clear
+               buffer_reset
        end
 
        redef fun close
        do
                super
-               _buffer.clear
+               buffer_reset
                end_reached = true
        end
 
        redef fun fill_buffer
        do
-               var nb = _file.io_read(_buffer.items, _buffer.capacity)
+               var nb = _file.io_read(_buffer, _buffer_capacity)
                if nb <= 0 then
                        end_reached = true
                        nb = 0
                end
-               _buffer.length = nb
+               _buffer_length = nb
                _buffer_pos = 0
        end
 
@@ -179,18 +178,23 @@ class FileWriter
        super FileStream
        super Writer
 
+       redef fun write_bytes(s) do
+               if last_error != null then return
+               if not _is_writable then
+                       last_error = new IOError("cannot write to non-writable stream")
+                       return
+               end
+               write_native(s.items, s.length)
+       end
+
        redef fun write(s)
        do
                if last_error != null then return
                if not _is_writable then
-                       last_error = new IOError("Cannot write to non-writable stream")
+                       last_error = new IOError("cannot write to non-writable stream")
                        return
                end
-               if s isa FlatText then
-                       write_native(s.to_cstring, s.length)
-               else
-                       for i in s.substrings do write_native(i.to_cstring, i.length)
-               end
+               for i in s.substrings do write_native(i.to_cstring, i.length)
        end
 
        redef fun write_byte(value)
@@ -435,10 +439,12 @@ class Path
        # ~~~
        #
        # See `Reader::read_all` for details.
-       fun read_all: String
+       fun read_all: String do return read_all_bytes.to_s
+
+       fun read_all_bytes: Bytes
        do
                var s = open_ro
-               var res = s.read_all
+               var res = s.read_all_bytes
                s.close
                return res
        end
index 7c4e606..b901596 100644 (file)
@@ -13,6 +13,7 @@ module stream
 
 intrude import ropes
 import error
+intrude import bytes
 
 in "C" `{
        #include <unistd.h>
@@ -48,19 +49,23 @@ abstract class Reader
        # Reads a byte. Returns `null` on EOF or timeout
        fun read_byte: nullable Int is abstract
 
+       # Reads a String of at most `i` length
+       fun read(i: Int): String do return read_bytes(i).to_s
+
        # Read at most i bytes
-       fun read(i: Int): String
+       fun read_bytes(i: Int): Bytes
        do
-               if last_error != null then return ""
-               var s = new FlatBuffer.with_capacity(i)
+               if last_error != null then return new Bytes.empty
+               var s = new NativeString(i)
+               var buf = new Bytes(s, 0, 0)
                while i > 0 and not eof do
-                       var c = read_char
+                       var c = read_byte
                        if c != null then
-                               s.add(c)
+                               buf.add c
                                i -= 1
                        end
                end
-               return s.to_s
+               return buf
        end
 
        # Read a string until the end of the line.
@@ -154,22 +159,27 @@ abstract class Reader
 
        # Read all the stream until the eof.
        #
-       # The content of the file is returned verbatim.
+       # The content of the file is returned as a String.
        #
        # ~~~
        # var txt = "Hello\n\nWorld\n"
        # var i = new StringReader(txt)
        # assert i.read_all == txt
        # ~~~
-       fun read_all: String
+       fun read_all: String do return read_all_bytes.to_s
+
+       # Read all the stream until the eof.
+       #
+       # The content of the file is returned verbatim.
+       fun read_all_bytes: Bytes
        do
-               if last_error != null then return ""
-               var s = new FlatBuffer
+               if last_error != null then return new Bytes.empty
+               var s = new Bytes.empty
                while not eof do
-                       var c = read_char
+                       var c = read_byte
                        if c != null then s.add(c)
                end
-               return s.to_s
+               return s
        end
 
        # Read a string until the end of the line and append it to `s`.
@@ -342,6 +352,10 @@ end
 # A `Stream` that can be written to
 abstract class Writer
        super Stream
+
+       # Writes bytes from `s`
+       fun write_bytes(s: Bytes) is abstract
+
        # write a string
        fun write(s: Text) is abstract
 
@@ -365,7 +379,7 @@ interface Writable
 
        # Like `write_to` but return a new String (may be quite large)
        #
-       # This funtionnality is anectodical, since the point
+       # This funtionality is anectodical, since the point
        # of streamable object to to be efficienlty written to a
        # stream without having to allocate and concatenate strings
        fun write_to_string: String
@@ -408,66 +422,87 @@ abstract class BufferedReader
                return c
        end
 
-       # Peeks up to `n` bytes in the buffer, returns an empty string on EOF
+       fun buffer_reset do
+               _buffer_length = 0
+               _buffer_pos = 0
+       end
+
+       # Peeks up to `n` bytes in the buffer
        #
        # The operation does not consume the buffer
        #
        # ~~~nitish
-       #       var x = new FileReader("File.txt")
-       #       assert x.peek(5) == x.read(5)
+       # var x = new FileReader.open("File.txt")
+       # assert x.peek(5) == x.read(5)
        # ~~~
-       fun peek(i: Int): String do
-               if eof then return ""
-               var b = new FlatBuffer.with_capacity(i)
-               while i > 0 and not eof do
-                       b.add _buffer[_buffer_pos]
-                       _buffer_pos += 1
-                       i -= 1
+       fun peek(i: Int): Bytes do
+               if eof then return new Bytes.empty
+               var remsp = _buffer_length - _buffer_pos
+               if i <= remsp then
+                       var bf = new Bytes.with_capacity(i)
+                       bf.append_ns_from(_buffer, i, _buffer_pos)
+                       return bf
                end
-               var nbuflen = b.length + (_buffer.length - _buffer_pos)
-               var nbuf = new FlatBuffer.with_capacity(nbuflen)
-               nbuf.append(b)
-               while _buffer_pos < _buffer.length do
-                       nbuf.add(_buffer[_buffer_pos])
-                       _buffer_pos += 1
+               var bf = new Bytes.with_capacity(i)
+               bf.append_ns_from(_buffer, remsp, _buffer_pos)
+               _buffer_pos = _buffer_length
+               read_intern(i - bf.length, bf)
+               remsp = _buffer_length - _buffer_pos
+               var full_len = bf.length + remsp
+               if full_len > _buffer_capacity then
+                       var c = _buffer_capacity
+                       while c < full_len do c = c * 2 + 2
+                       _buffer_capacity = c
                end
+               var nns = new NativeString(_buffer_capacity)
+               bf.items.copy_to(nns, bf.length, 0, 0)
+               _buffer.copy_to(nns, remsp, _buffer_pos, bf.length)
+               _buffer = nns
                _buffer_pos = 0
-               _buffer = nbuf
-               return b.to_s
+               _buffer_length = full_len
+               return bf
        end
 
-       redef fun read(i)
+       redef fun read_bytes(i)
        do
-               if last_error != null then return ""
-               if eof then return ""
+               if last_error != null then return new Bytes.empty
+               var buf = new Bytes.with_capacity(i)
+               read_intern(i, buf)
+               return buf
+       end
+
+       # Fills `buf` with at most `i` bytes read from `self`
+       private fun read_intern(i: Int, buf: Bytes): Int do
+               if eof then return 0
                var p = _buffer_pos
-               var bufsp = _buffer.length - p
+               var bufsp = _buffer_length - p
                if bufsp >= i then
                        _buffer_pos += i
-                       return _buffer.substring(p, i).to_s
+                       buf.append_ns_from(_buffer, i, p)
+                       return i
                end
-               _buffer_pos = _buffer.length
-               var readln = _buffer.length - p
-               var s = _buffer.substring(p, readln).to_s
-               fill_buffer
-               return s + read(i - readln)
+               _buffer_pos = _buffer_length
+               var readln = _buffer_length - p
+               buf.append_ns_from(_buffer, readln, p)
+               var rd = read_intern(i - readln, buf)
+               return rd + readln
        end
 
-       redef fun read_all
+       redef fun read_all_bytes
        do
-               if last_error != null then return ""
-               var s = new FlatBuffer
+               if last_error != null then return new Bytes.empty
+               var s = new Bytes.with_capacity(10)
                while not eof do
                        var j = _buffer_pos
-                       var k = _buffer.length
+                       var k = _buffer_length
                        while j < k do
-                               s.add(_buffer[j])
+                               s.add(_buffer[j].ascii)
                                j += 1
                        end
                        _buffer_pos = j
                        fill_buffer
                end
-               return s.to_s
+               return s
        end
 
        redef fun append_line_to(s)
@@ -475,10 +510,12 @@ abstract class BufferedReader
                loop
                        # First phase: look for a '\n'
                        var i = _buffer_pos
-                       while i < _buffer.length and _buffer[i] != '\n' do i += 1
+                       while i < _buffer_length and _buffer[i] != '\n' do
+                               i += 1
+                       end
 
                        var eol
-                       if i < _buffer.length then
+                       if i < _buffer_length then
                                assert _buffer[i] == '\n'
                                i += 1
                                eol = true
@@ -516,29 +553,37 @@ abstract class BufferedReader
 
        redef fun eof
        do
-               if _buffer_pos < _buffer.length then return false
+               if _buffer_pos < _buffer_length then return false
                if end_reached then return true
                fill_buffer
-               return _buffer_pos >= _buffer.length and end_reached
+               return _buffer_pos >= _buffer_length and end_reached
        end
 
        # The buffer
-       private var buffer: nullable FlatBuffer = null
+       private var buffer: NativeString = new NativeString(0)
 
        # The current position in the buffer
-       private var buffer_pos: Int = 0
+       private var buffer_pos = 0
+
+       # Length of the current buffer (i.e. nuber of bytes in the buffer)
+       private var buffer_length = 0
+
+       # Capacity of the buffer
+       private var buffer_capacity = 0
 
        # Fill the buffer
        protected fun fill_buffer is abstract
 
-       # Is the last fill_buffer reach the end
+       # Has the last fill_buffer reached the end
        protected fun end_reached: Bool is abstract
 
        # Allocate a `_buffer` for a given `capacity`.
        protected fun prepare_buffer(capacity: Int)
        do
-               _buffer = new FlatBuffer.with_capacity(capacity)
+               _buffer = new NativeString(capacity)
                _buffer_pos = 0 # need to read
+               _buffer_length = 0
+               _buffer_capacity = capacity
        end
 end
 
@@ -557,6 +602,11 @@ class StringWriter
        private var content = new Array[String]
        redef fun to_s do return content.to_s
        redef fun is_writable do return not closed
+
+       redef fun write_bytes(b) do
+               content.add(b.to_s)
+       end
+
        redef fun write(str)
        do
                assert not closed
@@ -607,11 +657,11 @@ class StringReader
                source = ""
        end
 
-       redef fun read_all do
-               var c = cursor
-               cursor = source.length
-               if c == 0 then return source
-               return source.substring_from(c)
+       redef fun read_all_bytes do
+               var nslen = source.length - cursor
+               var nns = new NativeString(nslen)
+               source.copy_to_native(nns, nslen, cursor, 0)
+               return new Bytes(nns, nslen, nslen)
        end
 
        redef fun eof do return cursor >= source.length
index a22c5b2..6eac618 100644 (file)
@@ -23,6 +23,7 @@ import sha1
 import base64
 
 intrude import standard::stream
+intrude import standard::bytes
 
 # Websocket compatible listener
 #
@@ -65,8 +66,10 @@ class WebsocketConnection
        super TCPStream
 
        init do
-               _buffer = new FlatBuffer
+               _buffer = new NativeString(1024)
                _buffer_pos = 0
+               _buffer_capacity = 1024
+               _buffer_length = 0
                var headers = parse_handshake
                var resp = handshake_response(headers)
 
@@ -119,22 +122,28 @@ class WebsocketConnection
        end
 
        # Frames a text message to be sent to a client
-       private fun frame_message(msg: String): String
+       private fun frame_message(msg: String): Bytes
        do
-               var ans_buffer = new FlatBuffer
+               var ans_buffer = new Bytes.with_capacity(msg.length)
                # Flag for final frame set to 1
                # opcode set to 1 (for text)
-               ans_buffer.add(129.ascii)
+               ans_buffer.add(129)
                if msg.length < 126 then
-                       ans_buffer.add(msg.length.ascii)
+                       ans_buffer.add(msg.length)
                end
                if msg.length >= 126 and msg.length <= 65535 then
-                       ans_buffer.add(126.ascii)
-                       ans_buffer.add(msg.length.rshift(8).ascii)
-                       ans_buffer.add(msg.length.ascii)
+                       ans_buffer.add(126)
+                       ans_buffer.add(msg.length.rshift(8))
+                       ans_buffer.add(msg.length)
                end
-               ans_buffer.append(msg)
-               return ans_buffer.to_s
+               if msg isa FlatString then
+                       ans_buffer.append_ns_from(msg.items, msg.length, msg.index_from)
+               else
+                       for i in msg.substrings do
+                               ans_buffer.append_ns_from(i.as(FlatString).items, i.length, i.as(FlatString).index_from)
+                       end
+               end
+               return ans_buffer
        end
 
        # Reads an HTTP frame
@@ -149,6 +158,7 @@ class WebsocketConnection
        # Gets the message from the client, unpads it and reconstitutes the message
        private fun unpad_message do
                var fin = false
+               var bf = new Bytes.empty
                while not fin do
                        var fst_byte = client.read_byte
                        var snd_byte = client.read_byte
@@ -174,10 +184,10 @@ class WebsocketConnection
                        if fin_flag != 0 then fin = true
                        var opcode = fst_byte.bin_and(15)
                        if opcode == 9 then
-                               _buffer.add(138.ascii)
-                               _buffer.add('\0')
-                               client.write(_buffer.to_s)
-                               _buffer_pos += 2
+                               bf.add(138)
+                               bf.add(0)
+                               client.write(bf.to_s)
+                               _buffer_pos = _buffer_length
                                return
                        end
                        if opcode == 8 then
@@ -192,76 +202,68 @@ class WebsocketConnection
                        var len = snd_byte.bin_and(127)
                        var payload_ext_len = 0
                        if len == 126 then
-                               var tmp = client.read(2)
+                               var tmp = client.read_bytes(2)
                                if tmp.length != 2 then
                                        last_error = new IOError("Error: received interrupted frame")
                                        client.close
                                        return
                                end
-                               payload_ext_len = tmp[1].ascii + tmp[0].ascii.lshift(8)
+                               payload_ext_len = tmp[1] + tmp[0].lshift(8)
                        else if len == 127 then
                                # 64 bits for length are not supported,
                                # only the last 32 will be interpreted as a Nit Integer
-                               var tmp = client.read(8)
+                               var tmp = client.read_bytes(8)
                                if tmp.length != 8 then
                                        last_error = new IOError("Error: received interrupted frame")
                                        client.close
                                        return
                                end
                                for pos in [0 .. tmp.length[ do
-                                       var i = tmp[pos].ascii
+                                       var i = tmp[pos]
                                        payload_ext_len += i.lshift(8 * (7 - pos))
                                end
                        end
                        if mask_flag != 0 then
+                               var mask = client.read_bytes(4).items
                                if payload_ext_len != 0 then
-                                       var msg = client.read(payload_ext_len+4)
-                                       var mask = msg.substring(0,4)
-                                       _buffer.append(unmask_message(mask, msg.substring(4, payload_ext_len)))
-                               else
-                                       if len == 0 then
-                                               return
-                                       end
-                                       var msg = client.read(len+4)
-                                       var mask = msg.substring(0,4)
-                                       _buffer.append(unmask_message(mask, msg.substring(4, len)))
+                                       len = payload_ext_len
                                end
+                               var msg = client.read_bytes(len).items
+                               bf.append_ns(unmask_message(mask, msg, len), len)
                        end
                end
+               _buffer = bf.items
+               _buffer_length = bf.length
        end
 
        # Unmasks a message sent by a client
-       private fun unmask_message(key: String, message: String): String
+       private fun unmask_message(key: NativeString, message: NativeString, len: Int): NativeString
        do
-               var return_message = new FlatBuffer.with_capacity(message.length)
-               var msg_iter = message.chars.iterator
+               var return_message = new NativeString(len)
 
-               while msg_iter.is_ok do
-                       return_message.chars[msg_iter.index] = msg_iter.item.ascii.bin_xor(key.chars[msg_iter.index%4].ascii).ascii
-                       msg_iter.next
+               for i in [0 .. len[ do
+                       return_message[i] = message[i].ascii.bin_xor(key[i%4].ascii).ascii
                end
 
-               return return_message.to_s
+               return return_message
        end
 
        # Checks if a connection to a client is available
        redef fun connected do return client.connected
 
-       redef fun write(msg)
-       do
-               client.write(frame_message(msg.to_s))
-       end
+       redef fun write_bytes(s) do client.write_bytes(frame_message(s.to_s))
+
+       redef fun write(msg) do client.write(frame_message(msg.to_s).to_s)
 
        redef fun is_writable do return client.connected
 
        redef fun fill_buffer
        do
-               _buffer.clear
-               _buffer_pos = 0
+               buffer_reset
                unpad_message
        end
 
-       redef fun end_reached do return client._buffer_pos >= client._buffer.length and client.end_reached
+       redef fun end_reached do return client._buffer_pos >= client._buffer_length and client.end_reached
 
        # Is there some data available to be read ?
        fun can_read(timeout: Int): Bool do return client.ready_to_read(timeout)
index c655110..4ba5154 100644 (file)
@@ -10,9 +10,9 @@ nitx [*options*] FILE [COMMAND]
 
 # DESCRIPTION
 
-`nitx` in an interactive tool that display information about programs and libraries.
+`nitx` in an interactive tool that displays informations about programs and libraries.
 
-A command that query some information can be given as and argument.
+A command that query some information can be given as parameter.
 This will immediately displays the information then terminates the programs.
 
 If no command are given, the program starts an interactive session where commands are entered until `:q` is given.
@@ -31,6 +31,15 @@ If no command are given, the program starts an interactive session where command
 `new: Type`
 :   lookup methods creating new instances of 'Type'.
 
+`call: Property`
+:   lookup calls to 'Property'.
+
+`doc: name`
+:   lookup documentation pages about 'name'.
+
+`code: name`
+:   lookup source code related to 'name'.
+
 `:h`
 :   display an help message about the commands.
 
@@ -39,7 +48,8 @@ If no command are given, the program starts an interactive session where command
 
 # OPTIONS
 
-Only common options of the Nit tools are understood.
+`-q`
+:      execute a query, display results in console then quit.
 
 # SEE ALSO
 
index 8de793c..dee8a74 100644 (file)
@@ -121,6 +121,26 @@ redef class MEntity
        #
        # See module `console`.
        fun cs_visibility_color(string: String): String do return string.green
+
+       # Source code associated to this MEntity.
+       #
+       # Uses `cs_location` to locate the source code.
+       fun cs_source_code: String do
+               # FIXME up location to mentity
+               var loc = new Location.from_string(cs_location)
+               var fr = new FileReader.open(loc.file.filename)
+               var content = new FlatBuffer
+               var i = 0
+               while not fr.eof do
+                       i += 1
+                       var line = fr.read_line
+                       if i < loc.line_start or i > loc.line_end then continue
+                       # FIXME add nitlight for console
+                       content.append "{line}\n"
+               end
+               fr.close
+               return content.write_to_string
+       end
 end
 
 redef class MProject
index a7ef6b1..dbd1d39 100644 (file)
@@ -59,6 +59,12 @@ class Nitx
        fun help do
                print "\nCommands:"
                print "\tname\t\tlookup module, class and property with the corresponding 'name'"
+               print "\tdoc: <name::space>\tdisplay the documentation page of 'namespace'"
+               print "\tparam: <Type>\tlookup methods using the corresponding 'Type' as parameter"
+               print "\treturn: <Type>\tlookup methods returning the corresponding 'Type'"
+               print "\tnew: <Type>\tlookup methods creating new instances of 'Type'"
+               print "\tcall: <name>\tlookup methods calling 'name'"
+               print "\tcode: <name>\tdisplay the source code associated to the 'name' entity"
                print "\t:h\t\tdisplay this help message"
                print "\t:q\t\tquit interactive mode"
                print ""
@@ -110,6 +116,19 @@ interface NitxQuery
                        return new NitxHelp
                else if query_string.has_prefix("comment:") then
                        return new CommentQuery(query_string)
+               else if query_string.has_prefix("doc:") then
+                       return new DocQuery(query_string)
+               else if query_string.has_prefix("param:") then
+                       return new ParamQuery(query_string)
+               else if query_string.has_prefix("return:") then
+                       return new ReturnQuery(query_string)
+               else if query_string.has_prefix("new:") then
+                       return new NewQuery(query_string)
+               else if query_string.has_prefix("call:") then
+                       return new CallQuery(query_string)
+               else if query_string.has_prefix("code:") then
+                       return new CodeQuery(query_string)
+
                end
                return new CommentQuery("comment: {query_string}")
        end
@@ -207,6 +226,188 @@ class CommentQuery
        end
 end
 
+# A query to search signatures using a specific `MType` as parameter.
+class ParamQuery
+       super MetaQuery
+
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var mtype_name = args.first
+               for mproperty in doc.mproperties do
+                       if not mproperty isa MMethod then continue
+                       var msignature = mproperty.intro.msignature
+                       if msignature != null then
+                               for mparam in msignature.mparameters do
+                                       if mparam.mtype.name == mtype_name then
+                                               res.add new MEntityMatch(self, mproperty)
+                                       end
+                               end
+                       end
+               end
+               return res
+       end
+end
+
+# A query to search signatures using a specific `MType` as return.
+class ReturnQuery
+       super MetaQuery
+
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var mtype_name = args.first
+               for mproperty in doc.mproperties do
+                       if not mproperty isa MMethod then continue
+                       var msignature = mproperty.intro.msignature
+                       if msignature != null then
+                               var mreturn = msignature.return_mtype
+                               if mreturn != null and mreturn.name == mtype_name then
+                                       res.add new MEntityMatch(self, mproperty)
+                               end
+                       end
+               end
+               return res
+       end
+end
+
+# A query to search methods creating new instances of a specific `MType`.
+class NewQuery
+       super MetaQuery
+
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var mtype_name = args.first
+               for mpropdef in doc.mpropdefs do
+                       var visitor = new TypeInitVisitor(mtype_name)
+                       var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
+                       if npropdef == null then continue
+                       visitor.enter_visit(npropdef)
+                       for i in visitor.inits do
+                               res.add new MEntityMatch(self, mpropdef)
+                       end
+               end
+               return res
+       end
+end
+
+# A query to search methods calling a specific `MProperty`.
+class CallQuery
+       super MetaQuery
+
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var mprop_name = args.first
+               for mpropdef in doc.mpropdefs do
+                       var visitor = new MPropertyCallVisitor
+                       var npropdef = nitx.ctx.modelbuilder.mpropdef2node(mpropdef)
+                       if npropdef == null then continue
+                       visitor.enter_visit(npropdef)
+                       for mprop in visitor.calls do
+                               if mprop.name != mprop_name then continue
+                               res.add new MEntityMatch(self, mpropdef)
+                       end
+               end
+               return res
+       end
+end
+
+# A query to search a Nitdoc documentation page by its name.
+class DocQuery
+       super MetaQuery
+
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var name = args.first
+               for page in doc.pages do
+                       if name == "*" then # FIXME dev only
+                               res.add new PageMatch(self, page)
+                       else if page.title == name then
+                               res.add new PageMatch(self, page)
+                       else if page isa MEntityPage and page.mentity.cs_namespace == name then
+                               res.add new PageMatch(self, page)
+                       end
+               end
+               return res
+       end
+
+       redef fun make_results(nitx, results) do
+               var len = results.length
+               # FIXME how to render the pager for one worded namespaces like "standard"?
+               if len == 1 then
+                       var page = results.first.as(PageMatch).page
+                       var pager = new Pager
+                       pager.add page.write_to_string
+                       pager.render
+                       return page
+               else
+                       return super
+               end
+       end
+end
+
+# A match between a `DocPage` and a `MEntity`.
+class PageMatch
+       super NitxMatch
+
+       # `DocPage` matched.
+       var page: DocPage
+
+       redef fun make_list_item do
+               var page = self.page
+               if page isa MEntityPage then
+                       return page.mentity.cs_list_item
+               end
+               return " * {page.title}"
+       end
+end
+
+# A query to search source code from a file name.
+class CodeQuery
+       super MetaQuery
+
+       # FIXME refactor this!
+       redef fun perform(nitx, doc) do
+               var res = new Array[NitxMatch]
+               var name = args.first
+               # if name is an existing sourcefile, opens it
+               if name.file_exists then
+                       var fr = new FileReader.open(name)
+                       var content = fr.read_all
+                       fr.close
+                       res.add new CodeMatch(self, name, content)
+                       return res
+               end
+               # else, lookup the model by name
+               for mentity in doc.search_mentities(name) do
+                       if mentity isa MClass then continue
+                       if mentity isa MProperty then continue
+                       res.add new CodeMatch(self, mentity.cs_location, mentity.cs_source_code)
+               end
+               return res
+       end
+
+       redef fun make_results(nitx, results) do
+               var page = new DocPage("Code Results")
+               for res in results do
+                       page.add new CodeQueryArticle(self, res.as(CodeMatch))
+               end
+               return page
+       end
+end
+
+# A match between a piece of code and a string.
+class CodeMatch
+       super NitxMatch
+
+       # Location of the code match.
+       var location: String
+
+       # Piece of code matched.
+       var content: String
+
+       redef fun make_list_item do return "* {location}"
+end
+
+
 # A query that contains a nitx command.
 #
 # These commands are prefixed with `:` and are used to control the execution of
@@ -260,6 +461,41 @@ redef class DocModel
        end
 end
 
+# Visitor looking for initialized `MType` (new T).
+#
+# See `NewQuery`.
+private class TypeInitVisitor
+       super Visitor
+
+       # `MType` name to look for.
+       var mtype_name: String
+
+       var inits = new HashSet[MType]
+       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 and mtype.name == mtype_name then inits.add(mtype)
+       end
+end
+
+# Visitor looking for calls to a `MProperty` (new T).
+#
+# See `CallQuery`.
+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.mproperty
+       end
+end
+
 # display
 
 # A `DocArticle` that displays query results.
@@ -290,6 +526,24 @@ private class QueryResultArticle
        end
 end
 
+# An article that displays a piece of code.
+private class CodeQueryArticle
+       super DocArticle
+
+       # The query linked to the result to display.
+       var query: NitxQuery
+
+       # The result to display.
+       var result: CodeMatch
+
+       redef fun render_body do
+               addn ""
+               addn "in {result.location}".gray.bold
+               addn ""
+               add result.content
+       end
+end
+
 # A Pager is used to display data into a unix `less` container.
 private class Pager
 
index 1765d9e..cf4e815 100644 (file)
@@ -18,7 +18,6 @@
 module debugger
 
 intrude import naive_interpreter
-import nitx
 intrude import semantize::local_var_init
 intrude import semantize::scope
 intrude import toolcontext
@@ -470,10 +469,6 @@ class Debugger
                else if command == "help" then
                        help
                        return true
-               # Opens a new NitIndex prompt on current model
-               else if command == "nitx" then
-                       new NitIndex.with_infos(modelbuilder, self.mainmodule).prompt
-                       return true
                else if command == "bt" or command == "backtrack" then
                        print stack_trace
                        return true
index 69b34e7..23490a8 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# nit index, is a command tool used to display documentation
+# `nitx`, is a command tool that displays useful informations about the code.
+#
+# Features:
+#
+# * Display comment from name/namespace
+# * Display documentation page from Nitdoc in console
+# * Find type usage in parameters, returns and news.
+# * Find usage of a specific property.
+# * Find source code related to class/property by its name.
 module nitx
 
-import model_utils
-import modelize
-
-# Main class of the nit index tool
-# NitIndex build the model using the toolcontext argument
-# then wait for query on std in to display documentation
-class NitIndex
-       private var toolcontext: ToolContext
-       private var model: Model is noinit
-       private var mbuilder: ModelBuilder is noinit
-       private var mainmodule: MModule is noinit
-       private var arguments: Array[String] is noinit
-       private var renderer: PagerMatchesRenderer is noinit
-
-       # New constructor to use the pre-calculated model when interpreting a module
-       init with_infos(mbuilder: ModelBuilder, mmodule: MModule) do
-
-               self.model = mbuilder.model
-               self.mbuilder = mbuilder
-
-               self.mainmodule = mmodule
-               self.toolcontext = mbuilder.toolcontext
-               self.arguments = toolcontext.option_context.rest
-
-               renderer = new PagerMatchesRenderer(self)
-       end
-
-       init do
-               # We need a model to collect stufs
-               self.arguments = toolcontext.option_context.rest
-
-               if arguments.length > 2 then
-                       print toolcontext.tooldescription
-                       exit(1)
-               end
-
-               model = new Model
-               mbuilder = new ModelBuilder(model, toolcontext)
-
-               var mmodules = mbuilder.parse([arguments.first])
-               if mmodules.is_empty then return
-               mbuilder.run_phases
-               assert mmodules.length == 1
-               self.mainmodule = mmodules.first
-
-               renderer = new PagerMatchesRenderer(self)
-       end
-
-       fun start do
-               if arguments.length == 1 then
-                       welcome
-                       prompt
-               else
-                       search(arguments[1])
-               end
-       end
-
-       fun welcome do
-               print "Welcome in the Nit Index."
-               print ""
-               print "Loaded modules:"
-               var mmodules = new Array[MModule]
-               mmodules.add_all(model.mmodules)
-               var sorter = new MEntityNameSorter
-               sorter.sort(mmodules)
-               for m in mmodules do
-                       print "\t{m.name}"
-               end
-               print ""
-               help
-       end
-
-       fun help do
-               print "\nCommands:"
-               print "\tname\t\tlookup module, class and property with the corresponding 'name'"
-               print "\tparam: Type\tlookup methods using the corresponding 'Type' as parameter"
-               print "\treturn: Type\tlookup methods returning the corresponding 'Type'"
-               print "\tnew: Type\tlookup methods creating new instances of 'Type'"
-               print "\t:h\t\tdisplay this help message"
-               print "\t:q\t\texit"
-               print ""
-       end
-
-       fun prompt do
-               printn ">> "
-               search(sys.stdin.read_line)
-       end
+import modelbuilder
+import doc::doc_phases::doc_console
 
-       fun search(entry: String) do
-               if entry.is_empty then
-                       prompt
-                       return
-               end
-               if entry == ":h" then
-                       help
-                       prompt
-                       return
-               end
-               if entry == ":q" then return
-
-               # Parse query string
-               var query = parse_query(entry)
-
-               # search in index
-               var matches = new HashSet[IndexMatch]
-               if query isa IndexQueryPair then
-                       if query.category == "return" then
-                               # seek return types
-                               matches.add_all(search_returns(query))
-                       else if query.category == "param" then
-                               # seek param types
-                               matches.add_all(search_params(query))
-                       else if query.category == "new" then
-                               # seek type inits
-                               matches.add_all(search_inits(query))
-                       end
-               else
-                       matches.add_all(search_modules(query))
-                       matches.add_all(search_classes(query))
-                       matches.add_all(search_properties(query))
-               end
-               # no matches
-               if matches.is_empty then
-                       print "Nothing known about '{query.string}', type ':h' for help"
-               else
-                       renderer.render_matches(query, matches)
-               end
-               if arguments.length == 1 then prompt
-       end
-
-       private fun parse_query(str: String): IndexQuery do
-               var parts = str.split_with(":")
-               if parts.length == 1 then
-                       return new IndexQuery(str, parts[0])
-               else
-                       var category = parts[0]
-                       var keyword = parts[1]
-                       if keyword.chars.first == ' ' then keyword = keyword.substring_from(1)
-                       return new IndexQueryPair(str, keyword, category)
-               end
-       end
-
-       # search for modules
-       private fun search_modules(query: IndexQuery): Set[MModule] do
-               var matches = new HashSet[MModule]
-               for mmodule in model.mmodules do
-                       if mmodule.name == query.keyword then matches.add(mmodule)
-               end
-               return matches
-       end
-
-       # search for classes
-       private fun search_classes(query: IndexQuery): Set[MClass] do
-               var matches = new HashSet[MClass]
-               for mclass in model.mclasses do
-                       if mclass.name == query.keyword then matches.add(mclass)
-               end
-               return matches
-       end
-
-       # search for properties
-       private fun search_properties(query: IndexQuery): Set[MProperty] do
-               var matches = new HashSet[MProperty]
-               for mproperty in model.mproperties do
-                       if mproperty.name == query.keyword then matches.add(mproperty)
-               end
-               return matches
-       end
-
-       # search for mpropdef returning keyword
-       private fun search_returns(query: IndexQuery): Set[MProperty] do
-               var matches = new HashSet[MProperty]
-               for mproperty in model.mproperties do
-                       var intro = mproperty.intro
-                       if intro isa MMethodDef then
-                               if intro.msignature.return_mtype != null and intro.msignature.return_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
-                       else if intro isa MAttributeDef then
-                               if intro.static_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
-                       end
-               end
-               return matches
-       end
-
-       # search for mpropdef taking keyword as parameter
-       private fun search_params(query: IndexQuery): Set[MProperty] do
-               var matches = new HashSet[MProperty]
-               for mproperty in model.mproperties do
-                       var intro = mproperty.intro
-                       if intro isa MMethodDef then
-                               var mparameters = intro.msignature.mparameters
-                               for mparameter in mparameters do
-                                       if mparameter.mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
-                               end
-                       else if intro isa MAttributeDef then
-                               if intro.static_mtype.to_console.has_prefix(query.keyword) then matches.add(mproperty)
-                       end
-               end
-               return matches
-       end
-
-       # search for mpropdef creating new instance of keyword
-       private fun search_inits(query: IndexQuery): Set[MPropDef] do
-               var mtype2mpropdefs = toolcontext.nitx_phase.mtype2mpropdefs
-               var matches = new HashSet[MPropDef]
-               for mtype in mtype2mpropdefs.keys do
-                       if mtype.to_console.has_prefix(query.keyword) then
-                               for mpropdef in mtype2mpropdefs[mtype] do
-                                       matches.add(mpropdef)
-                               end
-                       end
-               end
-               return matches
-       end
-end
-
-private class IndexQuery
-       var string: String
-       var keyword: String
-end
-
-private class IndexQueryPair
-       super IndexQuery
-       var category: String
-end
+redef class ToolContext
 
-# A match to a query in the nit index
-private interface IndexMatch
-       # Short preview of the result for result list display
-       fun preview(index: NitIndex, output: Pager) is abstract
-       fun content(index: NitIndex, output: Pager) is abstract
-end
+       # Nittx generation phase.
+       var docx: Phase = new NitxPhase(self, null)
 
-# Code Analysis
+       # Used to shortcut the prompt and display directly the result in console.
+       var opt_query = new OptionString("Nitx query to perform", "-q", "--query")
 
-redef class ToolContext
-       private var nitx_phase: NitxPhase = new NitxPhase(self, [modelize_property_phase])
+       init do option_context.add_option opt_query
 end
 
-# Compiler phase for nitx
+# Nitx phase explores the model and prepares the console rendering.
 private class NitxPhase
        super Phase
-
-       var mtype2mpropdefs = new HashMap[MType, Set[MPropDef]]
-       redef fun process_npropdef(npropdef) do
-               var visitor = new TypeInitVisitor
-               visitor.enter_visit(npropdef)
-               for mtype in visitor.inits do
-                       if not mtype2mpropdefs.has_key(mtype) then
-                               mtype2mpropdefs[mtype] = new HashSet[MPropDef]
-                       end
-                       mtype2mpropdefs[mtype].add(npropdef.mpropdef.as(not null))
-               end
-       end
-end
-
-# Visitor looking for initialized mtype (new T)
-private class TypeInitVisitor
-       super Visitor
-
-       var inits = new HashSet[MType]
-       redef fun visit(node)
+       redef fun process_mainmodule(mainmodule, mmodules)
        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 inits.add(mtype)
-       end
-end
-
-# Pager output for console
-
-private class PagerMatchesRenderer
-       var index: NitIndex
-
-       fun render_matches(query: IndexQuery, matches: Collection[IndexMatch]) do
-               var pager = new Pager
-               if matches.length == 1 then
-                       pager.add("= result for '{query.string}'".bold)
-                       pager.add("")
-                       pager.indent = pager.indent + 1
-                       matches.first.content(index, pager)
-                       pager.indent = pager.indent - 1
-               else
-                       pager.add("= multiple results for '{query.string}'".bold)
-                       pager.indent = pager.indent + 1
-                       for match in matches do
-                               pager.add("")
-                               match.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               pager.render
-       end
-
-       fun props_fulldoc(pager: Pager, raw_mprops: List[MProperty]) do
-               # group by module
-               var cats = new HashMap[MModule, Array[MProperty]]
-               for mprop in raw_mprops do
-                       if mprop isa MAttribute then continue
-                       var key = mprop.intro.mclassdef.mmodule
-                       if not cats.has_key(key) then cats[key] = new Array[MProperty]
-                       cats[key].add(mprop)
-               end
-               #sort groups
-               var sorter = new MEntityNameSorter
-               var sorted = new Array[MModule]
-               sorted.add_all(cats.keys)
-               sorter.sort(sorted)
-               # display
-               for mmodule in sorted do
-                       var mprops = cats[mmodule]
-                       pager.add("# matches in module {mmodule.namespace.bold}")
-                       sorter.sort(mprops)
-                       for mprop in mprops do
-
-                       end
-                       pager.add_rule
-               end
-       end
-end
-
-private class Pager
-       var content = new FlatBuffer
-       var indent = 0
-       fun add(text: String) do
-               add_indent
-               addn("{text}\n")
-       end
-       fun add_indent do addn("  " * indent)
-       fun addn(text: String) do content.append(text.escape)
-       fun add_rule do add("\n---\n")
-       fun render do sys.system("echo \"{content}\" | less -r")
-end
-
-redef class MModule
-       super IndexMatch
-       # prototype of the module
-       #       module name
-       private fun prototype: String do return "module {name.bold}"
-
-       # namespace of the module
-       #       project::name
-       private fun namespace: String do
-               if mgroup == null or mgroup.mproject.name == self.name then
-                       return self.name
-               else
-                       return "{mgroup.mproject}::{self.name}"
-               end
-       end
-
-       redef fun preview(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       pager.add(mdoc.short_comment.green)
-               end
-               pager.add(prototype)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-       end
-
-       redef fun content(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       for comment in mdoc.content do pager.add(comment.green)
-               end
-               pager.add(prototype)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-               pager.indent = pager.indent + 1
-               var sorter = new MEntityNameSorter
-               # imported modules
-               var imports = new Array[MModule]
-               for mmodule in in_importation.direct_greaters.to_a do
-                       imports.add(mmodule)
-               end
-               if not imports.is_empty then
-                       sorter.sort(imports)
-                       pager.add("")
-                       pager.add("== imported modules".bold)
-                       pager.indent = pager.indent + 1
-                       for mmodule in imports do
-                               pager.add("")
-                               mmodule.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               # mclassdefs
-               var intros = new Array[MClassDef]
-               var redefs = new Array[MClassDef]
-               for mclassdef in mclassdefs do
-                       if mclassdef.is_intro then
-                               intros.add(mclassdef)
-                       else
-                               redefs.add(mclassdef)
-                       end
-               end
-               # introductions
-               if not intros.is_empty then
-                       sorter.sort(intros)
-                       pager.add("")
-                       pager.add("== introduced classes".bold)
-                       pager.indent = pager.indent + 1
-                       for mclass in intros do
-                               pager.add("")
-                               mclass.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               # refinements
-               if not redefs.is_empty then
-                       sorter.sort(redefs)
-                       pager.add("")
-                       pager.add("== refined classes".bold)
-                       pager.indent = pager.indent + 1
-                       for mclass in redefs do
-                               pager.add("")
-                               mclass.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               pager.indent = pager.indent - 1
-       end
-end
+               var doc = new DocModel(mainmodule.model, mainmodule)
 
-redef class MClass
-       super IndexMatch
-       # return the generic signature of the class
-       #       [E, F]
-       private fun signature: String do
-               var res = new FlatBuffer
-               if arity > 0 then
-                       res.append("[")
-                       for i in [0..mparameters.length[ do
-                               res.append(mparameters[i].name)
-                               if i < mparameters.length - 1 then res.append(", ")
-                       end
-                       res.append("]")
-               end
-               return res.to_s
-       end
+               var phases = [
+                       new ExtractionPhase(toolcontext, doc),
+                       new MakePagePhase(toolcontext, doc),
+                       new ConcernsPhase(toolcontext, doc),
+                       new StructurePhase(toolcontext, doc): DocPhase]
 
-       # return the prototype of the class
-       # class name is displayed with colors depending on visibility
-       #       abstract interface Foo[E]
-       private fun prototype: String do
-               var res = new FlatBuffer
-               res.append("{kind} ")
-               if visibility.to_s == "public" then res.append("{name}{signature}".bold.green)
-               if visibility.to_s == "private" then res.append("{name}{signature}".bold.red)
-               if visibility.to_s == "protected" then res.append("{name}{signature}".bold.yellow)
-               return res.to_s
-       end
-
-       private fun namespace: String do
-               return "{intro_mmodule.namespace}::{name}"
-       end
-
-       redef fun preview(index, pager) do
-               intro.preview(index, pager)
-       end
-
-       redef fun content(index, pager) do
-               # intro comment
-               var sorter = new MEntityNameSorter
-               var mdoc = intro.mdoc
-               if mdoc != null then
-                       for comment in mdoc.content do pager.add(comment.green)
-               end
-               pager.add(intro.to_console)
-               pager.add("{intro.namespace}".bold.gray + " (lines {intro.location.lines})".gray)
-               pager.indent = pager.indent + 1
-               # parents
-               var supers = self.in_hierarchy(index.mainmodule).direct_greaters.to_a
-               if not supers.is_empty then
-                       sorter.sort(supers)
-                       pager.add("")
-                       pager.add("== supers".bold)
-                       pager.indent = pager.indent + 1
-                       for mclass in supers do
-                               pager.add("")
-                               mclass.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
+               for phase in phases do
+                       toolcontext.info("# {phase.class_name}", 1)
+                       phase.apply
                end
-               # formal types
-               if not self.parameter_types.is_empty then
-                       pager.add("")
-                       pager.add("== formal types".bold)
-                       pager.indent = pager.indent + 1
-                       for ft, bound in self.parameter_types do
-                               pager.add("")
-                               pager.add("{ft.to_s.bold.green}: {bound.to_console}")
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               # intro mproperties
-               var mpropdefs = intro.mpropdefs
-               index.mainmodule.linearize_mpropdefs(mpropdefs)
-               for cat in intro.cats2mpropdefs.keys do
-                       var defs = intro.cats2mpropdefs[cat].to_a
-                       if defs.is_empty then continue
-                       sorter.sort(defs)
-                       pager.add("")
-                       pager.add("== {cat}".bold)
-                       pager.indent = pager.indent + 1
-                       for mpropdef in defs do
-                               pager.add("")
-                               mpropdef.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               # refinements
-               if not self.mclassdefs.is_empty then
-                       pager.add("")
-                       pager.add("== refinements".bold)
-                       var mclassdefs = self.mclassdefs
-                       index.mainmodule.linearize_mclassdefs(mclassdefs)
-                       pager.indent = pager.indent + 1
-                       for mclassdef in mclassdefs do
-                               if not mclassdef.is_intro then
-                                       pager.add("")
-                                       mclassdef.content(index, pager)
-                               end
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               pager.indent = pager.indent - 1
-       end
-end
-
-redef class MClassDef
-       super IndexMatch
 
-       private fun namespace: String do
-               return "{mmodule.full_name}::{mclass.name}"
-       end
-
-       fun to_console: String do
-               var res = new FlatBuffer
-               if not is_intro then res.append("redef ")
-               res.append(mclass.prototype)
-               return res.to_s
-       end
-
-       redef fun preview(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       pager.add(mdoc.short_comment.green)
-               end
-               pager.add(to_console)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-       end
-
-       redef fun content(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       for comment in mdoc.content do pager.add(comment.green)
-               end
-               pager.add(to_console)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-               pager.indent = pager.indent + 1
-               var mpropdefs = self.mpropdefs
-               var sorter = new MEntityNameSorter
-               index.mainmodule.linearize_mpropdefs(mpropdefs)
-               for cat in cats2mpropdefs.keys do
-                       var defs = cats2mpropdefs[cat].to_a
-                       sorter.sort(defs)
-                       if defs.is_empty then continue
-                       pager.add("")
-                       pager.add("== {cat}".bold)
-                       pager.indent = pager.indent + 1
-                       for mpropdef in defs do
-                               pager.add("")
-                               mpropdef.preview(index, pager)
-                       end
-                       pager.indent = pager.indent - 1
-               end
-               pager.indent = pager.indent - 1
-       end
-
-       # get mpropdefs grouped by categories (vt, init, methods)
-       fun cats2mpropdefs: Map[String, Set[MPropDef]] do
-               var cats = new ArrayMap[String, Set[MPropDef]]
-               cats["virtual types"] = new HashSet[MPropDef]
-               cats["constructors"] = new HashSet[MPropDef]
-               cats["methods"] = new HashSet[MPropDef]
-
-               for mpropdef in mpropdefs do
-                       if mpropdef isa MAttributeDef then continue
-                       if mpropdef isa MVirtualTypeDef then cats["virtual types"].add(mpropdef)
-                       if mpropdef isa MMethodDef then
-                               if mpropdef.mproperty.is_init then
-                                       cats["constructors"].add(mpropdef)
-                               else
-                                       cats["methods"].add(mpropdef)
-                               end
-                       end
-               end
-               return cats
-       end
-end
-
-redef class MProperty
-       super IndexMatch
-
-       fun to_console: String do
-               if visibility.to_s == "public" then return name.green
-               if visibility.to_s == "private" then return name.red
-               if visibility.to_s == "protected" then return name.yellow
-               return name.bold
-       end
-
-       redef fun preview(index, pager) do
-               intro.preview(index, pager)
-       end
-
-       redef fun content(index, pager) do
-               intro.content(index, pager)
-               pager.indent = pager.indent + 1
-               var mpropdefs = self.mpropdefs
-               index.mainmodule.linearize_mpropdefs(mpropdefs)
-               for mpropdef in mpropdefs do
-                       if mpropdef isa MAttributeDef then continue
-                       if not mpropdef.is_intro then
-                               pager.add("")
-                               mpropdef.preview(index, pager)
-                       end
-               end
-               pager.indent = pager.indent - 1
-       end
-end
-
-redef class MPropDef
-       super IndexMatch
-
-       fun to_console: String is abstract
-
-       private fun namespace: String do
-               return "{mclassdef.namespace}::{mproperty.name}"
-       end
-
-       redef fun preview(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       pager.add(mdoc.short_comment.green)
-               end
-               pager.add(to_console)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-       end
-
-       redef fun content(index, pager) do
-               var mdoc = self.mdoc
-               if mdoc != null then
-                       for comment in mdoc.content do pager.add(comment.green)
-               end
-               pager.add(to_console)
-               pager.add("{namespace}".bold.gray + " (lines {location.lines})".gray)
-       end
-end
-
-redef class MMethodDef
-       redef fun to_console do
-               var res = new FlatBuffer
-               if not is_intro then res.append("redef ")
-               if not mproperty.is_init then res.append("fun ")
-               res.append(mproperty.to_console.bold)
-               if msignature != null then res.append(msignature.to_console)
-               if is_abstract then res.append " is abstract"
-               if is_intern then res.append " is intern"
-               if is_extern then res.append " is extern"
-               return res.to_s
-       end
-end
-
-redef class MVirtualTypeDef
-       redef fun to_console do
-               var res = new FlatBuffer
-               res.append("type ")
-               res.append(mproperty.to_console.bold)
-               res.append(": {bound.to_console}")
-               return res.to_s
-       end
-end
-
-redef class MAttributeDef
-       redef fun to_console do
-               var res = new FlatBuffer
-               res.append("var ")
-               res.append(mproperty.to_console.bold)
-               res.append(": {static_mtype.to_console}")
-               return res.to_s
-       end
-end
-
-redef class MSignature
-       redef fun to_console do
-               var res = new FlatBuffer
-               if not mparameters.is_empty then
-                       res.append("(")
-                       for i in [0..mparameters.length[ do
-                               res.append(mparameters[i].to_console)
-                               if i < mparameters.length - 1 then res.append(", ")
-                       end
-                       res.append(")")
-               end
-               if return_mtype != null then
-                       res.append(": {return_mtype.to_console}")
-               end
-               return res.to_s
-       end
-end
-
-redef class MParameter
-       fun to_console: String do
-               var res = new FlatBuffer
-               res.append("{name}: {mtype.to_console}")
-               if is_vararg then res.append("...")
-               return res.to_s
-       end
-end
-
-redef class MType
-       fun to_console: String do return self.to_s
-end
-
-redef class MNullableType
-       redef fun to_console do return "nullable {mtype.to_console}"
-end
-
-redef class MGenericType
-       redef fun to_console do
-               var res = new FlatBuffer
-               res.append("{mclass.name}[")
-               for i in [0..arguments.length[ do
-                       res.append(arguments[i].to_console)
-                       if i < arguments.length - 1 then res.append(", ")
-               end
-               res.append("]")
-               return res.to_s
-       end
-end
-
-redef class MParameterType
-       redef fun to_console do return name
-end
-
-redef class MVirtualType
-       redef fun to_console do return mproperty.name
-end
-
-redef class MDoc
-       private fun short_comment: String do
-               return content.first
-       end
-end
-
-# Redef String class to add a function to color the string
-redef class String
-
-       private fun add_escape_char(escapechar: String): String do
-               return "{escapechar}{self}\\033[0m"
-       end
-
-       private fun esc: Char do return 27.ascii
-       private fun gray: String do return add_escape_char("{esc}[30m")
-       private fun red: String do return add_escape_char("{esc}[31m")
-       private fun green: String do return add_escape_char("{esc}[32m")
-       private fun yellow: String do return add_escape_char("{esc}[33m")
-       private fun blue: String do return add_escape_char("{esc}[34m")
-       private fun purple: String do return add_escape_char("{esc}[35m")
-       private fun cyan: String do return add_escape_char("{esc}[36m")
-       private fun light_gray: String do return add_escape_char("{esc}[37m")
-       private fun bold: String do return add_escape_char("{esc}[1m")
-       private fun underline: String do return add_escape_char("{esc}[4m")
-
-       private fun escape: String
-       do
-               var b = new FlatBuffer
-               for c in self.chars do
-                       if c == '\n' then
-                               b.append("\\n")
-                       else if c == '\0' then
-                               b.append("\\0")
-                       else if c == '"' then
-                               b.append("\\\"")
-                       else if c == '\\' then
-                               b.append("\\\\")
-                       else if c == '`' then
-                               b.append("'")
-                       else if c.ascii < 32 then
-                               b.append("\\{c.ascii.to_base(8, false)}")
-                       else
-                               b.add(c)
-                       end
+               # start nitx
+               var nitx = new Nitx(toolcontext, doc)
+               var q = toolcontext.opt_query.value
+               if q != null then # shortcut prompt
+                       print ""
+                       nitx.do_query(q)
+                       return
                end
-               return b.to_s
-       end
-end
-
-redef class Location
-       fun lines: String do
-               return "{line_start}-{line_end}"
+               nitx.start
        end
 end
 
-# Create a tool context to handle options and paths
+# build toolcontext
 var toolcontext = new ToolContext
-toolcontext.tooldescription = "Usage: nitx [OPTION]... <file.nit> [query]\nDisplays specific pieces of API information from Nit source files."
+var tpl = new Template
+tpl.add "Usage: nitx [OPTION]... <file.nit>... [query]\n"
+tpl.add "Displays specific pieces of API information from Nit source files."
+toolcontext.tooldescription = tpl.write_to_string
+
+# process options
 toolcontext.process_options(args)
+var arguments = toolcontext.option_context.rest
 
-# Here we launch the nit index
-var ni = new NitIndex(toolcontext)
-ni.start
+# build model
+var model = new Model
+var mbuilder = new ModelBuilder(model, toolcontext)
+var mmodules = mbuilder.parse_full(arguments)
 
-# TODO seek subclasses and super classes <.<class> >.<class>
-# TODO seek subclasses and super types <:<type> >:<type>
-# TODO seek with regexp
-# TODO standardize namespaces with private option
+# process
+if mmodules.is_empty then return
+mbuilder.run_phases
+toolcontext.run_global_phases(mmodules)
index 402451a..7f05220 100644 (file)
@@ -1,3 +1,3 @@
-base_simple3.nit A
-base_simple3.nit foo
-base_simple3.nit base_simple3
+base_simple3.nit -q A
+base_simple3.nit -q foo
+base_simple3.nit -q base_simple3
index fc2e797..7a38c28 100644 (file)
@@ -15,6 +15,7 @@ redef class Deserializer
                if name == "Array[String]" then return new Array[String].from_deserializer(self)
                if name == "HashMap[Serializable, Array[Couple[Serializable, Int]]]" then return new HashMap[Serializable, Array[Couple[Serializable, Int]]].from_deserializer(self)
                if name == "Array[Couple[Serializable, Int]]" then return new Array[Couple[Serializable, Int]].from_deserializer(self)
+               if name == "Couple[Serializable, Int]" then return new Couple[Serializable, Int].from_deserializer(self)
                return super
        end
 end
index bb20768..ec462a0 100644 (file)
@@ -1,3 +1,3 @@
-Usage: nitx [OPTION]... <file.nit> [query]
+Usage: nitx [OPTION]... <file.nit>... [query]
 Displays specific pieces of API information from Nit source files.
 Use --help for help
index bf43dd3..f22bfee 100644 (file)
@@ -1,22 +1,13 @@
-\e[1m= result for 'A'\e[0m
 
-  class \e[32m\e[1mA\e[0m\e[0m
-  \e[30m\e[1mbase_simple3::A\e[0m\e[0m\e[30m (lines 29-32)\e[0m
-    
-    \e[1m== supers\e[0m
-      
-      interface \e[32m\e[1mObject\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::Object\e[0m\e[0m\e[30m (lines 19-20)\e[0m
-    
-    \e[1m== constructors\e[0m
-      
-      redef \e[1m\e[32minit\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::A::init\e[0m\e[0m\e[30m (lines 30-30)\e[0m
-    
-    \e[1m== methods\e[0m
-      
-      fun \e[1m\e[32mrun\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::A::run\e[0m\e[0m\e[30m (lines 31-31)\e[0m
-    
-    \e[1m== refinements\e[0m
+\e[1m\e[32m# 2 result(s) for 'comment: A'\e[m\e[m
+
\e[1m\e[32mC\e[m\e[m \e[1m\e[34mA\e[m\e[m
+   \e[1m\e[30mbase_simple3::A\e[m\e[m
+   class A
+   \e[30mbase_simple3.nit:29,1--32,3\e[m
+
\e[1m\e[32mC\e[m\e[m \e[1m\e[34mA\e[m\e[m
+   \e[1m\e[30mbase_simple3::base_simple3::A\e[m\e[m
+   class A
+   \e[30mbase_simple3.nit:29,1--32,3\e[m
 
index bdb2b9f..b7ca320 100644 (file)
@@ -1,5 +1,13 @@
-\e[1m= result for 'foo'\e[0m
 
-  fun \e[1m\e[32mfoo\e[0m\e[0m
-  \e[30m\e[1mbase_simple3::Sys::foo\e[0m\e[0m\e[30m (lines 49-49)\e[0m
+\e[1m\e[32m# 2 result(s) for 'comment: foo'\e[m\e[m
+
\e[1m\e[32mF\e[m\e[m \e[1m\e[34mfoo\e[m\e[m
+   \e[1m\e[30mbase_simple3::Sys::foo\e[m\e[m
+   fun foo
+   \e[30mbase_simple3.nit:49,1--19\e[m
+
\e[1m\e[32mF\e[m\e[m \e[1m\e[34mfoo\e[m\e[m
+   \e[1m\e[30mbase_simple3::base_simple3::Sys::foo\e[m\e[m
+   fun foo
+   \e[30mbase_simple3.nit:49,1--19\e[m
 
index 83a361e..b1fdd02 100644 (file)
@@ -1,28 +1,18 @@
-\e[1m= result for 'base_simple3'\e[0m
 
-  module \e[1mbase_simple3\e[0m
-  \e[30m\e[1mbase_simple3\e[0m\e[0m\e[30m (lines 17-66)\e[0m
-    
-    \e[1m== introduced classes\e[0m
-      
-      class \e[32m\e[1mA\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::A\e[0m\e[0m\e[30m (lines 29-32)\e[0m
-      
-      class \e[32m\e[1mB\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::B\e[0m\e[0m\e[30m (lines 34-42)\e[0m
-      
-      enum \e[32m\e[1mBool\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::Bool\e[0m\e[0m\e[30m (lines 22-23)\e[0m
-      
-      class \e[32m\e[1mC\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::C\e[0m\e[0m\e[30m (lines 44-47)\e[0m
-      
-      enum \e[32m\e[1mInt\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::Int\e[0m\e[0m\e[30m (lines 25-27)\e[0m
-      
-      interface \e[32m\e[1mObject\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::Object\e[0m\e[0m\e[30m (lines 19-20)\e[0m
-      
-      class \e[32m\e[1mSys\e[0m\e[0m
-      \e[30m\e[1mbase_simple3::Sys\e[0m\e[0m\e[30m (lines 49-49)\e[0m
+\e[1m\e[32m# 3 result(s) for 'comment: base_simple3'\e[m\e[m
+
\e[1m\e[32mP\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+   \e[1m\e[30mbase_simple3\e[m\e[m
+   project base_simple3
+   \e[30mbase_simple3.nit:17,1--66,13\e[m
+
\e[1m\e[32mG\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+   \e[1m\e[30mbase_simple3\e[m\e[m
+   group base_simple3
+   \e[30mbase_simple3.nit:17,1--66,13\e[m
+
\e[1m\e[32mM\e[m\e[m \e[1m\e[34mbase_simple3\e[m\e[m
+   \e[1m\e[30mbase_simple3::base_simple3\e[m\e[m
+   module base_simple3
+   \e[30mbase_simple3.nit:17,1--66,13\e[m