README: document nit_env.sh
[nit.git] / src / nitcatalog.nit
index 6aba6f1..21847ee 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-# Basic catalog generator for Nit projects
+# Basic catalog generator for Nit packages
 #
 # See: <http://nitlanguage.org/catalog/>
 #
-# The tool scans projects and generates the HTML files of a catalog.
+# The tool scans packages and generates the HTML files of a catalog.
 #
 # ## Features
 #
-# * [X] scan projects and their `.ini`
-# * [X] generate lists of projects
-# * [X] generate a page per project with the readme and most metadata
+# * [X] scan packages and their `.ini`
+# * [X] generate lists of packages
+# * [X] generate a page per package with the readme and most metadata
 # * [ ] link/include/be included in the documentation
-# * [ ] propose `related projects`
-# * [ ] show directory content (a la nitls)
+# * [ ] propose `related packages`
+# * [X] show directory content (a la nitls)
 # * [X] gather git information from the working directory
 # * [ ] gather git information from the repository
-# * [ ] gather project information from github
+# * [ ] gather package information from github
 # * [ ] gather people information from github
 # * [ ] reify people
 # * [ ] separate information gathering from rendering
 # * [ ] move up information gathering in (existing or new) service modules
-# * [ ] add command line options
+# * [X] add command line options
 # * [ ] harden HTML (escaping, path injection, etc)
 # * [ ] nitcorn server with RESTful API
 #
 # How to use the tool as the basis of a Nit code archive on the web usable with a package manager is not clear.
 module nitcatalog
 
-import loader # Scan&load projects, groups and modules
+import loader # Scan&load packages, groups and modules
 import doc::doc_down # Display mdoc
 import md5 # To get gravatar images
 import counter # For statistics
 import modelize # To process and count classes and methods
 
-redef class MProject
+redef class MPackage
        # Return the associated metadata from the `ini`, if any
        fun metadata(key: String): nullable String
        do
@@ -86,6 +86,12 @@ class CatalogPage
        # Placeholder to include additional things before the `</head>`.
        var more_head = new Template
 
+       # Relative path to the root directory (with the index file).
+       #
+       # Use "" for pages in the root directory
+       # Use ".." for pages in a subdirectory
+       var rootpath: String
+
        redef init
        do
                add """
@@ -94,7 +100,7 @@ class CatalogPage
 <head>
        <meta charset="utf-8">
        <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
-       <link rel="stylesheet" media="all" href="style.css">
+       <link rel="stylesheet" media="all" href="{{{rootpath / "style.css"}}}">
 """
                add more_head
 
@@ -116,7 +122,7 @@ class CatalogPage
     </div>
     <div class='collapse navbar-collapse' id='topmenu-collapse'>
      <ul class='nav navbar-nav'>
-      <li><a href="index.html">Catalog</a></li>
+      <li><a href="{{{rootpath / "index.html"}}}">Catalog</a></li>
      </ul>
     </div>
    </div>
@@ -139,7 +145,7 @@ class CatalogPage
 end
 
 redef class Int
-       # Returns `log(self+1)`. Used to compute score of projects
+       # Returns `log(self+1)`. Used to compute score of packages
        fun score: Float do return (self+1).to_f.log
 end
 
@@ -150,46 +156,46 @@ class Catalog
        # used to access the files and count source lines of code
        var modelbuilder: ModelBuilder
 
-       # Projects by tag
-       var tag2proj = new MultiHashMap[String, MProject]
+       # Packages by tag
+       var tag2proj = new MultiHashMap[String, MPackage]
 
-       # Projects by category
-       var cat2proj = new MultiHashMap[String, MProject]
+       # Packages by category
+       var cat2proj = new MultiHashMap[String, MPackage]
 
-       # Projects by maintainer
-       var maint2proj = new MultiHashMap[String, MProject]
+       # Packages by maintainer
+       var maint2proj = new MultiHashMap[String, MPackage]
 
-       # Projects by contributors
-       var contrib2proj = new MultiHashMap[String, MProject]
+       # Packages by contributors
+       var contrib2proj = new MultiHashMap[String, MPackage]
 
