# This file is part of NIT ( http://www.nitlanguage.org ). # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Basic catalog generator for Nit packages # # See: # # The tool scans packages and generates the HTML files of a catalog. # # See `catalog` for details module nitcatalog import loader # Scan&load packages, groups and modules import doc::doc_down # Display mdoc import catalog # A HTML page in a catalog # # This is just a template with the header pre-filled and the footer injected at rendering. # Therefore, once instantiated, the content can just be added to it. 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 ``. 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 """ """ add more_head add """
""" 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 """ """ end redef fun rendering do add """
""" add_piwik add """ """ end end redef class NitdocDecorator redef fun add_image(v, link, name, comment) do # Keep absolute links as is if link.has_prefix("http://") or link.has_prefix("https://") then super return end do # Get the directory of the doc object to deal with the relative link var mdoc = current_mdoc if mdoc == null then break var source = mdoc.location.file if source == null then break var path = source.filename var stat = path.file_stat if stat == null then break if not stat.is_dir then path = path.dirname # Get the full path to the local resource var fulllink = path / link.to_s stat = fulllink.file_stat if stat == null then break # Get a collision-free catalog name for the resource var hash = fulllink.md5 var ext = fulllink.file_extension if ext != null then hash = hash + "." + ext # Copy the local resource in the resource directory of the catalog var res = catalog.outdir / "res" / hash fulllink.file_copy_to(res) # Hijack the link in the html. link = ".." / "res" / hash super(v, link, name, comment) return end # Something went bad catalog.modelbuilder.toolcontext.error(current_mdoc.location, "Error: cannot find local image `{link}`") super end # The registered catalog # # It is used to deal with relative links in images. var catalog: Catalog is noautoinit end redef class Catalog redef init do # Register `self` to the global NitdocDecorator # FIXME this is ugly. But no better idea at the moment. modelbuilder.model.nitdoc_md_processor.emitter.decorator.as(NitdocDecorator).catalog = self end # The output directory where to generate pages var outdir: String is noautoinit # Return a empty `CatalogPage`. fun new_page(rootpath: String): CatalogPage do return new CatalogPage(self, rootpath) end # Recursively generate a level in the file tree of the *content* section private fun gen_content_level(ot: OrderedTree[MConcern], os: Array[Object], res: Template) do res.add "\n" end # Generate a full HTML page for a package fun generate_page(mpackage: MPackage): Writable do var res = new_page("..") var name = mpackage.name.html_escape res.more_head.add """{{{name}}}""" res.add """

{{{name}}}

