src/doc/commands: merge `doc_down` intro `templates_html`
[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 templates_html
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.has(mpackage) then
344 var reqs = deps[mpackage].greaters.to_a
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_direct_edge(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[mpackage].smallers.to_a
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_direct_edge(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.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.not_empty then
507 res.add "<td>{deps[p].greaters.length-1}</td>"
508 res.add "<td>{deps[p].direct_greaters.length}</td>"
509 res.add "<td>{deps[p].smallers.length-1}</td>"
510 res.add "<td>{deps[p].direct_smallers.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 var model = new Model
536 var tc = new ToolContext
537
538 var opt_dir = new OptionString("Directory where the HTML files are generated", "-d", "--dir")
539 var opt_no_git = new OptionBool("Do not gather git information from the working directory", "--no-git")
540 var opt_no_parse = new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
541 var opt_no_model = new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
542
543 # Piwik tracker URL.
544 # If you want to monitor your visitors.
545 var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker")
546 # Piwik tracker site id.
547 var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
548
549 tc.option_context.add_option(opt_dir, opt_no_git, opt_no_parse, opt_no_model, opt_piwik_tracker, opt_piwik_site_id)
550
551 tc.process_options(sys.args)
552 tc.keep_going = true
553
554 var modelbuilder = new ModelBuilder(model, tc)
555 var catalog = new Catalog(modelbuilder)
556
557 catalog.piwik_tracker = opt_piwik_tracker.value
558 var piwik_site_id = opt_piwik_site_id.value
559 if piwik_site_id != null then
560 if catalog.piwik_tracker == null then
561 print_error "Warning: ignored `{opt_piwik_site_id}` because `{opt_piwik_tracker}` is not set."
562 else if piwik_site_id.is_int then
563 print_error "Warning: ignored `{opt_piwik_site_id}`, an integer is required."
564 else
565 catalog.piwik_site_id = piwik_site_id.to_i
566 end
567 end
568
569
570 # Get files or groups
571 var args = tc.option_context.rest
572 var mmodules
573 if opt_no_parse.value then
574 mmodules = modelbuilder.scan_full(args)
575 else
576 mmodules = modelbuilder.parse_full(args)
577 end
578 var mpackages = new Set[MPackage]
579 for m in mmodules do
580 var p = m.mpackage
581 if p != null then mpackages.add p
582 end
583
584 # Scan packages and compute information
585 for p in model.mpackages do
586 var g = p.root
587 assert g != null
588 modelbuilder.scan_group(g)
589
590 # Load the module to process importation information
591 if opt_no_parse.value then continue
592
593 catalog.deps.add_node(p)
594 for gg in p.mgroups do for m in gg.mmodules do
595 for im in m.in_importation.direct_greaters do
596 var ip = im.mpackage
597 if ip == null or ip == p then continue
598 catalog.deps.add_edge(p, ip)
599 end
600 end
601 end
602
603 if not opt_no_git.value then for p in mpackages do
604 catalog.git_info(p)
605 end
606
607 # Run phases to modelize classes and properties (so we can count them)
608 if not opt_no_model.value then
609 modelbuilder.run_phases
610 end
611
612 var out = opt_dir.value or else "catalog.out"
613 (out/"p").mkdir
614 (out/"res").mkdir
615
616 catalog.outdir = out
617
618 # Generate the css (hard coded)
619 var css = """
620 body {
621 margin-top: 15px;
622 background-color: #f8f8f8;
623 }
624
625 a {
626 color: #0D8921;
627 text-decoration: none;
628 }
629
630 a:hover {
631 color: #333;
632 text-decoration: none;
633 }
634
635 h1 {
636 font-weight: bold;
637 color: #0D8921;
638 font-size: 22px;
639 }
640
641 h2 {
642 color: #6C6C6C;
643 font-size: 18px;
644 border-bottom: solid 3px #CCC;
645 }
646
647 h3 {
648 color: #6C6C6C;
649 font-size: 15px;
650 border-bottom: solid 1px #CCC;
651 }
652
653 ul {
654 list-style-type: square;
655 }
656
657 dd {
658 color: #6C6C6C;
659 margin-top: 1em;
660 margin-bottom: 1em;
661 }
662
663 pre {
664 border: 1px solid #CCC;
665 font-family: Monospace;
666 color: #2d5003;
667 background-color: rgb(250, 250, 250);
668 }
669
670 code {
671 font-family: Monospace;
672 color: #2d5003;
673 }
674
675 footer {
676 margin-top: 20px;
677 }
678
679 .container {
680 margin: 0 auto;
681 padding: 0 20px;
682 }
683
684 .content {
685 float: left;
686 margin-top: 40px;
687 width: 65%;
688 }
689
690 .sidebar {
691 float: right;
692 margin-top: 40px;
693 width: 30%
694 }
695
696 .sidebar h3 {
697 color: #0D8921;
698 font-size: 18px;
699 border-bottom: 0px;
700 }
701
702 .box {
703 margin: 0;
704 padding: 0;
705 }
706
707 .box li {
708 line-height: 2.5;
709 white-space: nowrap;
710 overflow: hidden;
711 text-overflow: ellipsis;
712 padding-right: 10px;
713 border-bottom: 1px solid rgba(0,0,0,0.2);
714 }
715 """
716 css.write_to_file(out/"style.css")
717
718 # PAGES
719
720 for p in mpackages do
721 # print p
722 var f = "p/{p.name}.html"
723 catalog.package_page(p)
724 catalog.generate_page(p).write_to_file(out/f)
725 # copy ini
726 var ini = p.ini
727 if ini != null then ini.write_to_file(out/"p/{p.name}.ini")
728 end
729
730 # INDEX
731
732 var index = catalog.new_page("")
733 index.more_head.add "<title>Packages in Nit</title>"
734
735 index.add """
736 <div class="content">
737 <h1>Packages in Nit</h1>
738 """
739
740 index.add "<h2>Highlighted Packages</h2>\n"
741 index.add catalog.list_best(catalog.score)
742
743 if catalog.deps.not_empty then
744 index.add "<h2>Most Required</h2>\n"
745 var reqs = new Counter[MPackage]
746 for p in mpackages do
747 reqs[p] = catalog.deps[p].smallers.length - 1
748 end
749 index.add catalog.list_best(reqs)
750 end
751
752 index.add "<h2>By First Tag</h2>\n"
753 index.add catalog.list_by(catalog.cat2proj, "cat_")
754
755 index.add "<h2>By Any Tag</h2>\n"
756 index.add catalog.list_by(catalog.tag2proj, "tag_")
757
758 index.add """
759 </div>
760 <div class="sidebar">
761 <h3>Stats</h3>
762 <ul class="box">
763 <li>{{{mpackages.length}}} packages</li>
764 <li>{{{catalog.maint2proj.length}}} maintainers</li>
765 <li>{{{catalog.contrib2proj.length}}} contributors</li>
766 <li>{{{catalog.tag2proj.length}}} tags</li>
767 <li>{{{catalog.mmodules.sum}}} modules</li>
768 <li>{{{catalog.mclasses.sum}}} classes</li>
769 <li>{{{catalog.mmethods.sum}}} methods</li>
770 <li>{{{catalog.loc.sum}}} lines of code</li>
771 </ul>
772 </div>
773 """
774
775 index.write_to_file(out/"index.html")
776
777 # PEOPLE
778
779 var page = catalog.new_page("")
780 page.more_head.add "<title>People of Nit</title>"
781 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
782 page.add "<h2>By Maintainer</h2>\n"
783 page.add catalog.list_by(catalog.maint2proj, "maint_")
784 page.add "<h2>By Contributor</h2>\n"
785 page.add catalog.list_by(catalog.contrib2proj, "contrib_")
786 page.add "</div>\n"
787 page.write_to_file(out/"people.html")
788
789 # TABLE
790
791 page = catalog.new_page("")
792 page.more_head.add "<title>Projets of Nit</title>"
793 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
794 page.add "<h2>Table of Projets</h2>\n"
795 page.add catalog.table_packages(mpackages.to_a)
796 page.add "</div>\n"
797 page.write_to_file(out/"table.html")