-       # Dependency between projects
-       var deps = new POSet[MProject]
+       # Dependency between packages
+       var deps = new POSet[MPackage]
 
-       # Number of modules by project
-       var mmodules = new Counter[MProject]
+       # Number of modules by package
+       var mmodules = new Counter[MPackage]
 
-       # Number of classes by project
-       var mclasses = new Counter[MProject]
+       # Number of classes by package
+       var mclasses = new Counter[MPackage]
 
-       # Number of methods by project
-       var mmethods = new Counter[MProject]
+       # Number of methods by package
+       var mmethods = new Counter[MPackage]
 
-       # Number of line of code by project
-       var loc = new Counter[MProject]
+       # Number of line of code by package
+       var loc = new Counter[MPackage]
 
-       # Number of commits by project
-       var commits = new Counter[MProject]
+       # Number of commits by package
+       var commits = new Counter[MPackage]
 
-       # Score by project
+       # Score by package
        #
        # The score is loosely computed using other metrics
-       var score = new Counter[MProject]
+       var score = new Counter[MPackage]
 
-       # Scan, register and add a contributor to a project
-       fun add_contrib(person: String, mproject: MProject, res: Template)
+       # Scan, register and add a contributor to a package
+       fun add_contrib(person: String, mpackage: MPackage, res: Template)
        do
                var projs = contrib2proj[person]
-               if not projs.has(mproject) then projs.add mproject
+               if not projs.has(mpackage) then projs.add mpackage
                var name = person
                var email = null
                var page = null
@@ -238,45 +244,102 @@ class Catalog
                res.add "</li>"
        end
 
+       # Recursively generate a level in the file tree of the *content* section
+       private fun gen_content_level(ot: OrderedTree[MConcern], os: Array[Object], res: Template)
+       do
+               res.add "<ul>\n"
+               for o in os do
+                       res.add "<li>"
+                       if o isa MGroup then
+                               var d = ""
+                               var mdoc = o.mdoc
+                               if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
+                               res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
+                       else if o isa MModule then
+                               var d = ""
+                               var mdoc = o.mdoc
+                               if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
+                               res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
+                       else
+                               abort
+                       end
+                       var subs = ot.sub.get_or_null(o)
+                       if subs != null then gen_content_level(ot, subs, res)
+                       res.add "</li>\n"
+               end
+               res.add "</ul>\n"
+       end
 
-       # Compute information and generate a full HTML page for a project
-       fun project_page(mproject: MProject): Writable
+       # Compute information and generate a full HTML page for a package
+       fun package_page(mpackage: MPackage): Writable
        do
-               var res = new CatalogPage
-               var score = score[mproject].to_f
-               var name = mproject.name.html_escape
+               var res = new CatalogPage("..")
+               var score = score[mpackage].to_f
+               var name = mpackage.name.html_escape
                res.more_head.add """<title>{{{name}}}</title>"""
 
                res.add """
 <div class="content">
 <h1 class="package-name">{{{name}}}</h1>
 """
-               var mdoc = mproject.mdoc_or_fallback
+               var mdoc = mpackage.mdoc_or_fallback
                if mdoc != null then
                        score += 100.0
                        res.add mdoc.html_documentation
                        score += mdoc.content.length.score
                end
+
+               res.add "<h2>Content</h2>"
+               var ot = new OrderedTree[MConcern]
+               for g in mpackage.mgroups do
+                       var pa = g.parent
+                       if g.is_interesting then
+                               ot.add(pa, g)
+                               pa = g
+                       end
+                       for mp in g.mmodules do
+                               ot.add(pa, mp)
+                       end
+               end
+               ot.sort_with(alpha_comparator)
+               gen_content_level(ot, ot.roots, res)
+
+
                res.add """
 </div>
 <div class="sidebar">
 <ul class="box">
 """
