nitcatalog: add command-line options
[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 # ## Features
22 #
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
33 # * [ ] reify people
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
39 #
40 # ## Issues and limitations
41 #
42 # The tool works likee the other tools and expects to find valid Nit source code in the directories
43 #
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)
47 #
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.
49 module nitcatalog
50
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
56
57 redef class MPackage
58 # Return the associated metadata from the `ini`, if any
59 fun metadata(key: String): nullable String
60 do
61 var ini = self.ini
62 if ini == null then return null
63 return ini[key]
64 end
65
66 # The list of maintainers
67 var maintainers = new Array[String]
68
69 # The list of contributors
70 var contributors = new Array[String]
71
72 # The date of the most recent commit
73 var last_date: nullable String = null
74
75 # The date of the oldest commit
76 var first_date: nullable String = null
77 end
78
79 # A HTML page in a catalog
80 #
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.
83 class CatalogPage
84 super Template
85
86 # Placeholder to include additional things before the `</head>`.
87 var more_head = new Template
88
89 redef init
90 do
91 add """
92 <!DOCTYPE html>
93 <html>
94 <head>
95 <meta charset="utf-8">
96 <link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
97 <link rel="stylesheet" media="all" href="style.css">
98 """
99 add more_head
100
101 add """
102 </head>
103 <body>
104 <div class='container-fluid'>
105 <div class='row'>
106 <nav id='topmenu' class='navbar navbar-default navbar-fixed-top' role='navigation'>
107 <div class='container-fluid'>
108 <div class='navbar-header'>
109 <button type='button' class='navbar-toggle' data-toggle='collapse' data-target='#topmenu-collapse'>
110 <span class='sr-only'>Toggle menu</span>
111 <span class='icon-bar'></span>
112 <span class='icon-bar'></span>
113 <span class='icon-bar'></span>
114 </button>
115 <span class='navbar-brand'><a href="http://nitlanguage.org/">Nitlanguage.org</a></span>
116 </div>
117 <div class='collapse navbar-collapse' id='topmenu-collapse'>
118 <ul class='nav navbar-nav'>
119 <li><a href="index.html">Catalog</a></li>
120 </ul>
121 </div>
122 </div>
123 </nav>
124 </div>
125 """
126 end
127
128 redef fun rendering
129 do
130 add """
131 </div> <!-- container-fluid -->
132 <script src='https://code.jquery.com/jquery-latest.min.js'></script>
133 <script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js'></script>
134 <script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'></script>
135 </body>
136 </html>
137 """
138 end
139 end
140
141 redef class Int
142 # Returns `log(self+1)`. Used to compute score of packages
143 fun score: Float do return (self+1).to_f.log
144 end
145
146 # The main class of the calatog generator that has the knowledge
147 class Catalog
148
149 # The modelbuilder
150 # used to access the files and count source lines of code
151 var modelbuilder: ModelBuilder
152
153 # Packages by tag
154 var tag2proj = new MultiHashMap[String, MPackage]
155
156 # Packages by category
157 var cat2proj = new MultiHashMap[String, MPackage]
158
159 # Packages by maintainer
160 var maint2proj = new MultiHashMap[String, MPackage]
161
162 # Packages by contributors
163 var contrib2proj = new MultiHashMap[String, MPackage]
164
165 # Dependency between packages
166 var deps = new POSet[MPackage]
167
168 # Number of modules by package
169 var mmodules = new Counter[MPackage]
170
171 # Number of classes by package
172 var mclasses = new Counter[MPackage]
173
174 # Number of methods by package
175 var mmethods = new Counter[MPackage]
176
177 # Number of line of code by package
178 var loc = new Counter[MPackage]
179
180 # Number of commits by package
181 var commits = new Counter[MPackage]
182
183 # Score by package
184 #
185 # The score is loosely computed using other metrics
186 var score = new Counter[MPackage]
187
188 # Scan, register and add a contributor to a package
189 fun add_contrib(person: String, mpackage: MPackage, res: Template)
190 do
191 var projs = contrib2proj[person]
192 if not projs.has(mpackage) then projs.add mpackage
193 var name = person
194 var email = null
195 var page = null
196
197 # Regular expressions are broken, need to investigate.
198 # So split manually.
199 #
200 #var re = "([^<(]*?)(<([^>]*?)>)?(\\((.*)\\))?".to_re
201 #var m = (person+" ").search(re)
202 #print "{person}: `{m or else "?"}` `{m[1] or else "?"}` `{m[3] or else "?"}` `{m[5] or else "?"}`"
203 do
204 var sp1 = person.split_once_on("<")
205 if sp1.length < 2 then
206 break
207 end
208 var sp2 = sp1.last.split_once_on(">")
209 if sp2.length < 2 then
210 break
211 end
212 name = sp1.first.trim
213 email = sp2.first.trim
214 var sp3 = sp2.last.split_once_on("(")
215 if sp3.length < 2 then
216 break
217 end
218 var sp4 = sp3.last.split_once_on(")")
219 if sp4.length < 2 then
220 break
221 end
222 page = sp4.first.trim
223 end
224
225 var e = name.html_escape
226 res.add "<li>"
227 if page != null then
228 res.add "<a href=\"{page.html_escape}\">"
229 end
230 if email != null then
231 # TODO get more things from github by using the email as a key
232 # "https://api.github.com/search/users?q={email}+in:email"
233 var md5 = email.md5.to_lower
234 res.add "<img src=\"https://secure.gravatar.com/avatar/{md5}?size=20&amp;default=retro\">&nbsp;"
235 end
236 res.add "{e}"
237 if page != null then res.add "</a>"
238 res.add "</li>"
239 end
240
241 # Recursively generate a level in the file tree of the *content* section
242 private fun gen_content_level(ot: OrderedTree[Object], os: Array[Object], res: Template)
243 do
244 res.add "<ul>\n"
245 for o in os do
246 res.add "<li>"
247 if o isa MGroup then
248 var d = ""
249 var mdoc = o.mdoc
250 if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
251 res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
252 else if o isa ModulePath then
253 var d = ""
254 var m = o.mmodule
255 if m != null then
256 var mdoc = m.mdoc
257 if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
258 end
259 res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
260 else
261 abort
262 end
263 var subs = ot.sub.get_or_null(o)
264 if subs != null then gen_content_level(ot, subs, res)
265 res.add "</li>\n"
266 end
267 res.add "</ul>\n"
268 end
269
270 # Compute information and generate a full HTML page for a package
271 fun package_page(mpackage: MPackage): Writable
272 do
273 var res = new CatalogPage
274 var score = score[mpackage].to_f
275 var name = mpackage.name.html_escape
276 res.more_head.add """<title>{{{name}}}</title>"""
277
278 res.add """
279 <div class="content">
280 <h1 class="package-name">{{{name}}}</h1>
281 """
282 var mdoc = mpackage.mdoc_or_fallback
283 if mdoc != null then
284 score += 100.0
285 res.add mdoc.html_documentation
286 score += mdoc.content.length.score
287 end
288
289 res.add "<h2>Content</h2>"
290 var ot = new OrderedTree[Object]
291 for g in mpackage.mgroups do
292 var pa = g.parent
293 if g.is_interesting then
294 ot.add(pa, g)
295 pa = g
296 end
297 for mp in g.module_paths do
298 ot.add(pa, mp)
299 end
300 end
301 ot.sort_with(alpha_comparator)
302 gen_content_level(ot, ot.roots, res)
303
304
305 res.add """
306 </div>
307 <div class="sidebar">
308 <ul class="box">
309 """
310 var homepage = mpackage.metadata("upstream.homepage")
311 if homepage != null then
312 score += 5.0
313 var e = homepage.html_escape
314 res.add "<li><a href=\"{e}\">{e}</a></li>\n"
315 end
316 var maintainer = mpackage.metadata("package.maintainer")
317 if maintainer != null then
318 score += 5.0
319 add_contrib(maintainer, mpackage, res)
320 mpackage.maintainers.add maintainer
321 var projs = maint2proj[maintainer]
322 if not projs.has(mpackage) then projs.add mpackage
323 end
324 var license = mpackage.metadata("package.license")
325 if license != null then
326 score += 5.0
327 var e = license.html_escape
328 res.add "<li><a href=\"http://opensource.org/licenses/{e}\">{e}</a> license</li>\n"
329 end
330 res.add "</ul>\n"
331
332 res.add "<h3>Source Code</h3>\n<ul class=\"box\">\n"
333 var browse = mpackage.metadata("upstream.browse")
334 if browse != null then
335 score += 5.0
336 var e = browse.html_escape
337 res.add "<li><a href=\"{e}\">{e}</a></li>\n"
338 end
339 var git = mpackage.metadata("upstream.git")
340 if git != null then
341 var e = git.html_escape
342 res.add "<li><tt>{e}</tt></li>\n"
343 end
344 var last_date = mpackage.last_date
345 if last_date != null then
346 var e = last_date.html_escape
347 res.add "<li>most recent commit: {e}</li>\n"
348 end
349 var first_date = mpackage.first_date
350 if first_date != null then
351 var e = first_date.html_escape
352 res.add "<li>oldest commit: {e}</li>\n"
353 end
354 var commits = commits[mpackage]
355 if commits != 0 then
356 res.add "<li>{commits} commits</li>\n"
357 end
358 res.add "</ul>\n"
359
360 res.add "<h3>Tags</h3>\n"
361 var tags = mpackage.metadata("package.tags")
362 var ts2 = new Array[String]
363 var cat = null
364 if tags != null then
365 var ts = tags.split(",")
366 for t in ts do
367 t = t.trim
368 if t == "" then continue
369 if cat == null then cat = t
370 tag2proj[t].add mpackage
371 t = t.html_escape
372 ts2.add "<a href=\"index.html#tag_{t}\">{t}</a>"
373 end
374 res.add_list(ts2, ", ", ", ")
375 end
376 if ts2.is_empty then
377 var t = "none"
378 cat = t
379 tag2proj[t].add mpackage
380 res.add "<a href=\"index.html#tag_{t}\">{t}</a>"
381 end
382 if cat != null then cat2proj[cat].add mpackage
383 score += ts2.length.score
384
385 var reqs = deps[mpackage].greaters.to_a
386 reqs.remove(mpackage)
387 alpha_comparator.sort(reqs)
388 res.add "<h3>Requirements</h3>\n"
389 if reqs.is_empty then
390 res.add "none"
391 else
392 var list = new Array[String]
393 for r in reqs do
394 var direct = deps.has_direct_edge(mpackage, r)
395 var s = "<a href=\"{r}.html\">"
396 if direct then s += "<strong>"
397 s += r.to_s
398 if direct then s += "</strong>"
399 s += "</a>"
400 list.add s
401 end
402 res.add_list(list, ", ", " and ")
403 end
404
405 reqs = deps[mpackage].smallers.to_a
406 reqs.remove(mpackage)
407 alpha_comparator.sort(reqs)
408 res.add "<h3>Clients</h3>\n"
409 if reqs.is_empty then
410 res.add "none"
411 else
412 var list = new Array[String]
413 for r in reqs do
414 var direct = deps.has_direct_edge(r, mpackage)
415 var s = "<a href=\"{r}.html\">"
416 if direct then s += "<strong>"
417 s += r.to_s
418 if direct then s += "</strong>"
419 s += "</a>"
420 list.add s
421 end
422 res.add_list(list, ", ", " and ")
423 end
424
425 score += deps[mpackage].greaters.length.score
426 score += deps[mpackage].direct_greaters.length.score
427 score += deps[mpackage].smallers.length.score
428 score += deps[mpackage].direct_smallers.length.score
429
430 var contributors = mpackage.contributors
431 if not contributors.is_empty then
432 res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
433 for c in contributors do
434 add_contrib(c, mpackage, res)
435 end
436 res.add "</ul>"
437 end
438 score += contributors.length.to_f
439
440 var mmodules = 0
441 var mclasses = 0
442 var mmethods = 0
443 var loc = 0
444 for g in mpackage.mgroups do
445 mmodules += g.module_paths.length
446 for m in g.mmodules do
447 var am = modelbuilder.mmodule2node(m)
448 if am != null then
449 var file = am.location.file
450 if file != null then
451 loc += file.line_starts.length - 1
452 end
453 end
454 for cd in m.mclassdefs do
455 mclasses += 1
456 for pd in cd.mpropdefs do
457 if not pd isa MMethodDef then continue
458 mmethods += 1
459 end
460 end
461 end
462 end
463 self.mmodules[mpackage] = mmodules
464 self.mclasses[mpackage] = mclasses
465 self.mmethods[mpackage] = mmethods
466 self.loc[mpackage] = loc
467
468 #score += mmodules.score
469 score += mclasses.score
470 score += mmethods.score
471 score += loc.score
472
473 res.add """
474 <h3>Stats</h3>
475 <ul class="box">
476 <li>{{{mmodules}}} modules</li>
477 <li>{{{mclasses}}} classes</li>
478 <li>{{{mmethods}}} methods</li>
479 <li>{{{loc}}} lines of code</li>
480 </ul>
481 """
482
483 res.add """
484 </div>
485 """
486 self.score[mpackage] = score.to_i
487
488 return res
489 end
490
491 # Return a short HTML sequence for a package
492 #
493 # Intended to use in lists.
494 fun li_package(p: MPackage): String
495 do
496 var res = ""
497 var f = "{p.name}.html"
498 res += "<a href=\"{f}\">{p}</a>"
499 var d = p.mdoc_or_fallback
500 if d != null then res += " - {d.html_synopsis.write_to_string}"
501 return res
502 end
503
504 # List packages by group.
505 #
506 # For each key of the `map` a `<h3>` is generated.
507 # Each package is then listed.
508 #
509 # The list of keys is generated first to allow fast access to the correct `<h3>`.
510 # `id_prefix` is used to give an id to the `<h3>` element.
511 fun list_by(map: MultiHashMap[String, MPackage], id_prefix: String): Template
512 do
513 var res = new Template
514 var keys = map.keys.to_a
515 alpha_comparator.sort(keys)
516 var list = [for x in keys do "<a href=\"#{id_prefix}{x.html_escape}\">{x.html_escape}</a>"]
517 res.add_list(list, ", ", " and ")
518
519 for k in keys do
520 var projs = map[k].to_a
521 alpha_comparator.sort(projs)
522 var e = k.html_escape
523 res.add "<h3 id=\"{id_prefix}{e}\">{e} ({projs.length})</h3>\n<ul>\n"
524 for p in projs do
525 res.add "<li>"
526 res.add li_package(p)
527 res.add "</li>"
528 end
529 res.add "</ul>"
530 end
531 return res
532 end
533
534 # List the 10 best packages from `cpt`
535 fun list_best(cpt: Counter[MPackage]): Template
536 do
537 var res = new Template
538 res.add "<ul>"
539 var best = cpt.sort
540 for i in [1..10] do
541 if i > best.length then break
542 var p = best[best.length-i]
543 res.add "<li>"
544 res.add li_package(p)
545 # res.add " ({cpt[p]})"
546 res.add "</li>"
547 end
548 res.add "</ul>"
549 return res
550 end
551
552 # Collect more information on a package using the `git` tool.
553 fun git_info(mpackage: MPackage)
554 do
555 var ini = mpackage.ini
556 if ini == null then return
557
558 # TODO use real git info
559 #var repo = ini.get_or_null("upstream.git")
560 #var branch = ini.get_or_null("upstream.git.branch")
561 #var directory = ini.get_or_null("upstream.git.directory")
562
563 var dirpath = mpackage.root.filepath
564 if dirpath == null then return
565
566 # Collect commits info
567 var res = git_run("log", "--no-merges", "--follow", "--pretty=tformat:%ad;%aN <%aE>", "--", dirpath)
568 var contributors = new Counter[String]
569 var commits = res.split("\n")
570 if commits.not_empty and commits.last == "" then commits.pop
571 self.commits[mpackage] = commits.length
572 for l in commits do
573 var s = l.split_once_on(';')
574 if s.length != 2 or s.last == "" then continue
575
576 # Collect date of last and first commit
577 if mpackage.last_date == null then mpackage.last_date = s.first
578 mpackage.first_date = s.first
579
580 # Count contributors
581 contributors.inc(s.last)
582 end
583 for c in contributors.sort.reverse_iterator do
584 mpackage.contributors.add c
585 end
586
587 end
588
589 # Produce a HTML table containig information on the packages
590 #
591 # `package_page` must have been called before so that information is computed.
592 fun table_packages(mpackages: Array[MPackage]): Template
593 do
594 alpha_comparator.sort(mpackages)
595 var res = new Template
596 res.add "<table data-toggle=\"table\" data-sort-name=\"name\" data-sort-order=\"desc\" width=\"100%\">\n"
597 res.add "<thead><tr>\n"
598 res.add "<th data-field=\"name\" data-sortable=\"true\">name</th>\n"
599 res.add "<th data-field=\"maint\" data-sortable=\"true\">maint</th>\n"
600 res.add "<th data-field=\"contrib\" data-sortable=\"true\">contrib</th>\n"
601 res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
602 res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
603 res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
604 res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
605 res.add "<th data-field=\"mod\" data-sortable=\"true\">modules</th>\n"
606 res.add "<th data-field=\"cla\" data-sortable=\"true\">classes</th>\n"
607 res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
608 res.add "<th data-field=\"loc\" data-sortable=\"true\">lines</th>\n"
609 res.add "<th data-field=\"score\" data-sortable=\"true\">score</th>\n"
610 res.add "</tr></thead>"
611 for p in mpackages do
612 res.add "<tr>"
613 res.add "<td><a href=\"{p.name}.html\">{p.name}</a></td>"
614 var maint = "?"
615 if p.maintainers.not_empty then maint = p.maintainers.first
616 res.add "<td>{maint}</td>"
617 res.add "<td>{p.contributors.length}</td>"
618 res.add "<td>{deps[p].greaters.length-1}</td>"
619 res.add "<td>{deps[p].direct_greaters.length}</td>"
620 res.add "<td>{deps[p].smallers.length-1}</td>"
621 res.add "<td>{deps[p].direct_smallers.length}</td>"
622 res.add "<td>{mmodules[p]}</td>"
623 res.add "<td>{mclasses[p]}</td>"
624 res.add "<td>{mmethods[p]}</td>"
625 res.add "<td>{loc[p]}</td>"
626 res.add "<td>{score[p]}</td>"
627 res.add "</tr>\n"
628 end
629 res.add "</table>\n"
630 return res
631 end
632 end
633
634 # Execute a git command and return the result
635 fun git_run(command: String...): String
636 do
637 # print "git {command.join(" ")}"
638 var p = new ProcessReader("git", command...)
639 var res = p.read_all
640 p.close
641 p.wait
642 return res
643 end
644
645 var model = new Model
646 var tc = new ToolContext
647
648 var opt_dir = new OptionString("Directory where the HTML files are generated", "-d", "--dir")
649 var opt_no_git = new OptionBool("Do not gather git information from the working directory", "--no-git")
650 var opt_no_parse = new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
651 var opt_no_model = new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
652
653 tc.option_context.add_option(opt_dir, opt_no_git, opt_no_parse, opt_no_model)
654
655 tc.process_options(sys.args)
656 tc.keep_going = true
657
658 var modelbuilder = new ModelBuilder(model, tc)
659 var catalog = new Catalog(modelbuilder)
660
661 # Get files or groups
662 for a in tc.option_context.rest do
663 modelbuilder.get_mgroup(a)
664 modelbuilder.identify_file(a)
665 end
666
667 # Scan packages and compute information
668 for p in model.mpackages do
669 var g = p.root
670 assert g != null
671 modelbuilder.scan_group(g)
672
673 # Load the module to process importation information
674 if opt_no_parse.value then continue
675 modelbuilder.parse_group(g)
676
677 catalog.deps.add_node(p)
678 for gg in p.mgroups do for m in gg.mmodules do
679 for im in m.in_importation.direct_greaters do
680 var ip = im.mpackage
681 if ip == null or ip == p then continue
682 catalog.deps.add_edge(p, ip)
683 end
684 end
685 end
686
687 if not opt_no_git.value then for p in model.mprojects do
688 catalog.git_info(p)
689 end
690
691 # Run phases to modelize classes and properties (so we can count them)
692 if not opt_no_model.value then
693 modelbuilder.run_phases
694 end
695
696 var out = opt_dir.value or else "catalog.out"
697 out.mkdir
698
699 # Generate the css (hard coded)
700 var css = """
701 body {
702 margin-top: 15px;
703 background-color: #f8f8f8;
704 }
705
706 a {
707 color: #0D8921;
708 text-decoration: none;
709 }
710
711 a:hover {
712 color: #333;
713 text-decoration: none;
714 }
715
716 h1 {
717 font-weight: bold;
718 color: #0D8921;
719 font-size: 22px;
720 }
721
722 h2 {
723 color: #6C6C6C;
724 font-size: 18px;
725 border-bottom: solid 3px #CCC;
726 }
727
728 h3 {
729 color: #6C6C6C;
730 font-size: 15px;
731 border-bottom: solid 1px #CCC;
732 }
733
734 ul {
735 list-style-type: square;
736 }
737
738 dd {
739 color: #6C6C6C;
740 margin-top: 1em;
741 margin-bottom: 1em;
742 }
743
744 pre {
745 border: 1px solid #CCC;
746 font-family: Monospace;
747 color: #2d5003;
748 background-color: rgb(250, 250, 250);
749 }
750
751 code {
752 font-family: Monospace;
753 color: #2d5003;
754 }
755
756 footer {
757 margin-top: 20px;
758 }
759
760 .container {
761 margin: 0 auto;
762 padding: 0 20px;
763 }
764
765 .content {
766 float: left;
767 margin-top: 40px;
768 width: 65%;
769 }
770
771 .sidebar {
772 float: right;
773 margin-top: 40px;
774 width: 30%
775 }
776
777 .sidebar h3 {
778 color: #0D8921;
779 font-size: 18px;
780 border-bottom: 0px;
781 }
782
783 .box {
784 margin: 0;
785 padding: 0;
786 }
787
788 .box li {
789 line-height: 2.5;
790 white-space: nowrap;
791 overflow: hidden;
792 text-overflow: ellipsis;
793 padding-right: 10px;
794 border-bottom: 1px solid rgba(0,0,0,0.2);
795 }
796 """
797 css.write_to_file(out/"style.css")
798
799 # PAGES
800
801 for p in model.mpackages do
802 # print p
803 var f = "{p.name}.html"
804 catalog.package_page(p).write_to_file(out/f)
805 end
806
807 # INDEX
808
809 var index = new CatalogPage
810 index.more_head.add "<title>Packages in Nit</title>"
811
812 index.add """
813 <div class="content">
814 <h1>Packages in Nit</h1>
815 """
816
817 index.add "<h2>Highlighted Packages</h2>\n"
818 index.add catalog.list_best(catalog.score)
819
820 index.add "<h2>Most Required</h2>\n"
821 var reqs = new Counter[MPackage]
822 for p in model.mpackages do
823 reqs[p] = catalog.deps[p].smallers.length - 1
824 end
825 index.add catalog.list_best(reqs)
826
827 index.add "<h2>By First Tag</h2>\n"
828 index.add catalog.list_by(catalog.cat2proj, "cat_")
829
830 index.add "<h2>By Any Tag</h2>\n"
831 index.add catalog.list_by(catalog.tag2proj, "tag_")
832
833 index.add """
834 </div>
835 <div class="sidebar">
836 <h3>Stats</h3>
837 <ul class="box">
838 <li>{{{model.mpackages.length}}} packages</li>
839 <li>{{{catalog.maint2proj.length}}} maintainers</li>
840 <li>{{{catalog.contrib2proj.length}}} contributors</li>
841 <li>{{{catalog.tag2proj.length}}} tags</li>
842 <li>{{{catalog.mmodules.sum}}} modules</li>
843 <li>{{{catalog.mclasses.sum}}} classes</li>
844 <li>{{{catalog.mmethods.sum}}} methods</li>
845 <li>{{{catalog.loc.sum}}} lines of code</li>
846 </ul>
847 </div>
848 """
849
850 index.write_to_file(out/"index.html")
851
852 # PEOPLE
853
854 var page = new CatalogPage
855 page.more_head.add "<title>People of Nit</title>"
856 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
857 page.add "<h2>By Maintainer</h2>\n"
858 page.add catalog.list_by(catalog.maint2proj, "maint_")
859 page.add "<h2>By Contributor</h2>\n"
860 page.add catalog.list_by(catalog.contrib2proj, "contrib_")
861 page.add "</div>\n"
862 page.write_to_file(out/"people.html")
863
864 # TABLE
865
866 page = new CatalogPage
867 page.more_head.add "<title>Projets of Nit</title>"
868 page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
869 page.add "<h2>Table of Projets</h2>\n"
870 page.add catalog.table_packages(model.mpackages)
871 page.add "</div>\n"
872 page.write_to_file(out/"table.html")