# * [ ] reify people
# * [ ] separate information gathering from rendering
# * [ ] move up information gathering in (existing or new) service modules
-# * [ ] add command line options
+# * [X] add command line options
# * [ ] harden HTML (escaping, path injection, etc)
# * [ ] nitcorn server with RESTful API
#
class CatalogPage
super Template
+ # The associated catalog, used to groups options and other global data
+ var catalog: Catalog
+
# Placeholder to include additional things before the `</head>`.
var more_head = new Template
+ # Relative path to the root directory (with the index file).
+ #
+ # Use "" for pages in the root directory
+ # Use ".." for pages in a subdirectory
+ var rootpath: String
+
redef init
do
add """
<head>
<meta charset="utf-8">
<link rel="stylesheet" media="all" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css">
- <link rel="stylesheet" media="all" href="style.css">
+ <link rel="stylesheet" media="all" href="{{{rootpath / "style.css"}}}">
"""
add more_head
</div>
<div class='collapse navbar-collapse' id='topmenu-collapse'>
<ul class='nav navbar-nav'>
- <li><a href="index.html">Catalog</a></li>
+ <li><a href="{{{rootpath / "index.html"}}}">Catalog</a></li>
</ul>
</div>
</div>
"""
end
+ # Inject piwik HTML code if required
+ private fun add_piwik
+ do
+ var tracker_url = catalog.piwik_tracker
+ if tracker_url == null then return
+
+ var site_id = catalog.piwik_site_id
+
+ tracker_url = tracker_url.trim
+ if tracker_url.chars.last != '/' then tracker_url += "/"
+ add """
+<!-- Piwik -->
+<script type="text/javascript">
+var _paq = _paq || [];
+_paq.push(['trackPageView']);
+_paq.push(['enableLinkTracking']);
+(function() {
+var u=(("https:" == document.location.protocol) ? "https" : "http") + "://{{{tracker_url.escape_to_c}}}";
+_paq.push(['setTrackerUrl', u+'piwik.php']);
+_paq.push(['setSiteId', {{{site_id}}}]);
+var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; g.type='text/javascript';
+g.defer=true; g.async=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
+})();
+
+</script>
+<noscript><p><img src="http://{{{tracker_url.html_escape}}}piwik.php?idsite={{{site_id}}}" style="border:0;" alt="" /></p></noscript>
+<!-- End Piwik Code -->
+"""
+
+ end
+
redef fun rendering
do
add """
<script src='https://code.jquery.com/jquery-latest.min.js'></script>
<script src='https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/bootstrap-table/1.8.1/bootstrap-table-all.min.js'></script>
+"""
+ add_piwik
+ add """
+
</body>
</html>
"""
# The score is loosely computed using other metrics
var score = new Counter[MPackage]
+ # Return a empty `CatalogPage`.
+ fun new_page(rootpath: String): CatalogPage
+ do
+ return new CatalogPage(self, rootpath)
+ end
+
# Scan, register and add a contributor to a package
fun add_contrib(person: String, mpackage: MPackage, res: Template)
do
end
# Recursively generate a level in the file tree of the *content* section
- private fun gen_content_level(ot: OrderedTree[Object], os: Array[Object], res: Template)
+ private fun gen_content_level(ot: OrderedTree[MConcern], os: Array[Object], res: Template)
do
res.add "<ul>\n"
for o in os do
var mdoc = o.mdoc
if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
- else if o isa ModulePath then
+ else if o isa MModule then
var d = ""
- var m = o.mmodule
- if m != null then
- var mdoc = m.mdoc
- if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
- end
+ var mdoc = o.mdoc
+ if mdoc != null then d = ": {mdoc.html_synopsis.write_to_string}"
res.add "<strong>{o.name}</strong>{d} ({o.filepath.to_s})"
else
abort
# Compute information and generate a full HTML page for a package
fun package_page(mpackage: MPackage): Writable
do
- var res = new CatalogPage
+ var res = new_page("..")
var score = score[mpackage].to_f
var name = mpackage.name.html_escape
res.more_head.add """<title>{{{name}}}</title>"""
end
res.add "<h2>Content</h2>"
- var ot = new OrderedTree[Object]
+ var ot = new OrderedTree[MConcern]
for g in mpackage.mgroups do
var pa = g.parent
if g.is_interesting then
ot.add(pa, g)
pa = g
end
- for mp in g.module_paths do
+ for mp in g.mmodules do
ot.add(pa, mp)
end
end
<div class="sidebar">
<ul class="box">
"""
+ var tryit = mpackage.metadata("upstream.tryit")
+ if tryit != null then
+ score += 1.0
+ var e = tryit.html_escape
+ res.add "<li><a href=\"{e}\">Try<span style=\"color:white\">n</span>it!</a></li>\n"
+ end
+ var apk = mpackage.metadata("upstream.apk")
+ if apk != null then
+ score += 1.0
+ var e = apk.html_escape
+ res.add "<li><a href=\"{e}\">Android apk</a></li>\n"
+ end
+
+ res.add """</ul>\n<ul class="box">\n"""
+
var homepage = mpackage.metadata("upstream.homepage")
if homepage != null then
score += 5.0
res.add "<h3>Tags</h3>\n"
var tags = mpackage.metadata("package.tags")
- var ts2 = new Array[String]
- var cat = null
+ var ts = new Array[String]
if tags != null then
- var ts = tags.split(",")
- for t in ts do
+ for t in tags.split(",") do
t = t.trim
if t == "" then continue
- if cat == null then cat = t
- tag2proj[t].add mpackage
- t = t.html_escape
- ts2.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+ ts.add t
end
- res.add_list(ts2, ", ", ", ")
end
- if ts2.is_empty then
- var t = "none"
- cat = t
+ if ts.is_empty then ts.add "none"
+ if tryit != null then ts.add "tryit"
+ if apk != null then ts.add "apk"
+ var ts2 = new Array[String]
+ for t in ts do
tag2proj[t].add mpackage
- res.add "<a href=\"index.html#tag_{t}\">{t}</a>"
+ t = t.html_escape
+ ts2.add "<a href=\"../index.html#tag_{t}\">{t}</a>"
end
- if cat != null then cat2proj[cat].add mpackage
- score += ts2.length.score
-
- var reqs = deps[mpackage].greaters.to_a
- reqs.remove(mpackage)
- alpha_comparator.sort(reqs)
- res.add "<h3>Requirements</h3>\n"
- if reqs.is_empty then
- res.add "none"
- else
- var list = new Array[String]
- for r in reqs do
- var direct = deps.has_direct_edge(mpackage, r)
- var s = "<a href=\"{r}.html\">"
- if direct then s += "<strong>"
- s += r.to_s
- if direct then s += "</strong>"
- s += "</a>"
- list.add s
+ res.add_list(ts2, ", ", ", ")
+ var cat = ts.first
+ cat2proj[cat].add mpackage
+ score += ts.length.score
+
+ if deps.has(mpackage) then
+ var reqs = deps[mpackage].greaters.to_a
+ reqs.remove(mpackage)
+ alpha_comparator.sort(reqs)
+ res.add "<h3>Requirements</h3>\n"
+ if reqs.is_empty then
+ res.add "none"
+ else
+ var list = new Array[String]
+ for r in reqs do
+ var direct = deps.has_direct_edge(mpackage, r)
+ var s = "<a href=\"{r}.html\">"
+ if direct then s += "<strong>"
+ s += r.to_s
+ if direct then s += "</strong>"
+ s += "</a>"
+ list.add s
+ end
+ res.add_list(list, ", ", " and ")
end
- res.add_list(list, ", ", " and ")
- end
- reqs = deps[mpackage].smallers.to_a
- reqs.remove(mpackage)
- alpha_comparator.sort(reqs)
- res.add "<h3>Clients</h3>\n"
- if reqs.is_empty then
- res.add "none"
- else
- var list = new Array[String]
- for r in reqs do
- var direct = deps.has_direct_edge(r, mpackage)
- var s = "<a href=\"{r}.html\">"
- if direct then s += "<strong>"
- s += r.to_s
- if direct then s += "</strong>"
- s += "</a>"
- list.add s
+ reqs = deps[mpackage].smallers.to_a
+ reqs.remove(mpackage)
+ alpha_comparator.sort(reqs)
+ res.add "<h3>Clients</h3>\n"
+ if reqs.is_empty then
+ res.add "none"
+ else
+ var list = new Array[String]
+ for r in reqs do
+ var direct = deps.has_direct_edge(r, mpackage)
+ var s = "<a href=\"{r}.html\">"
+ if direct then s += "<strong>"
+ s += r.to_s
+ if direct then s += "</strong>"
+ s += "</a>"
+ list.add s
+ end
+ res.add_list(list, ", ", " and ")
end
- res.add_list(list, ", ", " and ")
- end
- score += deps[mpackage].greaters.length.score
- score += deps[mpackage].direct_greaters.length.score
- score += deps[mpackage].smallers.length.score
- score += deps[mpackage].direct_smallers.length.score
+ score += deps[mpackage].greaters.length.score
+ score += deps[mpackage].direct_greaters.length.score
+ score += deps[mpackage].smallers.length.score
+ score += deps[mpackage].direct_smallers.length.score
+ end
var contributors = mpackage.contributors
+ var more_contributors = mpackage.metadata("package.more_contributors")
+ if more_contributors != null then
+ for c in more_contributors.split(",") do
+ contributors.add c.trim
+ end
+ end
if not contributors.is_empty then
res.add "<h3>Contributors</h3>\n<ul class=\"box\">"
for c in contributors do
var mmethods = 0
var loc = 0
for g in mpackage.mgroups do
- mmodules += g.module_paths.length
+ mmodules += g.mmodules.length
for m in g.mmodules do
var am = modelbuilder.mmodule2node(m)
if am != null then
fun li_package(p: MPackage): String
do
var res = ""
- var f = "{p.name}.html"
+ var f = "p/{p.name}.html"
res += "<a href=\"{f}\">{p}</a>"
var d = p.mdoc_or_fallback
if d != null then res += " - {d.html_synopsis.write_to_string}"
res.add "<th data-field=\"name\" data-sortable=\"true\">name</th>\n"
res.add "<th data-field=\"maint\" data-sortable=\"true\">maint</th>\n"
res.add "<th data-field=\"contrib\" data-sortable=\"true\">contrib</th>\n"
- res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
- res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
- res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
- res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
+ if deps.not_empty then
+ res.add "<th data-field=\"reqs\" data-sortable=\"true\">reqs</th>\n"
+ res.add "<th data-field=\"dreqs\" data-sortable=\"true\">direct<br>reqs</th>\n"
+ res.add "<th data-field=\"cli\" data-sortable=\"true\">clients</th>\n"
+ res.add "<th data-field=\"dcli\" data-sortable=\"true\">direct<br>clients</th>\n"
+ end
res.add "<th data-field=\"mod\" data-sortable=\"true\">modules</th>\n"
res.add "<th data-field=\"cla\" data-sortable=\"true\">classes</th>\n"
res.add "<th data-field=\"met\" data-sortable=\"true\">methods</th>\n"
res.add "</tr></thead>"
for p in mpackages do
res.add "<tr>"
- res.add "<td><a href=\"{p.name}.html\">{p.name}</a></td>"
+ res.add "<td><a href=\"p/{p.name}.html\">{p.name}</a></td>"
var maint = "?"
if p.maintainers.not_empty then maint = p.maintainers.first
res.add "<td>{maint}</td>"
res.add "<td>{p.contributors.length}</td>"
- res.add "<td>{deps[p].greaters.length-1}</td>"
- res.add "<td>{deps[p].direct_greaters.length}</td>"
- res.add "<td>{deps[p].smallers.length-1}</td>"
- res.add "<td>{deps[p].direct_smallers.length}</td>"
+ if deps.not_empty then
+ res.add "<td>{deps[p].greaters.length-1}</td>"
+ res.add "<td>{deps[p].direct_greaters.length}</td>"
+ res.add "<td>{deps[p].smallers.length-1}</td>"
+ res.add "<td>{deps[p].direct_smallers.length}</td>"
+ end
res.add "<td>{mmodules[p]}</td>"
res.add "<td>{mclasses[p]}</td>"
res.add "<td>{mmethods[p]}</td>"
res.add "</table>\n"
return res
end
+
+ # Piwik tracker URL, if any
+ var piwik_tracker: nullable String = null
+
+ # Piwik site ID
+ # Used when `piwik_tracker` is set
+ var piwik_site_id: Int = 1
end
# Execute a git command and return the result
var model = new Model
var tc = new ToolContext
+var opt_dir = new OptionString("Directory where the HTML files are generated", "-d", "--dir")
+var opt_no_git = new OptionBool("Do not gather git information from the working directory", "--no-git")
+var opt_no_parse = new OptionBool("Do not parse nit files (no importation information)", "--no-parse")
+var opt_no_model = new OptionBool("Do not analyse nit files (no class/method information)", "--no-model")
+
+# Piwik tracker URL.
+# If you want to monitor your visitors.
+var opt_piwik_tracker = new OptionString("Piwik tracker URL (ex: `nitlanguage.org/piwik/`)", "--piwik-tracker")
+# Piwik tracker site id.
+var opt_piwik_site_id = new OptionString("Piwik site ID", "--piwik-site-id")
+
+tc.option_context.add_option(opt_dir, opt_no_git, opt_no_parse, opt_no_model, opt_piwik_tracker, opt_piwik_site_id)
+
tc.process_options(sys.args)
tc.keep_going = true
var modelbuilder = new ModelBuilder(model, tc)
var catalog = new Catalog(modelbuilder)
+catalog.piwik_tracker = opt_piwik_tracker.value
+var piwik_site_id = opt_piwik_site_id.value
+if piwik_site_id != null then
+ if catalog.piwik_tracker == null then
+ print_error "Warning: ignored `{opt_piwik_site_id}` because `{opt_piwik_tracker}` is not set."
+ else if piwik_site_id.is_int then
+ print_error "Warning: ignored `{opt_piwik_site_id}`, an integer is required."
+ else
+ catalog.piwik_site_id = piwik_site_id.to_i
+ end
+end
+
+
# Get files or groups
-for a in tc.option_context.rest do
- modelbuilder.get_mgroup(a)
- modelbuilder.identify_file(a)
+var args = tc.option_context.rest
+if opt_no_parse.value then
+ modelbuilder.scan_full(args)
+else
+ modelbuilder.parse_full(args)
end
# Scan packages and compute information
modelbuilder.scan_group(g)
# Load the module to process importation information
- modelbuilder.parse_group(g)
+ if opt_no_parse.value then continue
catalog.deps.add_node(p)
for gg in p.mgroups do for m in gg.mmodules do
catalog.deps.add_edge(p, ip)
end
end
+end
+if not opt_no_git.value then for p in model.mpackages do
catalog.git_info(p)
end
# Run phases to modelize classes and properties (so we can count them)
-#modelbuilder.run_phases
+if not opt_no_model.value then
+ modelbuilder.run_phases
+end
-var out = "out"
-out.mkdir
+var out = opt_dir.value or else "catalog.out"
+(out/"p").mkdir
# Generate the css (hard coded)
var css = """
for p in model.mpackages do
# print p
- var f = "{p.name}.html"
+ var f = "p/{p.name}.html"
catalog.package_page(p).write_to_file(out/f)
end
# INDEX
-var index = new CatalogPage
+var index = catalog.new_page("")
index.more_head.add "<title>Packages in Nit</title>"
index.add """
index.add "<h2>Highlighted Packages</h2>\n"
index.add catalog.list_best(catalog.score)
-index.add "<h2>Most Required</h2>\n"
-var reqs = new Counter[MPackage]
-for p in model.mpackages do
- reqs[p] = catalog.deps[p].smallers.length - 1
+if catalog.deps.not_empty then
+ index.add "<h2>Most Required</h2>\n"
+ var reqs = new Counter[MPackage]
+ for p in model.mpackages do
+ reqs[p] = catalog.deps[p].smallers.length - 1
+ end
+ index.add catalog.list_best(reqs)
end
-index.add catalog.list_best(reqs)
index.add "<h2>By First Tag</h2>\n"
index.add catalog.list_by(catalog.cat2proj, "cat_")
# PEOPLE
-var page = new CatalogPage
+var page = catalog.new_page("")
page.more_head.add "<title>People of Nit</title>"
page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
page.add "<h2>By Maintainer</h2>\n"
# TABLE
-page = new CatalogPage
+page = catalog.new_page("")
page.more_head.add "<title>Projets of Nit</title>"
page.add """<div class="content">\n<h1>People of Nit</h1>\n"""
page.add "<h2>Table of Projets</h2>\n"