-               var homepage = mproject.metadata("upstream.homepage")
+               var tryit = mpackage.metadata("upstream.tryit")
+               if tryit != null then
+                       score += 1.0
+                       var e = tryit.html_escape
+                       res.add "<li><a href=\"{e}\">Try<span style=\"color:white\">n</span>it!</a></li>\n"
+               end
+               var apk = mpackage.metadata("upstream.apk")
+               if apk != null then
+                       score += 1.0
+                       var e = apk.html_escape
+                       res.add "<li><a href=\"{e}\">Android apk</a></li>\n"
+               end
+
+               res.add """</ul>\n<ul class="box">\n"""
+
+               var homepage = mpackage.metadata("upstream.homepage")
                if homepage != null then
                        score += 5.0
                        var e = homepage.html_escape
                        res.add "<li><a href=\"{e}\">{e}</a></li>\n"
                end
-               var maintainer = mproject.metadata("project.maintainer")
+               var maintainer = mpackage.metadata("package.maintainer")
                if maintainer != null then
                        score += 5.0
-                       add_contrib(maintainer, mproject, res)
-                       mproject.maintainers.add maintainer
+                       add_contrib(maintainer, mpackage, res)
+                       mpackage.maintainers.add maintainer
                        var projs = maint2proj[maintainer]
-                       if not projs.has(mproject) then projs.add mproject
+                       if not projs.has(mpackage) then projs.add mpackage
                end
-               var license = mproject.metadata("project.license")
+               var license = mpackage.metadata("package.license")
                if license != null then
                        score += 5.0
                        var e = license.html_escape
@@ -285,108 +348,109 @@ class Catalog
                res.add "</ul>\n"
 
                res.add "<h3>Source Code</h3>\n<ul class=\"box\">\n"
-               var browse = mproject.metadata("upstream.browse")
+               var browse = mpackage.metadata("upstream.browse")
                if browse != null then
                        score += 5.0
                        var e = browse.html_escape
                        res.add "<li><a href=\"{e}\">{e}</a></li>\n"
                end
-               var git = mproject.metadata("upstream.git")
+               var git = mpackage.metadata("upstream.git")
                if git != null then
                        var e = git.html_escape
                        res.add "<li><tt>{e}</tt></li>\n"
                end
-               var last_date = mproject.last_date
+               var last_date = mpackage.last_date
                if last_date != null then
                        var e = last_date.html_escape
                        res.add "<li>most recent commit: {e}</li>\n"
                end
-               var first_date = mproject.first_date
+               var first_date = mpackage.first_date
                if first_date != null then
                        var e = first_date.html_escape
                        res.add "<li>oldest commit: {e}</li>\n"
                end
-               var commits = commits[mproject]
+               var commits = commits[mpackage]
                if commits != 0 then
                        res.add "<li>{commits} commits</li>\n"
                end
                res.add "</ul>\n"
 
                res.add "<h3>Tags</h3>\n"
-               var tags = mproject.metadata("project.tags")
-               var ts2 = new Array[String]
-               var cat = null
+               var tags = mpackage.metadata("package.tags")
+               var ts = new Array[String]
                if tags != null then
-                       var ts = tags.split(",")
-                       for t in ts do
+                       for t in tags.split(",") do
                                t = t.trim
                                if t == "" then continue
-                               if cat == null then cat = t
-                               tag2proj[t].add mproject
-                               t = t.html_escape
-                               ts2.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+                               ts.add t
                        end
-                       res.add_list(ts2, ", ", ", ")
                end
-               if ts2.is_empty then
-                       var t = "none"
-                       cat = t
-                       tag2proj[t].add mproject
-                       res.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+               if ts.is_empty then ts.add "none"
+               if tryit != null then ts.add "tryit"
+               if apk != null then ts.add "apk"
+               var ts2 = new Array[String]
+               for t in ts do
+                       tag2proj[t].add mpackage
+                       t = t.html_escape
+                       ts2.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
                end
