1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 # Basic catalog generator for Nit packages
17 # See: <http://nitlanguage.org/catalog/>
19 # The tool scans packages and generates the HTML files of a catalog.
23 # * [X] scan packages and their `.ini`
24 # * [X] generate lists of packages
25 # * [X] generate a page per package with the readme and most metadata
26 # * [ ] link/include/be included in the documentation
27 # * [ ] propose `related packages`
28 # * [X] show directory content (a la nitls)
29 # * [X] gather git information from the working directory
30 # * [ ] gather git information from the repository
31 # * [ ] gather package information from github
32 # * [ ] gather people information from github
34 # * [ ] separate information gathering from rendering
35 # * [ ] move up information gathering in (existing or new) service modules
36 # * [X] add command line options
37 # * [ ] harden HTML (escaping, path injection, etc)
38 # * [ ] nitcorn server with RESTful API
40 # ## Issues and limitations
42 # The tool works likee the other tools and expects to find valid Nit source code in the directories
44 # * cruft and temporary files will be collected
45 # * missing source file (e.g. not yet generated by nitcc) will make information
46 # incomplete (e.g. invalid module thus partial dependency and metrics)
48 # How to use the tool as the basis of a Nit code archive on the web usable with a package manager is not clear.
51 import loader
# Scan&load packages, groups and modules
52 import doc
::doc_down
# Display mdoc
53 import md5
# To get gravatar images
54 import counter
# For statistics
55 import modelize
# To process and count classes and methods
58 # Return the associated metadata from the `ini`, if any
59 fun metadata
(key
: String): nullable String
62 if ini
== null then return null
66 # The list of maintainers
67 var maintainers
= new Array[String]
69 # The list of contributors
70 var contributors
= new Array[String]
72 # The date of the most recent commit
73 var last_date
: nullable String = null
75 # The date of the oldest commit
76 var first_date
: nullable String = null
79 # A HTML page in a catalog
81 # This is just a template with the header pre-filled and the footer injected at rendering.
82 # Therefore, once instantiated, the content can just be added to it.
86 # The associated catalog, used to groups options and other global data
89 # Placeholder to include additional things before the `</head>`.
90 var more_head
= new Template
92 # Relative path to the root directory (with the index file).
94 # Use "" for pages in the root directory
95 # Use ".." for pages in a subdirectory
104 <meta charset="utf-8">
105 <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
106 <link rel="stylesheet" media="all" href="{{{rootpath / "style.css"}}}">
113 <div class='container-fluid'>
115 <nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>
116 <div class='container-fluid'>
117 <div class='navbar-header'>
118 <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='#topmenu-collapse'>
119 <span class='sr-only'>Toggle menu</span>
120 <span class='icon-bar'></span>
121 <span class='icon-bar'></span>
122 <span class='icon-bar'></span>
124 <span class='navbar-brand'><a href="http://nitlanguage.org/">Nitlanguage.org</a></span>
126 <div class='collapse navbar-collapse' id='topmenu-collapse'>
127 <ul class='nav navbar-nav'>
128 <li><a href="{{{rootpath / "index.html"}}}">Catalog</a></li>
137 # Inject piwik HTML code if required
138 private fun add_piwik
140 var tracker_url
= catalog
.piwik_tracker
141 if tracker_url
== null then return
143 var site_id
= catalog
.piwik_site_id
145 tracker_url
= tracker_url
.trim
146 if tracker_url
.chars
.last
!= '/' then tracker_url
+= "/"
149 <script type="text/javascript">
150 var _paq = _paq || [];
151 _paq.push(['trackPageView']);
152 _paq.push(['enableLinkTracking']);
154 var u=(("https:" == document.location.protocol) ? "https" : "http") + "://{{{tracker_url.escape_to_c}}}";
155 _paq.push(['setTrackerUrl', u+'piwik.php']);
156 _paq.push(['setSiteId', {{{site_id}}}]);
157 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
158 g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
162 <noscript><p><img src="http://{{{tracker_url.html_escape}}}piwik.php?idsite={{{site_id}}}" style="border:0;" alt="" /></p></noscript>
163 <!-- End Piwik Code -->
171 </div> <!-- container-fluid -->
172 <script src='https://code.jquery.com/jquery-latest.min.js'></script>
173 <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js'></script>
174 <script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'></script>
186 # Returns `log(self+1)`. Used to compute score of packages
187 fun score
: Float do return (self+1).to_f
.log
190 # The main class of the calatog generator that has the knowledge
194 # used to access the files and count source lines of code
195 var modelbuilder
: ModelBuilder
198 var tag2proj
= new MultiHashMap[String, MPackage]
200 # Packages by category
201 var cat2proj
= new MultiHashMap[String, MPackage]
203 # Packages by maintainer
204 var maint2proj
= new MultiHashMap[String, MPackage]
206 # Packages by contributors
207 var contrib2proj
= new MultiHashMap[String, MPackage]
209 # Dependency between packages
210 var deps
= new POSet[MPackage]
212 # Number of modules by package
213 var mmodules
= new Counter[MPackage]
215 # Number of classes by package
216 var mclasses
= new Counter[MPackage]
218 # Number of methods by package
219 var mmethods
= new Counter[MPackage]
221 # Number of line of code by package
222 var loc
= new Counter[MPackage]
224 # Number of commits by package
225 var commits
= new Counter[MPackage]
229 # The score is loosely computed using other metrics
230 var score
= new Counter[MPackage]
232 # Return a empty `CatalogPage`.
233 fun new_page
(rootpath
: String): CatalogPage
235 return new CatalogPage(self, rootpath
)
238 # Scan, register and add a contributor to a package
239 fun add_contrib
(person
: String, mpackage
: MPackage, res
: Template)
241 var projs
= contrib2proj
[person
]
242 if not projs
.has
(mpackage
) then projs
.add mpackage
247 # Regular expressions are broken, need to investigate.
250 #var re = "([^<(]*?)(<([^>]*?)>)?(\\((.*)\\))?".to_re
251 #var m = (person+" ").search(re)
252 #print "{person}: `{m or else "?"}` `{m[1] or else "?"}` `{m[3] or else "?"}` `{m[5] or else "?"}`"
254 var sp1
= person
.split_once_on
("<")
255 if sp1
.length
< 2 then
258 var sp2
= sp1
.last
.split_once_on
(">")
259 if sp2
.length
< 2 then
262 name
= sp1
.first
.trim
263 email
= sp2
.first
.trim
264 var sp3
= sp2
.last
.split_once_on
("(")
265 if sp3
.length
< 2 then
268 var sp4
= sp3
.last
.split_once_on
(")")
269 if sp4
.length
< 2 then
272 page
= sp4
.first
.trim
275 var e
= name
.html_escape
278 res
.add
"<a href=\"{page.html_escape}\
">"
280 if email
!= null then
281 # TODO get more things from github by using the email as a key
282 # "https://api.github.com/search/users?q={email}+in:email"
283 var md5
= email
.md5
.to_lower
284 res
.add
"<img src=\"https
://secure
.gravatar
.com
/avatar
/{md5}?size
=20&
;default
=retro\
"> "
287 if page
!= null then res
.add
"</a>"
291 # Recursively generate a level in the file tree of the *content* section
292 private fun gen_content_level
(ot
: OrderedTree[MConcern], os
: Array[Object], res
: Template)
300 if mdoc
!= null then d
= ": {mdoc.html_synopsis.write_to_string}"
301 res
.add
"<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
302 else if o
isa MModule then
305 if mdoc
!= null then d
= ": {mdoc.html_synopsis.write_to_string}"
306 res
.add
"<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
310 var subs
= ot
.sub
.get_or_null
(o
)
311 if subs
!= null then gen_content_level
(ot
, subs
, res
)
317 # Compute information and generate a full HTML page for a package
318 fun package_page
(mpackage
: MPackage): Writable
320 var res
= new_page
("..")
321 var score
= score
[mpackage
].to_f
322 var name
= mpackage
.name
.html_escape
323 res
.more_head
.add
"""<title>{{{name}}}</title>"""
326 <div class="content">
327 <h1 class="package-name">{{{name}}}</h1>
329 var mdoc
= mpackage
.mdoc_or_fallback
332 res
.add mdoc
.html_documentation
333 score
+= mdoc
.content
.length
.score
336 res
.add
"<h2>Content</h2>"
337 var ot
= new OrderedTree[MConcern]
338 for g
in mpackage
.mgroups
do
340 if g
.is_interesting
then
344 for mp
in g
.mmodules
do
348 ot
.sort_with
(alpha_comparator
)
349 gen_content_level
(ot
, ot
.roots
, res
)
354 <div class="sidebar">
357 var tryit
= mpackage
.metadata
("upstream.tryit")
358 if tryit
!= null then
360 var e
= tryit
.html_escape
361 res
.add
"<li><a href=\"{e}\
">Try<span style=\"color
:white\
">n</span>it!</a></li>\n"
363 var apk
= mpackage
.metadata
("upstream.apk")
366 var e
= apk
.html_escape
367 res
.add
"<li><a href=\"{e}\
">Android apk</a></li>\n"
370 res
.add
"""</ul>\n<ul class="box">\n"""
372 var homepage
= mpackage
.metadata
("upstream.homepage")
373 if homepage
!= null then
375 var e
= homepage
.html_escape
376 res
.add
"<li><a href=\"{e}\
">{e}</a></li>\n"
378 var maintainer
= mpackage
.metadata
("package.maintainer")
379 if maintainer
!= null then
381 add_contrib
(maintainer
, mpackage
, res
)
382 mpackage
.maintainers
.add maintainer
383 var projs
= maint2proj
[maintainer
]
384 if not projs
.has
(mpackage
) then projs
.add mpackage
386 var license
= mpackage
.metadata
("package.license")
387 if license
!= null then
389 var e
= license
.html_escape
390 res
.add
"<li><a href=\"http
://opensource
.org
/licenses
/{e}\
">{e}</a> license</li>\n"
394 res
.add
"<h3>Source Code</h3>\n<ul class=\"box\
">\n"
395 var browse
= mpackage
.metadata
("upstream.browse")
396 if browse
!= null then
398 var e
= browse
.html_escape
399 res
.add
"<li><a href=\"{e}\
">{e}</a></li>\n"
401 var git
= mpackage
.metadata
("upstream.git")
403 var e
= git
.html_escape
404 res
.add
"<li><tt>{e}</tt></li>\n"
406 var last_date
= mpackage
.last_date
407 if last_date
!= null then
408 var e
= last_date
.html_escape
409 res
.add
"<li>most recent commit: {e}</li>\n"
411 var first_date
= mpackage
.first_date
412 if first_date
!= null then
413 var e
= first_date
.html_escape
414 res
.add
"<li>oldest commit: {e}</li>\n"
416 var commits
= commits
[mpackage
]
418 res
.add
"<li>{commits} commits</li>\n"
422 res
.add
"<h3>Tags</h3>\n"
423 var tags
= mpackage
.metadata
("package.tags")
424 var ts
= new Array[String]
426 for t
in tags
.split
(",") do
428 if t
== "" then continue
432 if ts
.is_empty
then ts
.add
"none"
433 if tryit
!= null then ts
.add
"tryit"
434 if apk
!= null then ts
.add
"apk"
435 var ts2
= new Array[String]
437 tag2proj
[t
].add mpackage
439 ts2
.add
"<a href=\"../index
.html
#tag_{t}\">{t}</a>"
441 res
.add_list
(ts2
, ", ", ", ")
443 cat2proj
[cat
].add mpackage
444 score
+= ts
.length
.score
446 if deps
.has
(mpackage
) then
447 var reqs
= deps
[mpackage
].greaters
.to_a
448 reqs
.remove
(mpackage
)
449 alpha_comparator
.sort
(reqs
)
450 res
.add
"<h3>Requirements</h3>\n"
451 if reqs
.is_empty
then
454 var list
= new Array[String]
456 var direct
= deps
.has_direct_edge
(mpackage
, r
)
457 var s
= "<a href=\"{r}.html\
">"
458 if direct
then s
+= "<strong>"
460 if direct
then s
+= "</strong>"
464 res
.add_list
(list
, ", ", " and ")
467 reqs
= deps
[mpackage
].smallers
.to_a
468 reqs
.remove
(mpackage
)
469 alpha_comparator
.sort
(reqs
)
470 res
.add
"<h3>Clients</h3>\n"
471 if reqs
.is_empty
then
474 var list
= new Array[String]
476 var direct
= deps
.has_direct_edge
(r
, mpackage
)
477 var s
= "<a href=\"{r}.html\
">"
478 if direct
then s
+= "<strong>"
480 if direct
then s
+= "</strong>"
484 res
.add_list
(list
, ", ", " and ")
487 score
+= deps
[mpackage
].greaters
.length
.score
488 score
+= deps
[mpackage
].direct_greaters
.length
.score
489 score
+= deps
[mpackage
].smallers
.length
.score
490 score
+= deps
[mpackage
].direct_smallers
.length
.score
493 var contributors
= mpackage
.contributors
494 var more_contributors
= mpackage
.metadata
("package.more_contributors")
495 if more_contributors
!= null then
496 for c
in more_contributors
.split
(",") do
497 contributors
.add c
.trim
500 if not contributors
.is_empty
then
501 res
.add
"<h3>Contributors</h3>\n<ul class=\"box\
">"
502 for c
in contributors
do
503 add_contrib
(c
, mpackage
, res
)
507 score
+= contributors
.length
.to_f
513 for g
in mpackage
.mgroups
do
514 mmodules
+= g
.mmodules
.length
515 for m
in g
.mmodules
do
516 var am
= modelbuilder
.mmodule2node
(m
)
518 var file
= am
.location
.file
520 loc
+= file
.line_starts
.length
- 1
523 for cd
in m
.mclassdefs
do
525 for pd
in cd
.mpropdefs
do
526 if not pd
isa MMethodDef then continue
532 self.mmodules
[mpackage
] = mmodules
533 self.mclasses
[mpackage
] = mclasses
534 self.mmethods
[mpackage
] = mmethods
535 self.loc
[mpackage
] = loc
537 #score += mmodules.score
538 score
+= mclasses
.score
539 score
+= mmethods
.score
545 <li>{{{mmodules}}} modules</li>
546 <li>{{{mclasses}}} classes</li>
547 <li>{{{mmethods}}} methods</li>
548 <li>{{{loc}}} lines of code</li>
555 self.score
[mpackage
] = score
.to_i
560 # Return a short HTML sequence for a package
562 # Intended to use in lists.
563 fun li_package
(p
: MPackage): String
566 var f
= "p/{p.name}.html"
567 res
+= "<a href=\"{f}\
">{p}</a>"
568 var d
= p
.mdoc_or_fallback
569 if d
!= null then res
+= " - {d.html_synopsis.write_to_string}"
573 # List packages by group.
575 # For each key of the `map` a `<h3>` is generated.
576 # Each package is then listed.
578 # The list of keys is generated first to allow fast access to the correct `<h3>`.
579 # `id_prefix` is used to give an id to the `<h3>` element.
580 fun list_by
(map
: MultiHashMap[String, MPackage], id_prefix
: String): Template
582 var res
= new Template
583 var keys
= map
.keys
.to_a
584 alpha_comparator
.sort
(keys
)
585 var list
= [for x
in keys
do "<a href=\"#{id_prefix}{x.html_escape}\">{x.html_escape}</a>"]
586 res
.add_list
(list
, ", ", " and ")
589 var projs
= map
[k
].to_a
590 alpha_comparator
.sort
(projs
)
591 var e
= k
.html_escape
592 res
.add
"<h3 id=\"{id_prefix}{e}\
">{e} ({projs.length})</h3>\n<ul>\n"
595 res
.add li_package
(p
)
603 # List the 10 best packages from `cpt`
604 fun list_best
(cpt
: Counter[MPackage]): Template
606 var res
= new Template
610 if i
> best
.length
then break
611 var p
= best
[best
.length-i
]
613 res
.add li_package
(p
)
614 # res.add " ({cpt[p]})"
621 # Collect more information on a package using the `git` tool.
622 fun git_info
(mpackage
: MPackage)
624 var ini
= mpackage
.ini
625 if ini
== null then return
627 # TODO use real git info
628 #var repo = ini.get_or_null("upstream.git")
629 #var branch = ini.get_or_null("upstream.git.branch")
630 #var directory = ini.get_or_null("upstream.git.directory")
632 var dirpath
= mpackage
.root
.filepath
633 if dirpath
== null then return
635 # Collect commits info
636 var res
= git_run
("log", "--no-merges", "--follow", "--pretty=tformat:%ad;%aN <%aE>", "--", dirpath
)
637 var contributors
= new Counter[String]
638 var commits
= res
.split
("\n")
639 if commits
.not_empty
and commits
.last
== "" then commits
.pop
640 self.commits
[mpackage
] = commits
.length
642 var s
= l
.split_once_on
(';')
643 if s
.length
!= 2 or s
.last
== "" then continue
645 # Collect date of last and first commit
646 if mpackage
.last_date
== null then mpackage
.last_date
= s
.first
647 mpackage
.first_date
= s
.first
650 contributors
.inc
(s
.last
)
652 for c
in contributors
.sort
.reverse_iterator
do
653 mpackage
.contributors
.add c
658 # Produce a HTML table containig information on the packages
660 # `package_page` must have been called before so that information is computed.
661 fun table_packages
(mpackages
: Array[MPackage]): Template
663 alpha_comparator
.sort
(mpackages
)
664 var res
= new Template
665 res
.add
"<table data-toggle=\"table\
" data-sort-name=\"name\
" data-sort-order=\"desc\
" width=\"100%\
">\n"
666 res
.add
"<thead><tr>\n"
667 res
.add
"<th data-field=\"name\
" data-sortable=\"true\
">name</th>\n"
668 res
.add
"<th data-field=\"maint\
" data-sortable=\"true\
">maint</th>\n"
669 res
.add
"<th data-field=\"contrib\
" data-sortable=\"true\
">contrib</th>\n"
670 if deps
.not_empty
then
671 res
.add
"<th data-field=\"reqs\
" data-sortable=\"true\
">reqs</th>\n"
672 res
.add
"<th data-field=\"dreqs\
" data-sortable=\"true\
">direct<br>reqs</th>\n"
673 res
.add
"<th data-field=\"cli\
" data-sortable=\"true\
">clients</th>\n"
674 res
.add
"<th data-field=\"dcli\
" data-sortable=\"true\
">direct<br>clients</th>\n"
676 res
.add
"<th data-field=\"mod\
" data-sortable=\"true\
">modules</th>\n"
677 res
.add
"<th data-field=\"cla\
" data-sortable=\"true\
">classes</th>\n"
678 res
.add
"<th data-field=\"met\
" data-sortable=\"true\
">methods</th>\n"
679 res
.add
"<th data-field=\"loc\
" data-sortable=\"true\
">lines</th>\n"
680 res
.add
"<th data-field=\"score\
" data-sortable=\"true\
">score</th>\n"
681 res
.add
"</tr></thead>"
682 for p
in mpackages
do
684 res
.add
"<td><a href=\"p
/{p.name}.html\
">{p.name}</a></td>"
686 if p
.maintainers
.not_empty
then maint
= p
.maintainers
.first
687 res
.add
"<td>{maint}</td>"
688 res
.add
"<td>{p.contributors.length}</td>"
689 if deps
.not_empty
then
690 res
.add
"<td>{deps[p].greaters.length-1}</td>"
691 res
.add
"<td>{deps[p].direct_greaters.length}</td>"
692 res
.add
"<td>{deps[p].smallers.length-1}</td>"
693 res
.add
"<td>{deps[p].direct_smallers.length}</td>"
695 res
.add
"<td>{mmodules[p]}</td>"
696 res
.add
"<td>{mclasses[p]}</td>"
697 res
.add
"<td>{mmethods[p]}</td>"
698 res
.add
"<td>{loc[p]}</td>"
699 res
.add
"<td>{score[p]}</td>"
706 # Piwik tracker URL, if any
707 var piwik_tracker
: nullable String = null
710 # Used when `piwik_tracker` is set
711 var piwik_site_id
: Int = 1
714 # Execute a git command and return the result
715 fun git_run
(command
: String...): String
717 # print "git {command.join(" ")}"
718 var p
= new ProcessReader("git", command
...)
725 var model
= new Model
726 var tc
= new ToolContext
728 var opt_dir
= new OptionString("Directory where the HTML files are generated", "-d", "--dir")
729 var opt_no_git
= new OptionBool("Do not gather git information from the working directory", "--no-git")
730 var opt_no_parse
= new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
731 var opt_no_model
= new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
734 # If you want to monitor your visitors.
735 var opt_piwik_tracker
= new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker")
736 # Piwik tracker site id.
737 var opt_piwik_site_id
= new OptionString("Piwik site ID", "--piwik-site-id")
739 tc
.option_context
.add_option
(opt_dir
, opt_no_git
, opt_no_parse
, opt_no_model
, opt_piwik_tracker
, opt_piwik_site_id
)
741 tc
.process_options
(sys
.args
)
744 var modelbuilder
= new ModelBuilder(model
, tc
)
745 var catalog
= new Catalog(modelbuilder
)
747 catalog
.piwik_tracker
= opt_piwik_tracker
.value
748 var piwik_site_id
= opt_piwik_site_id
.value
749 if piwik_site_id
!= null then
750 if catalog
.piwik_tracker
== null then
751 print_error
"Warning: ignored `{opt_piwik_site_id}` because `{opt_piwik_tracker}` is not set."
752 else if piwik_site_id
.is_int
then
753 print_error
"Warning: ignored `{opt_piwik_site_id}`, an integer is required."
755 catalog
.piwik_site_id
= piwik_site_id
.to_i
760 # Get files or groups
761 var args
= tc
.option_context
.rest
762 if opt_no_parse
.value
then
763 modelbuilder
.scan_full
(args
)
765 modelbuilder
.parse_full
(args
)
768 # Scan packages and compute information
769 for p
in model
.mpackages
do
772 modelbuilder
.scan_group
(g
)
774 # Load the module to process importation information
775 if opt_no_parse
.value
then continue
777 catalog
.deps
.add_node
(p
)
778 for gg
in p
.mgroups
do for m
in gg
.mmodules
do
779 for im
in m
.in_importation
.direct_greaters
do
781 if ip
== null or ip
== p
then continue
782 catalog
.deps
.add_edge
(p
, ip
)
787 if not opt_no_git
.value
then for p
in model
.mpackages
do
791 # Run phases to modelize classes and properties (so we can count them)
792 if not opt_no_model
.value
then
793 modelbuilder
.run_phases
796 var out
= opt_dir
.value
or else "catalog.out"
799 # Generate the css (hard coded)
803 background-color: #f8f8f8;
808 text-decoration: none;
813 text-decoration: none;
825 border-bottom: solid 3px #CCC;
831 border-bottom: solid 1px #CCC;
835 list-style-type: square;
845 border: 1px solid #CCC;
846 font-family: Monospace;
848 background-color: rgb(250, 250, 250);
852 font-family: Monospace;
892 text-overflow: ellipsis;
894 border-bottom: 1px solid rgba(0,0,0,0.2);
897 css
.write_to_file
(out
/"style.css")
901 for p
in model
.mpackages
do
903 var f
= "p/{p.name}.html"
904 catalog
.package_page
(p
).write_to_file
(out
/f
)
909 var index
= catalog
.new_page
("")
910 index
.more_head
.add
"<title>Packages in Nit</title>"
913 <div class="content">
914 <h1>Packages in Nit</h1>
917 index
.add
"<h2>Highlighted Packages</h2>\n"
918 index
.add catalog
.list_best
(catalog
.score
)
920 if catalog
.deps
.not_empty
then
921 index
.add
"<h2>Most Required</h2>\n"
922 var reqs
= new Counter[MPackage]
923 for p
in model
.mpackages
do
924 reqs
[p
] = catalog
.deps
[p
].smallers
.length
- 1
926 index
.add catalog
.list_best
(reqs
)
929 index
.add
"<h2>By First Tag</h2>\n"
930 index
.add catalog
.list_by
(catalog
.cat2proj
, "cat_")
932 index
.add
"<h2>By Any Tag</h2>\n"
933 index
.add catalog
.list_by
(catalog
.tag2proj
, "tag_")
937 <div class="sidebar">
940 <li>{{{model.mpackages.length}}} packages</li>
941 <li>{{{catalog.maint2proj.length}}} maintainers</li>
942 <li>{{{catalog.contrib2proj.length}}} contributors</li>
943 <li>{{{catalog.tag2proj.length}}} tags</li>
944 <li>{{{catalog.mmodules.sum}}} modules</li>
945 <li>{{{catalog.mclasses.sum}}} classes</li>
946 <li>{{{catalog.mmethods.sum}}} methods</li>
947 <li>{{{catalog.loc.sum}}} lines of code</li>
952 index
.write_to_file
(out
/"index.html")
956 var page
= catalog
.new_page
("")
957 page
.more_head
.add
"<title>People of Nit</title>"
958 page
.add
"""<div class="content">\n<h1>People of Nit</h1>\n"""
959 page
.add
"<h2>By Maintainer</h2>\n"
960 page
.add catalog
.list_by
(catalog
.maint2proj
, "maint_")
961 page
.add
"<h2>By Contributor</h2>\n"
962 page
.add catalog
.list_by
(catalog
.contrib2proj
, "contrib_")
964 page
.write_to_file
(out
/"people.html")
968 page
= catalog
.new_page
("")
969 page
.more_head
.add
"<title>Projets of Nit</title>"
970 page
.add
"""<div class="content">\n<h1>People of Nit</h1>\n"""
971 page
.add
"<h2>Table of Projets</h2>\n"
972 page
.add catalog
.table_packages
(model
.mpackages
)
974 page
.write_to_file
(out
/"table.html")