""" var mdoc = mpackage.mdoc_or_fallback if mdoc != null then res.add mdoc.html_documentation res.add "

Content

" 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.mmodules do ot.add(pa, mp) end end ot.sort_with(alpha_comparator) gen_content_level(ot, ot.roots, res) res.add """
""" return res end # Return a short HTML sequence for a package # # Intended to use in lists. fun li_package(p: MPackage): String do var res = "" var f = "p/{p.name}.html" res += "{p}" var d = p.mdoc_or_fallback if d != null then res += " - {d.html_synopsis.write_to_string}" return res end # List packages by group. # # For each key of the `map` a `

` is generated. # Each package is then listed. # # The list of keys is generated first to allow fast access to the correct `

`. # `id_prefix` is used to give an id to the `

` element. fun list_by(map: MultiHashMap[Object, MPackage], id_prefix: String): Template do var res = new Template var keys = map.keys.to_a alpha_comparator.sort(keys) var list = [for x in keys do "{x.to_s.html_escape}"] res.add_list(list, ", ", " and ") for k in keys do var projs = map[k].to_a alpha_comparator.sort(projs) var e = k.to_s.html_escape res.add "

{e} ({projs.length})

\n" end return res end # List the 10 best packages from `cpt` fun list_best(cpt: Counter[MPackage]): Template do var res = new Template res.add "" return res end # Produce a HTML table containig information on the packages # # `package_page` must have been called before so that information is computed. fun table_packages(mpackages: Array[MPackage]): Template do alpha_comparator.sort(mpackages) var res = new Template res.add "\n" res.add "\n" res.add "\n" res.add "\n" res.add "\n" if deps.not_empty then res.add "\n" res.add "\n" res.add "\n" res.add "\n" end res.add "\n" res.add "\n" res.add "\n" res.add "\n" res.add "\n" res.add "" for p in mpackages do res.add "" res.add "" var maint = "?" if p.maintainers.not_empty then maint = p.maintainers.first.name.html_escape res.add "" res.add "" if deps.not_empty then res.add "" res.add "" res.add "" res.add "" end res.add "" res.add "" res.add "" res.add "" res.add "" res.add "\n" end res.add "
namemaintcontribreqsdirect
reqs
clientsdirect
clients
modulesclassesmethodslinesscore
{p.name}{maint}{p.contributors.length}{deps[p].greaters.length-1}{deps[p].direct_greaters.length}{deps[p].smallers.length-1}{deps[p].direct_smallers.length}{mmodules[p]}{mclasses[p]}{mmethods[p]}{loc[p]}{score[p]}
\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 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 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 for p in model.mpackages do var g = p.root assert g != null modelbuilder.scan_group(g) # Load the module to process importation information if opt_no_parse.value then continue catalog.deps.add_node(p) for gg in p.mgroups do for m in gg.mmodules do for im in m.in_importation.direct_greaters do var ip = im.mpackage if ip == null or ip == p then continue 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) if not opt_no_model.value then modelbuilder.run_phases end var out = opt_dir.value or else "catalog.out" (out/"p").mkdir (out/"res").mkdir catalog.outdir = out # Generate the css (hard coded) var css = """ body { margin-top: 15px; background-color: #f8f8f8; } a { color: #0D8921; text-decoration: none; } a:hover { color: #333; text-decoration: none; } h1 { font-weight: bold; color: #0D8921; font-size: 22px; } h2 { color: #6C6C6C; font-size: 18px; border-bottom: solid 3px #CCC; } h3 { color: #6C6C6C; font-size: 15px; border-bottom: solid 1px #CCC; } ul { list-style-type: square; } dd { color: #6C6C6C; margin-top: 1em; margin-bottom: 1em; } pre { border: 1px solid #CCC; font-family: Monospace; color: #2d5003; background-color: rgb(250, 250, 250); } code { font-family: Monospace; color: #2d5003; } footer { margin-top: 20px; } .container { margin: 0 auto; padding: 0 20px; } .content { float: left; margin-top: 40px; width: 65%; } .sidebar { float: right; margin-top: 40px; width: 30% } .sidebar h3 { color: #0D8921; font-size: 18px; border-bottom: 0px; } .box { margin: 0; padding: 0; } .box li { line-height: 2.5; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-right: 10px; border-bottom: 1px solid rgba(0,0,0,0.2); } """ css.write_to_file(out/"style.css") # PAGES for p in model.mpackages do # print p var f = "p/{p.name}.html" catalog.package_page(p) catalog.generate_page(p).write_to_file(out/f) end # INDEX var index = catalog.new_page("") index.more_head.add "Packages in Nit" index.add """

Packages in Nit

""" index.add "

Highlighted Packages

\n" index.add catalog.list_best(catalog.score) if catalog.deps.not_empty then index.add "

Most Required

\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 "

By First Tag

\n" index.add catalog.list_by(catalog.cat2proj, "cat_") index.add "

By Any Tag

\n" index.add catalog.list_by(catalog.tag2proj, "tag_") index.add """
""" index.write_to_file(out/"index.html") # PEOPLE var page = catalog.new_page("") page.more_head.add "People of Nit" page.add """
\n

People of Nit

\n""" page.add "

By Maintainer

\n" page.add catalog.list_by(catalog.maint2proj, "maint_") page.add "

By Contributor

\n" page.add catalog.list_by(catalog.contrib2proj, "contrib_") page.add "
\n" page.write_to_file(out/"people.html") # TABLE page = catalog.new_page("") page.more_head.add "Projets of Nit" page.add """
\n

People of Nit

\n""" page.add "

Table of Projets

\n" page.add catalog.table_packages(model.mpackages) page.add "
\n" page.write_to_file(out/"table.html")