-               if cat != null then cat2proj[cat].add mproject
-               score += ts2.length.score
-
-               var reqs = deps[mproject].greaters.to_a
-               reqs.remove(mproject)
-               alpha_comparator.sort(reqs)
-               res.add "<h3>Requirements</h3>\n"
-               if reqs.is_empty then
-                       res.add "none"
-               else
-                       var list = new Array[String]
-                       for r in reqs do
-                               var direct = deps.has_direct_edge(mproject, r)
-                               var s = "<a href=\"{r}.html\">"
-                               if direct then s += "<strong>"
-                               s += r.to_s
-                               if direct then s += "</strong>"
-                               s += "</a>"
-                               list.add s
+               res.add_list(ts2, ", ", ", ")
+               var cat = ts.first
+               cat2proj[cat].add mpackage
+               score += ts.length.score
+
+               if deps.has(mpackage) then
+                       var reqs = deps[mpackage].greaters.to_a
+                       reqs.remove(mpackage)
+                       alpha_comparator.sort(reqs)
+                       res.add "<h3>Requirements</h3>\n"
+                       if reqs.is_empty then
+                               res.add "none"
+                       else
+                               var list = new Array[String]
+                               for r in reqs do
+                                       var direct = deps.has_direct_edge(mpackage, r)
+                                       var s = "<a href=\"{r}.html\">"
+                                       if direct then s += "<strong>"
+                                       s += r.to_s
+                                       if direct then s += "</strong>"
+                                       s += "</a>"
+                                       list.add s
+                               end
+                               res.add_list(list, ", ", " and ")
                        end
-                       res.add_list(list, ", ", " and ")
-               end
 
-               reqs = deps[mproject].smallers.to_a
-               reqs.remove(mproject)
-               alpha_comparator.sort(reqs)
-               res.add "<h3>Clients</h3>\n"
-               if reqs.is_empty then
-                       res.add "none"
-               else
-                       var list = new Array[String]
-                       for r in reqs do
-                               var direct = deps.has_direct_edge(r, mproject)
-                               var s = "<a href=\"{r}.html\">"
-                               if direct then s += "<strong>"
-                               s += r.to_s
-                               if direct then s += "</strong>"
-                               s += "</a>"
-                               list.add s
+                       reqs = deps[mpackage].smallers.to_a
+                       reqs.remove(mpackage)
+                       alpha_comparator.sort(reqs)
+                       res.add "<h3>Clients</h3>\n"
+                       if reqs.is_empty then
+                               res.add "none"
+                       else
+                               var list = new Array[String]
+                               for r in reqs do
+                                       var direct = deps.has_direct_edge(r, mpackage)
+                                       var s = "<a href=\"{r}.html\">"
+                                       if direct then s += "<strong>"
+                                       s += r.to_s
+                                       if direct then s += "</strong>"
+                                       s += "</a>"
+                                       list.add s
+                               end
+                               res.add_list(list, ", ", " and ")
                        end
-                       res.add_list(list, ", ", " and ")
-               end
 
-               score += deps[mproject].greaters.length.score
-               score += deps[mproject].direct_greaters.length.score
-               score += deps[mproject].smallers.length.score
-               score += deps[mproject].direct_smallers.length.score
+                       score += deps[mpackage].greaters.length.score
+                       score += deps[mpackage].direct_greaters.length.score
+                       score += deps[mpackage].smallers.length.score
+                       score += deps[mpackage].direct_smallers.length.score
+               end
 
-               var contributors = mproject.contributors
+               var contributors = mpackage.contributors
                if not contributors.is_empty then
                        res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
                        for c in contributors do
-                               add_contrib(c, mproject, res)
+                               add_contrib(c, mpackage, res)
                        end
                        res.add "</ul>"
                end
@@ -396,8 +460,8 @@ class Catalog
                var mclasses = 0
                var mmethods = 0
                var loc = 0
-               for g in mproject.mgroups do
-                       mmodules += g.module_paths.length
+               for g in mpackage.mgroups do
+                       mmodules += g.mmodules.length
                        for m in g.mmodules do
                                var am = modelbuilder.mmodule2node(m)
                                if am != null then
@@ -415,10 +479,10 @@ class Catalog
                                end
                        end
                end
