Merge: doc: fixed some typos and other misc. corrections
[nit.git] / src / nitcatalog.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
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
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 # Basic catalog generator for Nit packages
16 #
17 # See: <http://nitlanguage.org/catalog/>
18 #
19 # The tool scans packages and generates the HTML files of a catalog.
20 #
21 # See `catalog` for details
22 module nitcatalog
23
24 import loader # Scan&load packages, groups and modules
25 import catalog
26
27 import doc::templates::html_model
28
29 # A HTML page in a catalog
30 #
31 # This is just a template with the header pre-filled and the footer injected at rendering.
32 # Therefore, once instantiated, the content can just be added to it.
33 class CatalogPage
34 super Template
35
36 # The associated catalog, used to groups options and other global data
37 var catalog: Catalog
38
39 # Placeholder to include additional things before the `</head>`.
40 var more_head = new Template
41
42 # Relative path to the root directory (with the index file).
43 #
44 # Use "" for pages in the root directory
45 # Use ".." for pages in a subdirectory
46 var rootpath: String
47
48 redef init
49 do
50 add """
51 <!DOCTYPE html>
52 <html>
53 <head>
54 <meta charset="utf-8">
55 <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
56 <link rel="stylesheet" media="all" href="{{{rootpath / "style.css"}}}">
57 """
58 add more_head
59
60 add """
61 </head>
62 <body>
63 <div class='container-fluid'>
64 <div class='row'>
65 <nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>
66 <div class='container-fluid'>
67 <div class='navbar-header'>
68 <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='#topmenu-collapse'>
69 <span class='sr-only'>Toggle menu</span>
70 <span class='icon-bar'></span>
71 <span class='icon-bar'></span>
72 <span class='icon-bar'></span>
73 </button>
74 <span class='navbar-brand'><a href="http://nitlanguage.org/">Nitlanguage.org</a></span>
75 </div>
76 <div class='collapse navbar-collapse' id='topmenu-collapse'>
77 <ul class='nav navbar-nav'>
78 <li><a href="{{{rootpath / "index.html"}}}">Catalog</a></li>
79 </ul>
80 </div>
81 </div>
82 </nav>
83 </div>
84 """
85 end
86
87 # Inject piwik HTML code if required
88 private fun add_piwik
89 do
90 var tracker_url = catalog.piwik_tracker
91 if tracker_url == null then return
92
93 var site_id = catalog.piwik_site_id
94
95 tracker_url = tracker_url.trim
96 if tracker_url.chars.last != '/' then tracker_url += "/"
97 add """
98 <!-- Piwik -->
99 <script type="text/javascript">
100 var _paq = _paq || [];
101 _paq.push(['trackPageView']);
102 _paq.push(['enableLinkTracking']);
103 (function() {
104 var u=(("https:" == document.location.protocol) ? "https" : "http") + "://{{{tracker_url.escape_to_c}}}";
105 _paq.push(['setTrackerUrl', u+'piwik.php']);
106 _paq.push(['setSiteId', {{{site_id}}}]);
107 var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
108 g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
109 })();
110
111 </script>
112 <noscript><p><img src="http://{{{tracker_url.html_escape}}}piwik.php?idsite={{{site_id}}}" style="border:0;" alt="" /></p></noscript>
113 <!-- End Piwik Code -->
114 """
115
116 end
117
118 redef fun rendering
119 do
120 add """
121 </div> <!-- container-fluid -->
122 <script src='https://code.jquery.com/jquery-latest.min.js'></script>
123 <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js'></script>
124 <script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'></script>
125 """
126 add_piwik
127 add """
128
129 </body>
130 </html>
131 """
132 end
133 end
134
135 redef class NitdocDecorator
136 redef fun add_image(v, link, name, comment)
137 do
138 # Keep absolute links as is
139 if link.has_prefix("http://") or link.has_prefix("https://") then
140 super
141 return
142 end
143
144 do
145 # Get the directory of the doc object to deal with the relative link
146 var mdoc = current_mdoc
147 if mdoc == null then break
148 var source = mdoc.location.file
149 if source == null then break
150 var path = source.filename
151 var stat = path.file_stat
152 if stat == null then break
153 if not stat.is_dir then path = path.dirname
154
155 # Get the full path to the local resource
156 var fulllink = path / link.to_s
157 stat = fulllink.file_stat
158 if stat == null then break
159
160 # Get a collision-free catalog name for the resource
161 var hash = fulllink.md5
162 var ext = fulllink.file_extension
163 if ext != null then hash = hash + "." + ext
164
165 # Copy the local resource in the resource directory of the catalog
166 var res = catalog.outdir / "res" / hash
167 fulllink.file_copy_to(res)
168
169 # Hijack the link in the html.
170 link = ".." / "res" / hash
171 super(v, link, name, comment)
172 return
173 end
174
175 # Something went bad
176 catalog.modelbuilder.toolcontext.error(current_mdoc.location, "Error: cannot find local image `{link}`")
177 super
178 end
179
180 # The registered catalog
181 #
182 # It is used to deal with relative links in images.
183 var catalog: Catalog is noautoinit
184 end
185
186 redef class Catalog
187 redef init
188 do
189 # Register `self` to the global NitdocDecorator
190 # FIXME this is ugly. But no better idea at the moment.
191 modelbuilder.model.nitdoc_md_processor.decorator.as(NitdocDecorator).catalog = self
192 end
193
194 # The output directory where to generate pages
195 var outdir: String is noautoinit
196
197 # Return a empty `CatalogPage`.
198 fun new_page(rootpath: String): CatalogPage
199 do
200 return new CatalogPage(self, rootpath)
201 end
202
203 # Recursively generate a level in the file tree of the *content* section
204 private fun gen_content_level(ot: OrderedTree[MConcern], os: Array[Object], res: Template)
205 do
206 res.add "<ul>\n"
207 for o in os do
208 res.add "<li>"
209 if o isa MGroup then
210 var d = ""
211 var mdoc = o.mdoc
212 if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
213 res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
214 else if o isa MModule then
215 var d = ""
216 var mdoc = o.mdoc
217 if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
218 res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
219 else
220 abort
221 end
222 var subs = ot.sub.get_or_null(o)
223 if subs != null then gen_content_level(ot, subs, res)
224 res.add "</li>\n"
225 end
226 res.add "</ul>\n"
227 end
228
229 # Generate a full HTML page for a package
230 fun generate_page(mpackage: MPackage): Writable
231 do
232 var res = new_page("..")
233 var name = mpackage.name.html_escape
234 res.more_head.add """<title>{{{name}}}</title>"""
235
236 res.add """<div class="content">"""
237
238 var mdoc = mpackage.mdoc_or_fallback
239 if mdoc == null then
240 res.add """<h1 class="package-name">{{{name}}}</h1>"""
241 else
242 res.add """
243 <div style="float: left">
244 <h1 class="package-name">{{{name}}}&nbsp;-&nbsp;</h1>
245 </div>
246 """
247 res.add mdoc.html_documentation
248 end
249
250 res.add "<h2>Content</h2>"
251 var ot = new OrderedTree[MConcern]
252 for g in mpackage.mgroups do
253 var pa = g.parent
254 if g.is_interesting then
255 ot.add(pa, g)
256 pa = g
257 end
258 for mp in g.mmodules do
259 ot.add(pa, mp)
260 end
261 end
262 ot.sort_with(alpha_comparator)
263 gen_content_level(ot, ot.roots, res)
264
265
266 res.add """
267 </div>
268 <div class="sidebar">
269 <ul class="box">
270 """
271 var tryit = mpackage.metadata.metadata("upstream.tryit")
272 if tryit != null then
273 var e = tryit.html_escape
274 res.add "<li><a href=\"{e}\">Try<span style=\"color:white\">n</span>it!</a></li>\n"
275 end
276 var apk = mpackage.metadata.metadata("upstream.apk")
277 if apk != null then
278 var e = apk.html_escape
279 res.add "<li><a href=\"{e}\">Android apk</a></li>\n"
280 end
281
282 res.add """</ul>\n<ul class="box">\n"""
283
284 var homepage = mpackage.metadata.metadata("upstream.homepage")
285 if homepage != null then
286 var e = homepage.html_escape
287 res.add "<li><a href=\"{e}\">{e}</a></li>\n"
288 end
289 for maintainer in mpackage.metadata.maintainers do
290 res.add "<li>{maintainer.to_html}</li>"
291 end
292 var license = mpackage.metadata.metadata("package.license")
293 if license != null then
294 var e = license.html_escape
295 res.add "<li><a href=\"http://opensource.org/licenses/{e}\">{e}</a> license</li>\n"
296 end
297 res.add "</ul>\n"
298
299 res.add "<h3>Source Code</h3>\n<ul class=\"box\">\n"
300 var browse = mpackage.metadata.metadata("upstream.browse")
301 if browse != null then
302 var e = browse.html_escape
303 res.add "<li><a href=\"{e}\">{e}</a></li>\n"
304 end
305 var git = mpackage.metadata.metadata("upstream.git")
306 if git != null then
307 var e = git.html_escape
308 res.add "<li><tt>{e}</tt></li>\n"
309 end
310 var last_date = mpackage.metadata.last_date
311 if last_date != null then
312 var e = last_date.html_escape
313 res.add "<li>most recent commit: {e}</li>\n"
314 end
315 var first_date = mpackage.metadata.first_date
316 if first_date != null then
317 var e = first_date.html_escape
318 res.add "<li>oldest commit: {e}</li>\n"
319 end
320 var commits = commits[mpackage]
321 if commits != 0 then
322 res.add "<li>{commits} commits</li>\n"
323 end
324 res.add "</ul>\n"
325
326 res.add "<h3>Quality</h3>\n<ul class=\"box\">\n"
327 var errors = errors[mpackage]
328 if errors > 0 then
329 res.add "<li>{errors} errors</li>\n"
330 end
331 res.add "<li>{warnings[mpackage]} warnings ({warnings_per_kloc[mpackage]}/kloc)</li>\n"
332 res.add "<li>{documentation_score[mpackage]}% documented</li>\n"
333 res.add "</ul>\n"
334
335 res.add "<h3>Tags</h3>\n"
336 var ts2 = new Array[String]
337 for t in mpackage.metadata.tags do
338 t = t.html_escape
339 ts2.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
340 end
341 res.add_list(ts2, ", ", ", ")
342
343 if deps.vertices.has(mpackage) then
344 var reqs = deps.get_all_successors(mpackage)
345 reqs.remove(mpackage)
346 alpha_comparator.sort(reqs)
347 res.add "<h3>Requirements</h3>\n"
348 if reqs.is_empty then
349 res.add "none"
350 else
351 var list = new Array[String]
352 for r in reqs do
353 var direct = deps.has_arc(mpackage, r)
354 var s = "<a href=\"{r}.html\">"
355 if direct then s += "<strong>"
356 s += r.to_s
357 if direct then s += "</strong>"
358 s += "</a>"
359 list.add s
360 end
361 res.add_list(list, ", ", " and ")
362 end
363
364 reqs = deps.get_all_predecessors(mpackage)
365 reqs.remove(mpackage)
366 alpha_comparator.sort(reqs)
367 res.add "<h3>Clients</h3>\n"
368 if reqs.is_empty then
369 res.add "none"
370 else
371 var list = new Array[String]
372 for r in reqs do
373 var direct = deps.has_arc(r, mpackage)
374 var s = "<a href=\"{r}.html\">"
375 if direct then s += "<strong>"
376 s += r.to_s
377 if direct then s += "</strong>"
378 s += "</a>"
379 list.add s
380 end
381 res.add_list(list, ", ", " and ")
382 end
383 end
384
385 var contributors = mpackage.metadata.contributors
386 if not contributors.is_empty then
387 res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
388 for c in contributors do
389 res.add "<li>{c.to_html}</li>"
390 end
391 res.add "</ul>"
392 end
393
394 res.add """
395 <h3>Stats</h3>
396 <ul class="box">
397 <li>{{{mmodules[mpackage]}}} modules</li>
398 <li>{{{mclasses[mpackage]}}} classes</li>
399 <li>{{{mmethods[mpackage]}}} methods</li>
400 <li>{{{loc[mpackage]}}} lines of code</li>
401 </ul>
402 """
403
404 res.add """
405 </div>
406 """
407 return res
408 end
409
410 # Return a short HTML sequence for a package
411 #
412 # Intended to use in lists.
413 fun li_package(p: MPackage): String
414 do
415 var res = ""
416 var f = "p/{p.name}.html"
417 res += "<a href=\"{f}\">{p}</a>"
418 var d = p.mdoc_or_fallback
419 if d != null then res += " - {d.html_synopsis.write_to_string}"
420 return res
421 end
422
423 # List packages by group.
424 #
425 # For each key of the `map` a `<h3>` is generated.
426 # Each package is then listed.
427 #
428 # The list of keys is generated first to allow fast access to the correct `<h3>`.
429 # `id_prefix` is used to give an id to the `<h3>` element.
430 fun list_by(map: MultiHashMap[Object, MPackage], id_prefix: String): Template
431 do
432 var res = new Template
433 var keys = map.keys.to_a
434 alpha_comparator.sort(keys)
435 var list = [for x in keys do "<a href=\"#{id_prefix}{x.to_s.html_escape}\">{x.to_s.html_escape}</a>"]
436 res.add_list(list, ", ", " and ")
437
438 for k in keys do
439 var projs = map[k].to_a
440 alpha_comparator.sort(projs)
441 var e = k.to_s.html_escape
442 res.add "<h3 id=\"{id_prefix}{e}\">{e} ({projs.length})</h3>\n<ul>\n"
443 for p in projs do
444 res.add "<li>"
445 res.add li_package(p)
446 res.add "</li>"
447 end
448 res.add "</ul>"
449 end
450 return res
451 end
452
453 # List the 10 best packages from `cpt`
454 fun list_best(cpt: Counter[MPackage]): Template
455 do
456 var res = new Template
457 res.add "<ul>"
458 var best = cpt.sort
459 for i in [1..10] do
460 if i > best.length then break
461 var p = best[best.length-i]
462 res.add "<li>"
463 res.add li_package(p)
464 # res.add " ({cpt[p]})"
465 res.add "</li>"
466 end
467 res.add "</ul>"
468 return res
469 end
470
471 # Produce a HTML table containig information on the packages
472 #
473 # `package_page` must have been called before so that information is computed.
474 fun table_packages(mpackages: Array[MPackage]): Template
475 do
476 alpha_comparator.sort(mpackages)
477 var res = new Template
478 res.add "<table data-toggle=\"table\" data-sort-name=\"name\" data-sort-order=\"desc\" width=\"100%\">\n"
479 res.add "<thead><tr>\n"
480 res.add "<th data-field=\"name\" data-sortable=\"true\">name</th>\n"
481 res.add "<th data-field=\"maint\" data-sortable=\"true\">maint</th>\n"
482 res.add "<th data-field=\"contrib\" data-sortable=\"true\">contrib</th>\n"
483 if deps.vertices.not_empty then
484 res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
485 res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
486 res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
487 res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
488 end
489 res.add "<th data-field=\"mod\" data-sortable=\"true\">modules</th>\n"
490 res.add "<th data-field=\"cla\" data-sortable=\"true\">classes</th>\n"
491 res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
492 res.add "<th data-field=\"loc\" data-sortable=\"true\">lines</th>\n"
493 res.add "<th data-field=\"score\" data-sortable=\"true\">score</th>\n"
494 res.add "<th data-field=\"errors\" data-sortable=\"true\">errors</th>\n"
495 res.add "<th data-field=\"warnings\" data-sortable=\"true\">warnings</th>\n"
496 res.add "<th data-field=\"warnings_per_kloc\" data-sortable=\"true\">w/kloc</th>\n"
497 res.add "<th data-field=\"doc\" data-sortable=\"true\">doc</th>\n"
498 res.add "</tr></thead>"
499 for p in mpackages do
500 res.add "<tr>"
501 res.add "<td><a href=\"p/{p.name}.html\">{p.name}</a></td>"
502 var maint = "?"
503 if p.metadata.maintainers.not_empty then maint = p.metadata.maintainers.first.name.html_escape
504 res.add "<td>{maint}</td>"
505 res.add "<td>{p.metadata.contributors.length}</td>"
506 if deps.vertices.not_empty then
507 res.add "<td>{deps.get_all_successors(p).length-1}</td>"
508 res.add "<td>{deps.successors(p).length}</td>"
509 res.add "<td>{deps.get_all_predecessors(p).length-1}</td>"
510 res.add "<td>{deps.predecessors(p).length}</td>"
511 end
512 res.add "<td>{mmodules[p]}</td>"
513 res.add "<td>{mclasses[p]}</td>"
514 res.add "<td>{mmethods[p]}</td>"
515 res.add "<td>{loc[p]}</td>"
516 res.add "<td>{score[p]}</td>"
517 res.add "<td>{errors[p]}</td>"
518 res.add "<td>{warnings[p]}</td>"
519 res.add "<td>{warnings_per_kloc[p]}</td>"
520 res.add "<td>{documentation_score[p]}</td>"
521 res.add "</tr>\n"
522 end
523 res.add "</table>\n"
524 return res
525 end
526
527 # Piwik tracker URL, if any
528 var piwik_tracker: nullable String = null
529
530 # Piwik site ID
531 # Used when `piwik_tracker` is set
532 var piwik_site_id: Int = 1
533 end
534
535 redef class Person
536 redef fun to_html do
537 var res = ""
538 var e = name.html_escape
539 var page = self.page
540 if page != null then
541 res += "<a href=\"{page.html_escape}\">"
542 end
543 var gravatar = self.gravatar
544 if gravatar != null then
545 res += "<img src=\"https://secure.gravatar.com/avatar/{gravatar}?size=20&amp;default=retro\">&nbsp;"
546 end
547 res += e
548 if page != null then res += "</a>"
549 return res
550 end
551 end
552
553 var model = new Model
554 var tc = new ToolContext
555
556 var opt_dir = new OptionString("Directory where the HTML files are generated", "-d", "--dir")
557 var opt_no_git = new OptionBool("Do not gather git information from the working directory", "--no-git")
558 var opt_no_parse = new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
559 var opt_no_model = new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
560
561 # Piwik tracker URL.
562 # If you want to monitor your visitors.
563 var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker")
564 # Piwik tracker site id.
565 var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
566
567 tc.option_context.add_option(opt_dir, opt_no_git, opt_no_parse, opt_no_model, opt_piwik_tracker, opt_piwik_site_id)
568
569 tc.process_options(sys.args)
570 tc.keep_going = true
571
572 var modelbuilder = new ModelBuilder(model, tc)
573 var catalog = new Catalog(modelbuilder)
574
575 catalog.piwik_tracker = opt_piwik_tracker.value
576 var piwik_site_id = opt_piwik_site_id.value
577 if piwik_site_id != null then
578 if catalog.piwik_tracker == null then
579 print_error "Warning: ignored `{opt_piwik_site_id}` because `{opt_piwik_tracker}` is not set."
580 else if piwik_site_id.is_int then
581 print_error "Warning: ignored `{opt_piwik_site_id}`, an integer is required."
582 else
583 catalog.piwik_site_id = piwik_site_id.to_i
584 end
585 end
586
587
588 # Get files or groups
589 var args = tc.option_context.rest
590 var mmodules
591 if opt_no_parse.value then
592 mmodules = modelbuilder.scan_full(args)
593 else
594 mmodules = modelbuilder.parse_full(args)
595 end
596 var mpackages = modelbuilder.model.mpackage_importation_graph.vertices
597
598 # Scan packages and compute information
599 for p in mpackages do
600 var g = p.root
601 assert g != null
602 modelbuilder.scan_group(g)
603 end
604
605 if not opt_no_git.value then for p in mpackages do
606 catalog.git_info(p)
607 end
608
609 # Run phases to modelize classes and properties (so we can count them)
610 if not opt_no_model.value then
611 modelbuilder.run_phases
612 end
613
614 var out = opt_dir.value or else "catalog.out"
615 (out/"p").mkdir
616 (out/"res").mkdir
617
618 catalog.outdir = out
619
620 # Generate the css (hard coded)
621 var css = """
622 body {
623 margin-top: 15px;
624 background-color: #f8f8f8;
625 }
626
627 a {
628 color: #0D8921;
629 text-decoration: none;
630 }
631
632 a:hover {
633 color: #333;
634 text-decoration: none;
635 }
636
637 h1 {
638 font-weight: bold;
639 color: #0D8921;
640 font-size: 22px;
641 }
642
643 h2 {
644 color: #6C6C6C;
645 font-size: 18px;
646 border-bottom: solid 3px #CCC;
647 }
648
649 h3 {
650 color: #6C6C6C;
651 font-size: 15px;
652 border-bottom: solid 1px #CCC;
653 }
654
655 ul {
656 list-style-type: square;
657 }
658
659 dd {
660 color: #6C6C6C;
661 margin-top: 1em;
662 margin-bottom: 1em;
663 }
664
665 pre {
666 border: 1px solid #CCC;
667 font-family: Monospace;
668 color: #2d5003;
669 background-color: rgb(250, 250, 250);
670 }
671
672 code {
673 font-family: Monospace;
674 color: #2d5003;
675 }
676
677 footer {
678 margin-top: 20px;
679 }
680
681 .container {
682 margin: 0 auto;
683 padding: 0 20px;
684 }
685
686 .content {
687 float: left;
688 margin-top: 40px;
689 width: 65%;
690 }
691
692 .sidebar {
693 float: right;
694 margin-top: 40px;
695 width: 30%
696 }
697
698 .sidebar h3 {
699 color: #0D8921;
700 font-size: 18px;
701 border-bottom: 0px;
702 }
703
704 .box {
705 margin: 0;
706 padding: 0;
707 }
708
709 .box li {
710 line-height: 2.5;
711 white-space: nowrap;
712 overflow: hidden;
713 text-overflow: ellipsis;
714 padding-right: 10px;
715 border-bottom: 1px solid rgba(0,0,0,0.2);
716 }
717 """
718 css.write_to_file(out/"style.css")
719
720 # PAGES
721
722 for p in mpackages do
723 # print p
724 var f = "p/{p.name}.html"
725 catalog.package_page(p)
726 catalog.generate_page(p).write_to_file(out/f)
727 # copy ini
728 var ini = p.ini
729 if ini != null then ini.write_to_file(out/"p/{p.name}.ini")
730 end
731
732 # INDEX
733
734 var index = catalog.new_page("")
735 index.more_head.add "<title>Packages in Nit</title>"
736
737 index.add """
738 <div class="content">
739 <h1>Packages in Nit</h1>
740 """
741
742 index.add "<h2>Highlighted Packages</h2>\n"
743 index.add catalog.list_best(catalog.score)
744
745 if catalog.deps.vertices.not_empty then
746 index.add "<h2>Most Required</h2>\n"
747 var reqs = new Counter[MPackage]
748 for p in mpackages do
749 reqs[p] = catalog.deps.get_all_successors(p).length - 1
750 end
751 index.add catalog.list_best(reqs)
752 end
753
754 index.add "<h2>By First Tag</h2>\n"
755 index.add catalog.list_by(catalog.cat2proj, "cat_")
756
757 index.add "<h2>By Any Tag</h2>\n"
758 index.add catalog.list_by(catalog.tag2proj, "tag_")
759
760 index.add """
761 </div>
762 <div class="sidebar">
763 <h3>Stats</h3>
764 <ul class="box">
765 <li>{{{mpackages.length}}} packages</li>
766 <li>{{{catalog.maint2proj.length}}} maintainers</li>
767 <li>{{{catalog.contrib2proj.length}}} contributors</li>
768 <li>{{{catalog.tag2proj.length}}} tags</li>
769 <li>{{{catalog.mmodules.sum}}} modules</li>
770 <li>{{{catalog.mclasses.sum}}} classes</li>
771 <li>{{{catalog.mmethods.sum}}} methods</li>
772 <li>{{{catalog.loc.sum}}} lines of code</li>
773 </ul>
774 </div>
775 """
776
777 index.write_to_file(out/"index.html")
778
779 # PEOPLE
780
781 var page = catalog.new_page("")
782 page.more_head.add "<title>People of Nit</title>"
783 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
784 page.add "<h2>By Maintainer</h2>\n"
785 page.add catalog.list_by(catalog.maint2proj, "maint_")
786 page.add "<h2>By Contributor</h2>\n"
787 page.add catalog.list_by(catalog.contrib2proj, "contrib_")
788 page.add "</div>\n"
789 page.write_to_file(out/"people.html")
790
791 # TABLE
792
793 page = catalog.new_page("")
794 page.more_head.add "<title>Projets of Nit</title>"
795 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
796 page.add "<h2>Table of Projets</h2>\n"
797 page.add catalog.table_packages(mpackages.to_a)
798 page.add "</div>\n"
799 page.write_to_file(out/"table.html")