# 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 catalog import doc::templates::html_model # 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.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 """
""" var mdoc = mpackage.mdoc_or_fallback if mdoc == null then res.add """

{{{name}}}

""" else res.add """

{{{name}}} - 

""" res.add mdoc.html_documentation end 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.vertices.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 "\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.metadata.maintainers.not_empty then maint = p.metadata.maintainers.first.name.html_escape res.add "" res.add "" if deps.vertices.not_empty then res.add "" res.add "" res.add "" res.add "" end res.add "" res.add "" res.add "" res.add "" res.add "" res.add "" res.add "" res.add "" res.add "" res.add "\n" end res.add "
namemaintcontribreqsdirect
reqs
clientsdirect
clients
modulesclassesmethodslinesscoreerrorswarningsw/klocdoc
{p.name}{maint}{p.metadata.contributors.length}{deps.get_all_successors(p).length-1}{deps.successors(p).length}{deps.get_all_predecessors(p).length-1}{deps.predecessors(p).length}{mmodules[p]}{mclasses[p]}{mmethods[p]}{loc[p]}{score[p]}{errors[p]}{warnings[p]}{warnings_per_kloc[p]}{documentation_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 redef class Person redef fun to_html do var res = "" var e = name.html_escape var page = self.page if page != null then res += "" end var gravatar = self.gravatar if gravatar != null then res += " " end res += e if page != null then res += "" return res end 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 var mmodules if opt_no_parse.value then mmodules = modelbuilder.scan_full(args) else mmodules = modelbuilder.parse_full(args) end var mpackages = modelbuilder.model.mpackage_importation_graph.vertices # Scan packages and compute information for p in mpackages do var g = p.root assert g != null modelbuilder.scan_group(g) end if not opt_no_git.value then for p in 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 mpackages do # print p var f = "p/{p.name}.html" catalog.package_page(p) catalog.generate_page(p).write_to_file(out/f) # copy ini var ini = p.ini if ini != null then ini.write_to_file(out/"p/{p.name}.ini") 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.vertices.not_empty then index.add "

Most Required

\n" var reqs = new Counter[MPackage] for p in mpackages do reqs[p] = catalog.deps.get_all_successors(p).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(mpackages.to_a) page.add "
\n" page.write_to_file(out/"table.html")