-               self.mmodules[mproject] = mmodules
-               self.mclasses[mproject] = mclasses
-               self.mmethods[mproject] = mmethods
-               self.loc[mproject] = loc
+               self.mmodules[mpackage] = mmodules
+               self.mclasses[mpackage] = mclasses
+               self.mmethods[mpackage] = mmethods
+               self.loc[mpackage] = loc
 
                #score += mmodules.score
                score += mclasses.score
@@ -438,32 +502,32 @@ class Catalog
                res.add """
 </div>
 """
-               self.score[mproject] = score.to_i
+               self.score[mpackage] = score.to_i
 
                return res
        end
 
-       # Return a short HTML sequence for a project
+       # Return a short HTML sequence for a package
        #
        # Intended to use in lists.
-       fun li_project(p: MProject): String
+       fun li_package(p: MPackage): String
        do
                var res = ""
-               var f = "{p.name}.html"
+               var f = "p/{p.name}.html"
                res += "<a href=\"{f}\">{p}</a>"
                var d = p.mdoc_or_fallback
                if d != null then res += " - {d.html_synopsis.write_to_string}"
                return res
        end
 
-       # List projects by group.
+       # List packages by group.
        #
        # For each key of the `map` a `<h3>` is generated.
-       # Each project is then listed.
+       # Each package is then listed.
        #
        # The list of keys is generated first to allow fast access to the correct `<h3>`.
        # `id_prefix` is used to give an id to the `<h3>` element.
-       fun list_by(map: MultiHashMap[String, MProject], id_prefix: String): Template
+       fun list_by(map: MultiHashMap[String, MPackage], id_prefix: String): Template
        do
                var res = new Template
                var keys = map.keys.to_a
@@ -478,7 +542,7 @@ class Catalog
                        res.add "<h3 id=\"{id_prefix}{e}\">{e} ({projs.length})</h3>\n<ul>\n"
                        for p in projs do
                                res.add "<li>"
-                               res.add li_project(p)
+                               res.add li_package(p)
                                res.add "</li>"
                        end
                        res.add "</ul>"
@@ -486,8 +550,8 @@ class Catalog
                return res
        end
 
-       # List the 10 best projects from `cpt`
-       fun list_best(cpt: Counter[MProject]): Template
+       # List the 10 best packages from `cpt`
+       fun list_best(cpt: Counter[MPackage]): Template
        do
                var res = new Template
                res.add "<ul>"
@@ -496,7 +560,7 @@ class Catalog
                        if i > best.length then break
                        var p = best[best.length-i]
                        res.add "<li>"
-                       res.add li_project(p)
+                       res.add li_package(p)
                        # res.add " ({cpt[p]})"
                        res.add "</li>"
                end
@@ -504,10 +568,10 @@ class Catalog
                return res
        end
 
-       # Collect more information on a project using the `git` tool.
-       fun git_info(mproject: MProject)
+       # Collect more information on a package using the `git` tool.
+       fun git_info(mpackage: MPackage)
        do
-               var ini = mproject.ini
+               var ini = mpackage.ini
                if ini == null then return
 
                # TODO use real git info
@@ -515,7 +579,7 @@ class Catalog
                #var branch = ini.get_or_null("upstream.git.branch")
                #var directory = ini.get_or_null("upstream.git.directory")
 
-               var dirpath = mproject.root.filepath
+               var dirpath = mpackage.root.filepath
                if dirpath == null then return
 
                # Collect commits info
@@ -523,57 +587,61 @@ class Catalog
                var contributors = new Counter[String]
                var commits = res.split("\n")
                if commits.not_empty and commits.last == "" then commits.pop
-               self.commits[mproject] = commits.length
+               self.commits[mpackage] = commits.length
                for l in commits do
                        var s = l.split_once_on(';')
                        if s.length != 2 or s.last == "" then continue
 
                        # Collect date of last and first commit
-                       if mproject.last_date == null then mproject.last_date = s.first
-                       mproject.first_date = s.first
+                       if mpackage.last_date == null then mpackage.last_date = s.first
+                       mpackage.first_date = s.first
 
                        # Count contributors
                        contributors.inc(s.last)
                end
                for c in contributors.sort.reverse_iterator do
-                       mproject.contributors.add c
+                       mpackage.contributors.add c
                end
 
        end
 
-       # Produce a HTML table containig information on the projects
+       # Produce a HTML table containig information on the packages
        #
-       # `project_page` must have been called before so that information is computed.
-       fun table_projects(mprojects: Array[MProject]): Template
+       # `package_page` must have been called before so that information is computed.
+       fun table_packages(mpackages: Array[MPackage]): Template
        do
-               alpha_comparator.sort(mprojects)
+               alpha_comparator.sort(mpackages)
                var res = new Template
                res.add "<table data-toggle=\"table\" data-sort-name=\"name\" data-sort-order=\"desc\" width=\"100%\">\n"
                res.add "<thead><tr>\n"
                res.add "<th data-field=\"name\" data-sortable=\"true\">name</th>\n"
                res.add "<th data-field=\"maint\" data-sortable=\"true\">maint</th>\n"
                res.add "<th data-field=\"contrib\" data-sortable=\"true\">contrib</th>\n"
-               res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
-               res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
-               res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
-               res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
+               if deps.not_empty then
+                       res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
+                       res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
+                       res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
+                       res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
+               end
                res.add "<th data-field=\"mod\" data-sortable=\"true\">modules</th>\n"
                res.add "<th data-field=\"cla\" data-sortable=\"true\">classes</th>\n"
                res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
                res.add "<th data-field=\"loc\" data-sortable=\"true\">lines</th>\n"
                res.add "<th data-field=\"score\" data-sortable=\"true\">score</th>\n"
                res.add "</tr></thead>"
-               for p in mprojects do
+               for p in mpackages do
                        res.add "<tr>"
-                       res.add "<td><a href=\"{p.name}.html\">{p.name}</a></td>"
+                       res.add "<td><a href=\"p/{p.name}.html\">{p.name}</a></td>"
                        var maint = "?"
                        if p.maintainers.not_empty then maint = p.maintainers.first
                        res.add "<td>{maint}</td>"
                        res.add "<td>{p.contributors.length}</td>"
-                       res.add "<td>{deps[p].greaters.length-1}</td>"
-                       res.add "<td>{deps[p].direct_greaters.length}</td>"
-                       res.add "<td>{deps[p].smallers.length-1}</td>"
-                       res.add "<td>{deps[p].direct_smallers.length}</td>"
+                       if deps.not_empty then
+                               res.add "<td>{deps[p].greaters.length-1}</td>"
+                               res.add "<td>{deps[p].direct_greaters.length}</td>"
+                               res.add "<td>{deps[p].smallers.length-1}</td>"
+                               res.add "<td>{deps[p].direct_smallers.length}</td>"
+                       end
                        res.add "<td>{mmodules[p]}</td>"
                        res.add "<td>{mclasses[p]}</td>"
                        res.add "<td>{mmethods[p]}</td>"
@@ -600,6 +668,13 @@ end
 var model = new Model
 var tc = new ToolContext
 
+var opt_dir = new OptionString("Directory where the HTML files are generated", "-d", "--dir")
+var opt_no_git = new OptionBool("Do not gather git information from the working directory", "--no-git")
+var opt_no_parse = new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
+var opt_no_model = new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
+
+tc.option_context.add_option(opt_dir, opt_no_git, opt_no_parse, opt_no_model)
+
 tc.process_options(sys.args)
 tc.keep_going = true
 
@@ -607,37 +682,43 @@ var modelbuilder = new ModelBuilder(model, tc)
 var catalog = new Catalog(modelbuilder)
 
 # Get files or groups
-for a in tc.option_context.rest do
-       modelbuilder.get_mgroup(a)
-       modelbuilder.identify_file(a)
+var args = tc.option_context.rest
+if opt_no_parse.value then
+       modelbuilder.scan_full(args)
+else
+       modelbuilder.parse_full(args)
 end
 
-# Scan projects and compute information
-for p in model.mprojects do
+# Scan packages and compute information
+for p in model.mpackages do
        var g = p.root
        assert g != null
        modelbuilder.scan_group(g)
 
        # Load the module to process importation information
-       modelbuilder.parse_group(g)
+       if opt_no_parse.value then continue
 
        catalog.deps.add_node(p)
        for gg in p.mgroups do for m in gg.mmodules do
                for im in m.in_importation.direct_greaters do
-                       var ip = im.mproject
+                       var ip = im.mpackage
                        if ip == null or ip == p then continue
                        catalog.deps.add_edge(p, ip)
                end
        end
+end
 
+if not opt_no_git.value then for p in model.mpackages do
        catalog.git_info(p)
 end
 
 # Run phases to modelize classes and properties (so we can count them)
-#modelbuilder.run_phases
+if not opt_no_model.value then
+       modelbuilder.run_phases
+end
 
-var out = "out"
-out.mkdir
+var out = opt_dir.value or else "catalog.out"
+(out/"p").mkdir
 
 # Generate the css (hard coded)
 var css = """
@@ -741,31 +822,33 @@ css.write_to_file(out/"style.css")
 
 # PAGES
 
-for p in model.mprojects do
+for p in model.mpackages do
        # print p
-       var f = "{p.name}.html"
-       catalog.project_page(p).write_to_file(out/f)
+       var f = "p/{p.name}.html"
+       catalog.package_page(p).write_to_file(out/f)
 end
 
 # INDEX
 
-var index = new CatalogPage
-index.more_head.add "<title>Projects in Nit</title>"
+var index = new CatalogPage("")
+index.more_head.add "<title>Packages in Nit</title>"
 
 index.add """
 <div class="content">
-<h1>Projects in Nit</h1>
+<h1>Packages in Nit</h1>
 """
 
-index.add "<h2>Highlighted Projects</h2>\n"
+index.add "<h2>Highlighted Packages</h2>\n"
 index.add catalog.list_best(catalog.score)
 
-index.add "<h2>Most Required</h2>\n"
-var reqs = new Counter[MProject]
-for p in model.mprojects do
-       reqs[p] = catalog.deps[p].smallers.length - 1
+if catalog.deps.not_empty then
+       index.add "<h2>Most Required</h2>\n"
+       var reqs = new Counter[MPackage]
+       for p in model.mpackages do
+               reqs[p] = catalog.deps[p].smallers.length - 1
+       end
+       index.add catalog.list_best(reqs)
 end
-index.add catalog.list_best(reqs)
 
 index.add "<h2>By First Tag</h2>\n"
 index.add catalog.list_by(catalog.cat2proj, "cat_")
@@ -778,7 +861,7 @@ index.add """
 <div class="sidebar">
 <h3>Stats</h3>
 <ul class="box">
-<li>{{{model.mprojects.length}}} projects</li>
+<li>{{{model.mpackages.length}}} packages</li>
 <li>{{{catalog.maint2proj.length}}} maintainers</li>
 <li>{{{catalog.contrib2proj.length}}} contributors</li>
 <li>{{{catalog.tag2proj.length}}} tags</li>
@@ -794,7 +877,7 @@ index.write_to_file(out/"index.html")
 
 # PEOPLE
 
-var page = new CatalogPage
+var page = new CatalogPage("")
 page.more_head.add "<title>People of Nit</title>"
 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
 page.add "<h2>By Maintainer</h2>\n"
@@ -806,10 +889,10 @@ page.write_to_file(out/"people.html")
 
 # TABLE
 
-page = new CatalogPage
+page = new CatalogPage("")
 page.more_head.add "<title>Projets of Nit</title>"
 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
 page.add "<h2>Table of Projets</h2>\n"
-page.add catalog.table_projects(model.mprojects)
+page.add catalog.table_packages(model.mpackages)
 page.add "</div>\n"
 page.write_to_file(out/"table.html")