aboutsummaryrefslogtreecommitdiffstats
path: root/lv2specgen/lv2specgen.py
diff options
context:
space:
mode:
Diffstat (limited to 'lv2specgen/lv2specgen.py')
-rwxr-xr-xlv2specgen/lv2specgen.py1666
1 files changed, 1066 insertions, 600 deletions
diff --git a/lv2specgen/lv2specgen.py b/lv2specgen/lv2specgen.py
index efbf053..8cfa1c7 100755
--- a/lv2specgen/lv2specgen.py
+++ b/lv2specgen/lv2specgen.py
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
#
# lv2specgen, a documentation generator for LV2 specifications.
-# Copyright (c) 2009-2012 David Robillard <d@drobilla.net>
+# Copyright (c) 2009-2014 David Robillard <d@drobilla.net>
#
# Based on SpecGen:
# <http://forge.morfeo-project.org/wiki_en/index.php/SpecGen>
@@ -30,36 +30,41 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
-__date__ = "2011-10-26"
-__version__ = __date__.replace('-', '.')
-__authors__ = """
-Christopher Schmidt,
-Uldis Bojars,
-Sergio Fernández,
-David Robillard"""
-__license__ = "MIT License <http://www.opensource.org/licenses/mit>"
-__contact__ = "devel@lists.lv2plug.in"
-
import datetime
+import markdown
+import markdown.extensions
+import optparse
import os
import re
import sys
+import time
import xml.sax.saxutils
import xml.dom
import xml.dom.minidom
+__date__ = "2011-10-26"
+__version__ = __date__.replace("-", ".")
+__authors__ = """
+Christopher Schmidt,
+Uldis Bojars,
+Sergio Fernández,
+David Robillard"""
+__license__ = "MIT License <http://www.opensource.org/licenses/mit>"
+__contact__ = "devel@lists.lv2plug.in"
+
try:
from lxml import etree
+
have_lxml = True
-except:
+except Exception:
have_lxml = False
try:
import pygments
import pygments.lexers
+ import pygments.lexers.rdf
import pygments.formatters
- from pygments.lexer import RegexLexer, include, bygroups
- from pygments.token import Text, Comment, Operator, Keyword, Name, String, Literal, Punctuation
+
have_pygments = True
except ImportError:
print("Error importing pygments, syntax highlighting disabled")
@@ -78,31 +83,32 @@ spec_url = None
spec_ns_str = None
spec_ns = None
spec_pre = None
+spec_bundle = None
specgendir = None
ns_list = {
- "http://www.w3.org/1999/02/22-rdf-syntax-ns#" : "rdf",
- "http://www.w3.org/2000/01/rdf-schema#" : "rdfs",
- "http://www.w3.org/2002/07/owl#" : "owl",
- "http://www.w3.org/2001/XMLSchema#" : "xsd",
- "http://rdfs.org/sioc/ns#" : "sioc",
- "http://xmlns.com/foaf/0.1/" : "foaf",
- "http://purl.org/dc/elements/1.1/" : "dc",
- "http://purl.org/dc/terms/" : "dct",
- "http://purl.org/rss/1.0/modules/content/" : "content",
- "http://www.w3.org/2003/01/geo/wgs84_pos#" : "geo",
- "http://www.w3.org/2004/02/skos/core#" : "skos",
- "http://lv2plug.in/ns/lv2core#" : "lv2",
- "http://usefulinc.com/ns/doap#" : "doap",
- "http://ontologi.es/doap-changeset#" : "dcs"
- }
-
-rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
-rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
-owl = rdflib.Namespace('http://www.w3.org/2002/07/owl#')
-lv2 = rdflib.Namespace('http://lv2plug.in/ns/lv2core#')
-doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
-dcs = rdflib.Namespace('http://ontologi.es/doap-changeset#')
-foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
+ "http://www.w3.org/1999/02/22-rdf-syntax-ns#": "rdf",
+ "http://www.w3.org/2000/01/rdf-schema#": "rdfs",
+ "http://www.w3.org/2002/07/owl#": "owl",
+ "http://www.w3.org/2001/XMLSchema#": "xsd",
+ "http://rdfs.org/sioc/ns#": "sioc",
+ "http://xmlns.com/foaf/0.1/": "foaf",
+ "http://purl.org/dc/elements/1.1/": "dc",
+ "http://purl.org/dc/terms/": "dct",
+ "http://purl.org/rss/1.0/modules/content/": "content",
+ "http://www.w3.org/2003/01/geo/wgs84_pos#": "geo",
+ "http://www.w3.org/2004/02/skos/core#": "skos",
+ "http://lv2plug.in/ns/lv2core#": "lv2",
+ "http://usefulinc.com/ns/doap#": "doap",
+ "http://ontologi.es/doap-changeset#": "dcs",
+}
+
+rdf = rdflib.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+rdfs = rdflib.Namespace("http://www.w3.org/2000/01/rdf-schema#")
+owl = rdflib.Namespace("http://www.w3.org/2002/07/owl#")
+lv2 = rdflib.Namespace("http://lv2plug.in/ns/lv2core#")
+doap = rdflib.Namespace("http://usefulinc.com/ns/doap#")
+dcs = rdflib.Namespace("http://ontologi.es/doap-changeset#")
+foaf = rdflib.Namespace("http://xmlns.com/foaf/0.1/")
def findStatements(model, s, p, o):
@@ -110,10 +116,10 @@ def findStatements(model, s, p, o):
def findOne(m, s, p, o):
- l = findStatements(m, s, p, o)
+ triples = findStatements(m, s, p, o)
try:
- return l.next()
- except:
+ return sorted(triples)[0]
+ except Exception:
return None
@@ -146,6 +152,12 @@ def isLiteral(n):
def niceName(uri):
+ global spec_bundle
+ if uri.startswith(spec_ns_str):
+ return uri[len(spec_ns_str) :]
+ elif uri == str(rdfs.seeAlso):
+ return "See also"
+
regexp = re.compile("^(.*[/#])([^/#]+)$")
rez = regexp.search(uri)
if not rez:
@@ -165,148 +177,131 @@ def termName(m, urinode):
def getLabel(m, urinode):
- l = findOne(m, urinode, rdfs.label, None)
- if l:
- return getLiteralString(getObject(l))
+ statement = findOne(m, urinode, rdfs.label, None)
+ if statement:
+ return getLiteralString(getObject(statement))
else:
- return ''
-
-if have_pygments:
- # Based on sw.py by Philip Cooper
- class Notation3Lexer(RegexLexer):
- """
- Lexer for N3 / Turtle / NT
- """
- name = 'N3'
- aliases = ['n3', 'turtle']
- filenames = ['*.n3', '*.ttl', '*.nt']
- mimetypes = ['text/rdf+n3','application/x-turtle','application/n3']
-
- tokens = {
- 'comments': [
- (r'(\s*#.*)', Comment)
- ],
- 'root': [
- include('comments'),
- (r'(\s*@(?:prefix|base|keywords)\s*)(\w*:\s+)?(<[^> ]*>\s*\.\s*)',bygroups(Keyword,Name.Variable,Name.Namespace)),
- (r'\s*(<[^>]*\>)', Name.Class, ('triple','predObj')),
- (r'(\s*[a-zA-Z_:][a-zA-Z0-9\-_:]*\s)', Name.Class, ('triple','predObj')),
- (r'\s*\[\]\s*', Name.Class, ('triple','predObj')),
- ],
- 'triple' : [
- (r'\s*\.\s*', Text, '#pop')
- ],
- 'predObj': [
- include('comments'),
- (r'\s*a\s*', Name.Keyword, 'object'),
- (r'\s*[a-zA-Z_:][a-zA-Z0-9\-_:]*\b\s*', Name.Tag, 'object'),
- (r'\s*(<[^>]*\>)', Name.Tag, 'object'),
- (r'\s*\]\s*', Text, '#pop'),
- (r'(?=\s*\.\s*)', Keyword, '#pop'),
- ],
- 'objList': [
- include('comments'),
- (r'\s*\)', Text, '#pop'),
- include('object')
- ],
- 'object': [
- include('comments'),
- (r'\s*\[', Text, 'predObj'),
- (r'\s*<[^> ]*>', Name.Tag),
- (r'\s*("""(?:.|\n)*?""")(\@[a-z]{2-4}|\^\^<?[a-zA-Z0-9\-\:_#/\.]*>?)?\s*', bygroups(Literal.String,Text)),
- (r'\s*".*?[^\\]"(?:\@[a-z]{2-4}|\^\^<?[a-zA-Z0-9\-\:_#/\.]*>?)?\s*', Literal.String),
- (r'\s*[0-9]+\.[0-9]*\s*\n?', Literal.Number),
- (r'\s*[0-9]+\s*\n?', Literal.Number),
- (r'\s*[a-zA-Z0-9\-_\:]+\s*', Name.Tag),
- (r'\s*\(', Text, 'objList'),
- (r'\s*;\s*\n?', Punctuation, '#pop'),
- (r'\s*,\s*\n?', Punctuation), # Added by drobilla so "," is not an error
- (r'(?=\s*\])', Text, '#pop'),
- (r'(?=\s*\.)', Text, '#pop'),
- ],
- }
-
-def linkify(string):
+ return ""
+
+
+def linkifyCodeIdentifiers(string):
+ "Add links to code documentation for identifiers like LV2_Type"
+
if linkmap == {}:
return string
- "Add links to code documentation for identifiers"
if string in linkmap.keys():
# Exact match for complete string
return linkmap[string]
- rgx = re.compile('([^a-zA-Z0-9_:])(' + \
- '|'.join(map(re.escape, linkmap)) + \
- ')([^a-zA-Z0-9_:])')
+ rgx = re.compile(
+ "([^a-zA-Z0-9_:])("
+ + "|".join(map(re.escape, linkmap))
+ + ")([^a-zA-Z0-9_:])"
+ )
def translateCodeLink(match):
return match.group(1) + linkmap[match.group(2)] + match.group(3)
-
+
return rgx.sub(translateCodeLink, string)
-def getComment(m, urinode, classlist, proplist, instalist):
- c = findOne(m, urinode, lv2.documentation, None)
- if c:
- markup = getLiteralString(getObject(c))
-
- # Syntax highlight all C code
- if have_pygments:
- code_rgx = re.compile('<pre class="c-code">(.*?)</pre>', re.DOTALL)
- while True:
- code = code_rgx.search(markup)
- if not code:
- break
- match_str = xml.sax.saxutils.unescape(code.group(1))
- code_str = pygments.highlight(
- match_str,
- pygments.lexers.CLexer(),
- pygments.formatters.HtmlFormatter())
- markup = code_rgx.sub(code_str, markup, 1)
-
- # Syntax highlight all Turtle code
- if have_pygments:
- code_rgx = re.compile('<pre class="turtle-code">(.*?)</pre>', re.DOTALL)
- while True:
- code = code_rgx.search(markup)
- if not code:
- break
- match_str = xml.sax.saxutils.unescape(code.group(1))
- code_str = pygments.highlight(
- match_str,
- Notation3Lexer(),
- pygments.formatters.HtmlFormatter())
- markup = code_rgx.sub(code_str, markup, 1)
-
- # Add links to code documentation for identifiers
- markup = linkify(markup)
-
- # Transform prefixed names like eg:something into links if possible
- rgx = re.compile('([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)')
- namespaces = getNamespaces(m)
- def translateLink(match):
- text = match.group(0)
- prefix = match.group(1)
- name = match.group(2)
- curie = match.group(0)
- uri = rdflib.URIRef(spec_ns + name)
- if prefix == spec_pre:
- if not ((classlist and uri in classlist) or
- (instalist and uri in instalist) or
- (proplist and uri in proplist)):
- print("warning: Link to undefined resource <%s>\n" % text)
- return '<a href="#%s">%s</a>' % (name, curie)
- elif prefix in namespaces:
- return '<a href="%s">%s</a>' % (
- namespaces[match.group(1)] + match.group(2),
- match.group(0))
- else:
- return text
- markup = rgx.sub(translateLink, markup)
-
- if have_lxml:
- try:
- # Parse and validate documentation as XHTML Basic 1.1
- doc = """<?xml version="1.0" encoding="UTF-8"?>
+
+def linkifyVocabIdentifiers(m, string, classlist, proplist, instalist):
+ "Add links to vocabulary documentation for prefixed names like eg:Thing"
+
+ rgx = re.compile("([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)")
+ namespaces = getNamespaces(m)
+
+ def translateLink(match):
+ text = match.group(0)
+ prefix = match.group(1)
+ name = match.group(2)
+ uri = rdflib.URIRef(spec_ns + name)
+ if prefix == spec_pre:
+ if not (
+ (classlist and uri in classlist)
+ or (instalist and uri in instalist)
+ or (proplist and uri in proplist)
+ ):
+ print("warning: Link to undefined resource <%s>\n" % text)
+ return '<a href="#%s">%s</a>' % (name, name)
+ elif prefix in namespaces:
+ return '<a href="%s">%s</a>' % (
+ namespaces[match.group(1)] + match.group(2),
+ match.group(0),
+ )
+ else:
+ return text
+
+ return rgx.sub(translateLink, string)
+
+
+def prettifyHtml(m, markup, subject, classlist, proplist, instalist):
+ # Syntax highlight all C code
+ if have_pygments:
+ code_rgx = re.compile('<pre class="c-code">(.*?)</pre>', re.DOTALL)
+ while True:
+ code = code_rgx.search(markup)
+ if not code:
+ break
+ match_str = xml.sax.saxutils.unescape(code.group(1))
+ code_str = pygments.highlight(
+ match_str,
+ pygments.lexers.CLexer(),
+ pygments.formatters.HtmlFormatter(),
+ )
+ markup = code_rgx.sub(code_str, markup, 1)
+
+ # Syntax highlight all Turtle code
+ if have_pygments:
+ code_rgx = re.compile(
+ '<pre class="turtle-code">(.*?)</pre>', re.DOTALL
+ )
+ while True:
+ code = code_rgx.search(markup)
+ if not code:
+ break
+ match_str = xml.sax.saxutils.unescape(code.group(1))
+ code_str = pygments.highlight(
+ match_str,
+ pygments.lexers.rdf.TurtleLexer(),
+ pygments.formatters.HtmlFormatter(),
+ )
+ markup = code_rgx.sub(code_str, markup, 1)
+
+ # Add links to code documentation for identifiers
+ markup = linkifyCodeIdentifiers(markup)
+
+ # Add internal links for known prefixed names
+ markup = linkifyVocabIdentifiers(m, markup, classlist, proplist, instalist)
+
+ # Transform names like #foo into links into this spec if possible
+ rgx = re.compile("([ \t\n\r\f\v^]+)#([a-zA-Z0-9_-]+)")
+
+ def translateLocalLink(match):
+ text = match.group(0)
+ space = match.group(1)
+ name = match.group(2)
+ uri = rdflib.URIRef(spec_ns + name)
+ if (
+ (classlist and uri in classlist)
+ or (instalist and uri in instalist)
+ or (proplist and uri in proplist)
+ ):
+ return '%s<a href="#%s">%s</a>' % (space, name, name)
+ else:
+ print("warning: Link to undefined resource <%s>\n" % name)
+ return text
+
+ markup = rgx.sub(translateLocalLink, markup)
+
+ if not have_lxml:
+ print("warning: No Python lxml module found, output may be invalid")
+ else:
+ try:
+ # Parse and validate documentation as XHTML Basic 1.1
+ doc = (
+ """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.1//EN"
"DTD/xhtml-basic11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
@@ -314,43 +309,108 @@ def getComment(m, urinode, classlist, proplist, instalist):
<title>Validation Skeleton Document</title>
</head>
<body>
-%s
+"""
+ + markup
+ + """
</body>
-</html>
-""" % str(markup.decode())
-
- oldcwd = os.getcwd()
- os.chdir(specgendir)
- parser = etree.XMLParser(dtd_validation=True, no_network=True)
- root = etree.fromstring(doc, parser)
- os.chdir(oldcwd)
- except Exception as e:
- print("Invalid lv2:documentation for %s\n%s" % (urinode, e))
+</html>"""
+ )
+
+ oldcwd = os.getcwd()
+ os.chdir(specgendir)
+ parser = etree.XMLParser(dtd_validation=True, no_network=True)
+ etree.fromstring(doc.encode("utf-8"), parser)
+ except Exception as e:
+ print("Invalid documentation for %s\n%s" % (subject, e))
+ line_num = 1
+ for line in doc.split("\n"):
+ print("%3d: %s" % (line_num, line))
+ line_num += 1
+ finally:
+ os.chdir(oldcwd)
+
+ return markup
+
+
+def formatDoc(m, urinode, literal, classlist, proplist, instalist):
+ string = getLiteralString(literal)
+
+ if literal.datatype == lv2.Markdown:
+ ext = [
+ "markdown.extensions.codehilite",
+ "markdown.extensions.tables",
+ "markdown.extensions.def_list",
+ ]
+
+ doc = markdown.markdown(string, extensions=ext)
+
+ # Hack to make tables valid XHTML Basic 1.1
+ for tag in ["thead", "tbody"]:
+ doc = doc.replace("<%s>\n" % tag, "")
+ doc = doc.replace("</%s>\n" % tag, "")
+
+ return prettifyHtml(m, doc, urinode, classlist, proplist, instalist)
+ else:
+ doc = xml.sax.saxutils.escape(string)
+ doc = linkifyCodeIdentifiers(doc)
+ doc = linkifyVocabIdentifiers(m, doc, classlist, proplist, instalist)
+ return "<p>%s</p>" % doc
- return markup
- c = findOne(m, urinode, rdfs.comment, None)
+def getComment(m, subject, classlist, proplist, instalist):
+ c = findOne(m, subject, rdfs.comment, None)
if c:
- text = getLiteralString(getObject(c))
- return '<p>%s</p>' % xml.sax.saxutils.escape(text)
+ comment = getObject(c)
+ return formatDoc(m, subject, comment, classlist, proplist, instalist)
+
+ return ""
+
- return ''
+def getDetailedDocumentation(m, subject, classlist, proplist, instalist):
+ markup = ""
+
+ d = findOne(m, subject, lv2.documentation, None)
+ if d:
+ doc = getObject(d)
+ if doc.datatype == lv2.Markdown:
+ markup += formatDoc(
+ m, subject, doc, classlist, proplist, instalist
+ )
+ else:
+ html = getLiteralString(doc)
+ markup += prettifyHtml(
+ m, html, subject, classlist, proplist, instalist
+ )
+
+ return markup
+
+
+def getFullDocumentation(m, subject, classlist, proplist, instalist):
+ # Use rdfs:comment for first summary line
+ markup = getComment(m, subject, classlist, proplist, instalist)
+
+ # Use lv2:documentation for further details
+ markup += getDetailedDocumentation(
+ m, subject, classlist, proplist, instalist
+ )
+
+ return markup
def getProperty(val, first=True):
"Return a string representing a property value in a property table"
- doc = ''
+ doc = ""
if not first:
- doc += '<tr><td></td>' # Empty cell in header column
- doc += '<td>%s</td></tr>\n' % val
+ doc += "<tr><th></th>" # Empty cell in header column
+ doc += "<td>%s</td></tr>\n" % val
return doc
def endProperties(first):
if first:
- return '</tr>'
+ return "</tr>"
else:
- return ''
+ return ""
def rdfsPropertyInfo(term, m):
@@ -358,54 +418,64 @@ def rdfsPropertyInfo(term, m):
global classranges
global classdomains
doc = ""
- range = ""
- domain = ""
+
+ label = getLabel(m, term)
+ if label != "":
+ doc += "<tr><th>Label</th><td>%s</td></tr>" % label
# Find subPropertyOf information
- rlist = ''
+ rlist = ""
first = True
for st in findStatements(m, term, rdfs.subPropertyOf, None):
k = getTermLink(getObject(st), term, rdfs.subPropertyOf)
rlist += getProperty(k, first)
first = False
- if rlist != '':
- doc += '<tr><th>Sub-property of</th>' + rlist
+ if rlist != "":
+ doc += "<tr><th>Sub-property of</th>" + rlist
# Domain stuff
domains = findStatements(m, term, rdfs.domain, None)
domainsdoc = ""
first = True
- for d in domains:
+ for d in sorted(domains):
union = findOne(m, getObject(d), owl.unionOf, None)
if union:
uris = parseCollection(m, getObject(union))
for uri in uris:
- domainsdoc += getProperty(getTermLink(uri, term, rdfs.domain), first)
+ domainsdoc += getProperty(
+ getTermLink(uri, term, rdfs.domain), first
+ )
add(classdomains, uri, term)
else:
if not isBlank(getObject(d)):
- domainsdoc += getProperty(getTermLink(getObject(d), term, rdfs.domain), first)
+ domainsdoc += getProperty(
+ getTermLink(getObject(d), term, rdfs.domain), first
+ )
first = False
- if (len(domainsdoc) > 0):
+ if len(domainsdoc) > 0:
doc += "<tr><th>Domain</th>%s" % domainsdoc
# Range stuff
ranges = findStatements(m, term, rdfs.range, None)
rangesdoc = ""
first = True
- for r in ranges:
+ for r in sorted(ranges):
union = findOne(m, getObject(r), owl.unionOf, None)
if union:
uris = parseCollection(m, getObject(union))
for uri in uris:
- rangesdoc += getProperty(getTermLink(uri, term, rdfs.range), first)
+ rangesdoc += getProperty(
+ getTermLink(uri, term, rdfs.range), first
+ )
add(classranges, uri, term)
first = False
else:
if not isBlank(getObject(r)):
- rangesdoc += getProperty(getTermLink(getObject(r), term, rdfs.range), first)
+ rangesdoc += getProperty(
+ getTermLink(getObject(r), term, rdfs.range), first
+ )
first = False
- if (len(rangesdoc) > 0):
+ if len(rangesdoc) > 0:
doc += "<tr><th>Range</th>%s" % rangesdoc
return doc
@@ -416,10 +486,10 @@ def parseCollection(model, node):
while node:
first = findOne(model, node, rdf.first, None)
- rest = findOne(model, node, rdf.rest, None)
+ rest = findOne(model, node, rdf.rest, None)
if not first or not rest:
- break;
-
+ break
+
uris.append(getObject(first))
node = getObject(rest)
@@ -428,41 +498,36 @@ def parseCollection(model, node):
def getTermLink(uri, subject=None, predicate=None):
uri = str(uri)
- extra = ''
- if subject != None and predicate != None:
- extra = 'about="%s" rel="%s" resource="%s"' % (str(subject), niceName(str(predicate)), uri)
- if (uri.startswith(spec_ns_str)):
- return '<a href="#%s" %s>%s</a>' % (uri.replace(spec_ns_str, ""), extra, niceName(uri))
+ extra = ""
+ if subject is not None and predicate is not None:
+ extra = 'about="%s" rel="%s" resource="%s"' % (
+ str(subject),
+ niceName(str(predicate)),
+ uri,
+ )
+ if uri.startswith(spec_ns_str):
+ return '<a href="#%s" %s>%s</a>' % (
+ uri.replace(spec_ns_str, ""),
+ extra,
+ niceName(uri),
+ )
else:
return '<a href="%s" %s>%s</a>' % (uri, extra, niceName(uri))
-def rdfsClassInfo(term, m):
- """Generate rdfs-type information for Classes: ranges, and domains."""
- global classranges
- global classdomains
- doc = ""
-
- # Find subClassOf information
+def owlRestrictionInfo(term, m):
+ """Generate OWL restriction information for Classes"""
restrictions = []
- superclasses = []
- for st in findStatements(m, term, rdfs.subClassOf, None):
- if not isBlank(getObject(st)):
- uri = getObject(st)
- if not uri in superclasses:
- superclasses.append(uri)
- else:
- meta_type = findOne(m, getObject(st), rdf.type, None)
- restrictions.append(getSubject(meta_type))
-
- if len(superclasses) > 0:
- doc += "\n<tr><th>Sub-class of</th>"
- first = True
- for superclass in superclasses:
- doc += getProperty(getTermLink(superclass), first)
- first = False
+ for s in findStatements(m, term, rdfs.subClassOf, None):
+ if findOne(m, getObject(s), rdf.type, owl.Restriction):
+ restrictions.append(getObject(s))
+
+ if not restrictions:
+ return ""
+
+ doc = "<dl>"
- for r in restrictions:
+ for r in sorted(restrictions):
props = findStatements(m, r, None, None)
onProp = None
comment = None
@@ -471,46 +536,82 @@ def rdfsClassInfo(term, m):
onProp = getObject(p)
elif getPredicate(p) == rdfs.comment:
comment = getObject(p)
- if onProp != None:
- doc += '<tr><th>Restriction on %s</th><td>' % getTermLink(onProp)
+ if onProp is not None:
+ doc += "<dt>Restriction on %s</dt>\n" % getTermLink(onProp)
- prop_str = ''
- last_pred = None
- first = True
+ prop_str = ""
for p in findStatements(m, r, None, None):
- if (getPredicate(p) == owl.onProperty
+ if (
+ getPredicate(p) == owl.onProperty
or getPredicate(p) == rdfs.comment
- or (getPredicate(p) == rdf.type and getObject(p) == owl.Restriction)
- or getPredicate(p) == lv2.documentation):
- last_pred = None
+ or (
+ getPredicate(p) == rdf.type
+ and getObject(p) == owl.Restriction
+ )
+ or getPredicate(p) == lv2.documentation
+ ):
continue
- if getPredicate(p) != last_pred:
- prop_str += '<tr><th>%s</th>\n' % getTermLink(getPredicate(p))
- first = True
+ prop_str += getTermLink(getPredicate(p))
+
if isResource(getObject(p)):
- prop_str += getProperty(getTermLink(getObject(p)), first)
- first = False
+ prop_str += " " + getTermLink(getObject(p))
elif isLiteral(getObject(p)):
- prop_str += getProperty(getLiteralString(getObject(p)), first)
- first = False
+ prop_str += " " + getLiteralString(getObject(p))
+
+ if comment is not None:
+ prop_str += "\n<div>%s</div>\n" % getLiteralString(comment)
- last_pred = getPredicate(p)
+ doc += "<dd>%s</dd>" % prop_str if prop_str else ""
- prop_str += endProperties(first)
+ doc += "</dl>"
+ return doc
- if prop_str != '':
- doc += '<table class=\"restriction\">%s</table>\n' % prop_str
- if comment != None:
- doc += "<span>%s</span>\n" % getLiteralString(comment)
- doc += '</td></tr>'
+
+def rdfsClassInfo(term, m):
+ """Generate rdfs-type information for Classes: ranges, and domains."""
+ global classranges
+ global classdomains
+ doc = ""
+
+ label = getLabel(m, term)
+ if label != "":
+ doc += "<tr><th>Label</th><td>%s</td></tr>" % label
+
+ # Find superclasses
+ superclasses = set()
+ for st in findStatements(m, term, rdfs.subClassOf, None):
+ if not isBlank(getObject(st)):
+ uri = getObject(st)
+ superclasses |= set([uri])
+
+ if len(superclasses) > 0:
+ doc += "\n<tr><th>Subclass of</th>"
+ first = True
+ for superclass in sorted(superclasses):
+ doc += getProperty(getTermLink(superclass), first)
+ first = False
+
+ # Find subclasses
+ subclasses = set()
+ for st in findStatements(m, None, rdfs.subClassOf, term):
+ if not isBlank(getObject(st)):
+ uri = getSubject(st)
+ subclasses |= set([uri])
+
+ if len(subclasses) > 0:
+ doc += "\n<tr><th>Superclass of</th>"
+ first = True
+ for superclass in sorted(subclasses):
+ doc += getProperty(getTermLink(superclass), first)
+ first = False
# Find out about properties which have rdfs:domain of t
d = classdomains.get(str(term), "")
if d:
- dlist = ''
+ dlist = ""
first = True
- for k in d:
+ for k in sorted(d):
dlist += getProperty(getTermLink(k), first)
first = False
doc += "<tr><th>In domain of</th>%s" % dlist
@@ -518,9 +619,9 @@ def rdfsClassInfo(term, m):
# Find out about properties which have rdfs:range of t
r = classranges.get(str(term), "")
if r:
- rlist = ''
+ rlist = ""
first = True
- for k in r:
+ for k in sorted(r):
rlist += getProperty(getTermLink(k), first)
first = False
doc += "<tr><th>In range of</th>%s" % rlist
@@ -529,30 +630,45 @@ def rdfsClassInfo(term, m):
def isSpecial(pred):
- """Return True if the predicate is "special" and shouldn't be emitted generically"""
- return pred in [rdf.type, rdfs.range, rdfs.domain, rdfs.label, rdfs.comment, rdfs.subClassOf, rdfs.subPropertyOf, lv2.documentation]
+ """Return True if `pred` shouldn't be documented generically"""
+ return pred in [
+ rdf.type,
+ rdfs.range,
+ rdfs.domain,
+ rdfs.label,
+ rdfs.comment,
+ rdfs.subClassOf,
+ rdfs.subPropertyOf,
+ lv2.documentation,
+ owl.withRestrictions,
+ ]
def blankNodeDesc(node, m):
properties = findStatements(m, node, None, None)
- doc = ''
- last_pred = ''
- for p in properties:
+ doc = ""
+ for p in sorted(properties):
if isSpecial(getPredicate(p)):
continue
- doc += '<tr>'
+ doc += "<tr>"
doc += '<td class="blankterm">%s</td>\n' % getTermLink(getPredicate(p))
if isResource(getObject(p)):
doc += '<td class="blankdef">%s</td>\n' % getTermLink(getObject(p))
# getTermLink(str(getObject(p)), node, getPredicate(p))
elif isLiteral(getObject(p)):
- doc += '<td class="blankdef">%s</td>\n' % getLiteralString(getObject(p))
+ doc += '<td class="blankdef">%s</td>\n' % getLiteralString(
+ getObject(p)
+ )
elif isBlank(getObject(p)):
- doc += '<td class="blankdef">' + blankNodeDesc(getObject(p), m) + '</td>\n'
+ doc += (
+ '<td class="blankdef">'
+ + blankNodeDesc(getObject(p), m)
+ + "</td>\n"
+ )
else:
doc += '<td class="blankdef">?</td>\n'
- doc += '</tr>'
- if doc != '':
+ doc += "</tr>"
+ if doc != "":
doc = '<table class="blankdesc">\n%s\n</table>\n' % doc
return doc
@@ -561,27 +677,25 @@ def extraInfo(term, m):
"""Generate information about misc. properties of a term"""
doc = ""
properties = findStatements(m, term, None, None)
- last_pred = None
first = True
- for p in properties:
+ for p in sorted(properties):
if isSpecial(getPredicate(p)):
- last_pred = None
continue
- if getPredicate(p) != last_pred:
- doc += '<tr><th>%s</th>\n' % getTermLink(getPredicate(p))
- first = True
+ doc += "<tr><th>%s</th>\n" % getTermLink(getPredicate(p))
if isResource(getObject(p)):
- doc += getProperty(getTermLink(getObject(p), term, getPredicate(p)), first)
+ doc += getProperty(
+ getTermLink(getObject(p), term, getPredicate(p)), first
+ )
elif isLiteral(getObject(p)):
- doc += getProperty(linkify(str(getObject(p))), first)
+ doc += getProperty(
+ linkifyCodeIdentifiers(str(getObject(p))), first
+ )
elif isBlank(getObject(p)):
doc += getProperty(str(blankNodeDesc(getObject(p), m)), first)
else:
- doc += getProperty('?', first)
- first = False
- last_pred = getPredicate(p)
+ doc += getProperty("?", first)
- #doc += endProperties(first)
+ # doc += endProperties(first)
return doc
@@ -590,16 +704,20 @@ def rdfsInstanceInfo(term, m):
"""Generate rdfs-type information for instances"""
doc = ""
+ label = getLabel(m, term)
+ if label != "":
+ doc += "<tr><th>Label</th><td>%s</td></tr>" % label
+
first = True
- for match in findStatements(m, term, rdf.type, None):
- doc += getProperty(getTermLink(getObject(match),
- term,
- rdf.type),
- first)
+ types = ""
+ for match in sorted(findStatements(m, term, rdf.type, None)):
+ types += getProperty(
+ getTermLink(getObject(match), term, rdf.type), first
+ )
first = False
- if doc != "":
- doc = "<tr><th>Type</th>" + doc
+ if types != "":
+ doc += "<tr><th>Type</th>" + types
doc += endProperties(first)
@@ -608,7 +726,7 @@ def rdfsInstanceInfo(term, m):
def owlInfo(term, m):
"""Returns an extra information that is defined about a term using OWL."""
- res = ''
+ res = ""
# Inverse properties ( owl:inverseOf )
first = True
@@ -621,78 +739,81 @@ def owlInfo(term, m):
def owlTypeInfo(term, propertyType, name):
if findOne(m, term, rdf.type, propertyType):
- return "<tr><th>OWL Type</th><td>%s</td></tr>\n" % name
+ return "<tr><th>Type</th><td>%s</td></tr>\n" % name
else:
return ""
res += owlTypeInfo(term, owl.DatatypeProperty, "Datatype Property")
res += owlTypeInfo(term, owl.ObjectProperty, "Object Property")
res += owlTypeInfo(term, owl.AnnotationProperty, "Annotation Property")
- res += owlTypeInfo(term, owl.InverseFunctionalProperty, "Inverse Functional Property")
+ res += owlTypeInfo(
+ term, owl.InverseFunctionalProperty, "Inverse Functional Property"
+ )
res += owlTypeInfo(term, owl.SymmetricProperty, "Symmetric Property")
return res
+def isDeprecated(m, subject):
+ deprecated = findOne(m, subject, owl.deprecated, None)
+ return deprecated and (str(deprecated[2]).find("true") >= 0)
+
+
def docTerms(category, list, m, classlist, proplist, instalist):
"""
A wrapper class for listing all the terms in a specific class (either
Properties, or Classes. Category is 'Property' or 'Class', list is a
- list of term names (strings), return value is a chunk of HTML.
+ list of term URI strings, return value is a chunk of HTML.
"""
doc = ""
- nspre = spec_pre
- for item in list:
- t = termName(m, item)
- if (t.startswith(spec_ns_str)) and (
- len(t[len(spec_ns_str):].split("/")) < 2):
- term = t
- t = t.split(spec_ns_str[-1])[1]
- curie = "%s:%s" % (nspre, t)
- else:
- if t.startswith("http://"):
- term = t
- curie = getShortName(t)
- t = getAnchor(t)
- else:
- term = spec_ns[t]
- curie = "%s:%s" % (nspre, t)
-
- term_uri = term
+ for term in list:
+ if not term.startswith(spec_ns_str):
+ sys.stderr.write("warning: Skipping external term `%s'" % term)
+ continue
- doc += """<div class="specterm" id="%s" about="%s">\n<h3>%s <a href="#%s">%s</a></h3>\n""" % (t, term_uri, category, getAnchor(str(term_uri)), curie)
+ t = termName(m, term)
+ curie = term.split(spec_ns_str[-1])[1]
+ doc += '<div class="specterm" id="%s" about="%s">' % (t, term)
+ doc += '<h3><a href="#%s">%s</a></h3>' % (getAnchor(term), curie)
+ doc += '<span class="spectermtype">%s</span>' % category
- label = getLabel(m, term)
- comment = getComment(m, term, classlist, proplist, instalist)
+ comment = getFullDocumentation(m, term, classlist, proplist, instalist)
+ is_deprecated = isDeprecated(m, term)
doc += '<div class="spectermbody">'
- if label != '' or comment != '':
- doc += '<div class="description">'
-
- if label != '':
- doc += "<div property=\"rdfs:label\" class=\"label\">%s</div>" % label
-
- if comment != '':
- doc += "<div property=\"rdfs:comment\">%s</div>" % comment
-
- if label != '' or comment != '':
- doc += "</div>"
terminfo = ""
- if category == 'Property':
- terminfo += owlInfo(term, m)
+ extrainfo = ""
+ if category == "Property":
terminfo += rdfsPropertyInfo(term, m)
- if category == 'Class':
+ terminfo += owlInfo(term, m)
+ if category == "Class":
terminfo += rdfsClassInfo(term, m)
- if category == 'Instance':
+ extrainfo += owlRestrictionInfo(term, m)
+ if category == "Instance":
terminfo += rdfsInstanceInfo(term, m)
terminfo += extraInfo(term, m)
- if (len(terminfo) > 0): # to prevent empty list (bug #882)
+ if len(terminfo) > 0: # to prevent empty list (bug #882)
doc += '\n<table class="terminfo">%s</table>\n' % terminfo
- doc += '</div>'
+ doc += '<div class="description">'
+
+ if is_deprecated:
+ doc += '<div class="warning">Deprecated</div>'
+
+ if comment != "":
+ doc += (
+ '<div class="comment" property="rdfs:comment">%s</div>'
+ % comment
+ )
+
+ doc += extrainfo
+
+ doc += "</div>"
+
+ doc += "</div>"
doc += "\n</div>\n\n"
return doc
@@ -700,7 +821,7 @@ def docTerms(category, list, m, classlist, proplist, instalist):
def getShortName(uri):
uri = str(uri)
- if ("#" in uri):
+ if "#" in uri:
return uri.split("#")[-1]
else:
return uri.split("/")[-1]
@@ -708,29 +829,31 @@ def getShortName(uri):
def getAnchor(uri):
uri = str(uri)
- if (uri.startswith(spec_ns_str)):
- return uri[len(spec_ns_str):].replace("/", "_")
+ if uri.startswith(spec_ns_str):
+ return uri[len(spec_ns_str) :].replace("/", "_")
else:
return getShortName(uri)
def buildIndex(m, classlist, proplist, instalist=None):
- """
- Builds the A-Z list of terms. Args are a list of classes (strings) and
- a list of props (strings)
- """
+ if not (classlist or proplist or instalist):
+ return ""
- if len(classlist) == 0 and len(proplist) == 0 and (
- not instalist or len(instalist) == 0):
- return ''
+ head = ""
+ body = ""
- azlist = '<dl class="index">'
+ def termLink(m, t):
+ if str(t).startswith(spec_ns_str):
+ name = termName(m, t)
+ return '<a href="#%s">%s</a>' % (name, name)
+ else:
+ return '<a href="%s">%s</a>' % (str(t), str(t))
- if (len(classlist) > 0):
- azlist += "<dt>Classes</dt><dd><ul>"
- classlist.sort()
+ if len(classlist) > 0:
+ head += '<th><a href="#ref-classes" />Classes</th>'
+ body += "<td><ul>"
shown = {}
- for c in classlist:
+ for c in sorted(classlist):
if c in shown:
continue
@@ -738,65 +861,66 @@ def buildIndex(m, classlist, proplist, instalist=None):
local_subclass = False
for p in findStatements(m, c, rdfs.subClassOf, None):
parent = str(p[2])
- if parent[0:len(spec_ns_str)] == spec_ns_str:
+ if parent[0 : len(spec_ns_str)] == spec_ns_str:
local_subclass = True
if local_subclass:
continue
shown[c] = True
- name = termName(m, c)
- if name.startswith(spec_ns_str):
- name = name.split(spec_ns_str[-1])[1]
- azlist += '<li><a href="#%s">%s</a>' % (name, name)
+ body += "<li>" + termLink(m, c)
+
def class_tree(c):
- tree = ''
+ tree = ""
shown[c] = True
subclasses = []
for s in findStatements(m, None, rdfs.subClassOf, c):
subclasses += [getSubject(s)]
- subclasses.sort()
- for s in subclasses:
- s_name = termName(m, s)
- tree += '<li><a href="#%s">%s</a>\n' % (s_name, s_name)
+ for s in sorted(subclasses):
+ tree += "<li>" + termLink(m, s)
tree += class_tree(s)
- tree += '</li>'
- if tree != '':
- tree = '<ul>' + tree + '</ul>'
+ tree += "</li>"
+ if tree != "":
+ tree = "<ul>" + tree + "</ul>"
return tree
- azlist += class_tree(c)
- azlist += '</li>'
- azlist += '</ul></dd>\n'
-
- if (len(proplist) > 0):
- azlist += "<dt>Properties</dt><dd>"
- proplist.sort()
- props = []
- for p in proplist:
- name = termName(m, p)
- if name.startswith(spec_ns_str):
- name = name.split(spec_ns_str[-1])[1]
- props += ['<a href="#%s">%s</a>' % (name, name)]
- azlist += ', '.join(props) + '</dd>\n'
-
- if (instalist != None and len(instalist) > 0):
- azlist += "<dt>Instances</dt><dd>"
- instas = []
- for i in instalist:
+
+ body += class_tree(c)
+ body += "</li>"
+ body += "</ul></td>\n"
+
+ if len(proplist) > 0:
+ head += '<th><a href="#ref-properties" />Properties</th>'
+ body += "<td><ul>"
+ for p in sorted(proplist):
+ body += "<li>%s</li>" % termLink(m, p)
+ body += "</ul></td>\n"
+
+ if instalist is not None and len(instalist) > 0:
+ head += '<th><a href="#ref-instances" />Instances</th>'
+ body += "<td><ul>"
+ for i in sorted(instalist):
p = getShortName(i)
anchor = getAnchor(i)
- instas += ['<a href="#%s">%s</a>' % (anchor, p)]
- azlist += ', '.join(instas) + '</dd>\n'
+ body += '<li><a href="#%s">%s</a></li>' % (anchor, p)
+ body += "</ul></td>\n"
+
+ if head and body:
+ return """<table class="index">
+<thead><tr>%s</tr></thead>
+<tbody><tr>%s</tr></tbody></table>
+""" % (
+ head,
+ body,
+ )
- azlist += '\n</dl>'
- return azlist
+ return ""
def add(where, key, value):
- if not key in where:
+ if key not in where:
where[key] = []
- if not value in where[key]:
+ if value not in where[key]:
where[key].append(value)
@@ -814,23 +938,36 @@ def specInformation(m, ns):
classlist = []
for onetype in classtypes:
for classStatement in findStatements(m, None, rdf.type, onetype):
- for range in findStatements(m, None, rdfs.range, getSubject(classStatement)):
+ for range in findStatements(
+ m, None, rdfs.range, getSubject(classStatement)
+ ):
if not isBlank(getSubject(classStatement)):
- add(classranges,
+ add(
+ classranges,
str(getSubject(classStatement)),
- str(getSubject(range)))
- for domain in findStatements(m, None, rdfs.domain, getSubject(classStatement)):
+ str(getSubject(range)),
+ )
+ for domain in findStatements(
+ m, None, rdfs.domain, getSubject(classStatement)
+ ):
if not isBlank(getSubject(classStatement)):
- add(classdomains,
+ add(
+ classdomains,
str(getSubject(classStatement)),
- str(getSubject(domain)))
+ str(getSubject(domain)),
+ )
if not isBlank(getSubject(classStatement)):
klass = getSubject(classStatement)
if klass not in classlist and str(klass).startswith(ns):
classlist.append(klass)
# Create a list of properties in the schema.
- proptypes = [rdf.Property, owl.ObjectProperty, owl.DatatypeProperty, owl.AnnotationProperty]
+ proptypes = [
+ rdf.Property,
+ owl.ObjectProperty,
+ owl.DatatypeProperty,
+ owl.AnnotationProperty,
+ ]
proplist = []
for onetype in proptypes:
for propertyStatement in findStatements(m, None, rdf.type, onetype):
@@ -845,7 +982,7 @@ def specProperty(m, subject, predicate):
"Return a property of the spec."
for c in findStatements(m, subject, predicate, None):
return getLiteralString(getObject(c))
- return ''
+ return ""
def specProperties(m, subject, predicate):
@@ -858,94 +995,136 @@ def specProperties(m, subject, predicate):
def specAuthors(m, subject):
"Return an HTML description of the authors of the spec."
+
+ subjects = [subject]
+ p = findOne(m, subject, lv2.project, None)
+ if p:
+ subjects += [getObject(p)]
+
dev = set()
- for i in findStatements(m, subject, doap.developer, None):
- for j in findStatements(m, getObject(i), foaf.name, None):
- dev.add(getLiteralString(getObject(j)))
+ for s in subjects:
+ for i in findStatements(m, s, doap.developer, None):
+ for j in findStatements(m, getObject(i), foaf.name, None):
+ dev.add(getLiteralString(getObject(j)))
maint = set()
- for i in findStatements(m, subject, doap.maintainer, None):
- for j in findStatements(m, getObject(i), foaf.name, None):
- maint.add(getLiteralString(getObject(j)))
+ for s in subjects:
+ for i in findStatements(m, s, doap.maintainer, None):
+ for j in findStatements(m, getObject(i), foaf.name, None):
+ maint.add(getLiteralString(getObject(j)))
- doc = ''
+ doc = ""
- devdoc = ''
+ devdoc = ""
first = True
- for d in dev:
+ for d in sorted(dev):
if not first:
- devdoc += ', '
- devdoc += '<span class="author" property="doap:developer">%s</span>' % d
+ devdoc += ", "
+ devdoc += (
+ '<span class="author" property="doap:developer">%s</span>' % d
+ )
first = False
if len(dev) == 1:
- doc += '<tr><th class="metahead">Developer</th><td>%s</td></tr>' % devdoc
+ doc += (
+ '<tr><th class="metahead">Developer</th><td>%s</td></tr>' % devdoc
+ )
elif len(dev) > 0:
- doc += '<tr><th class="metahead">Developers</th><td>%s</td></tr>' % devdoc
+ doc += (
+ '<tr><th class="metahead">Developers</th><td>%s</td></tr>' % devdoc
+ )
- maintdoc = ''
+ maintdoc = ""
first = True
- for m in maint:
+ for m in sorted(maint):
if not first:
- maintdoc += ', '
- maintdoc += '<span class="author" property="doap:maintainer">%s</span>' % m
+ maintdoc += ", "
+ maintdoc += (
+ '<span class="author" property="doap:maintainer">%s</span>' % m
+ )
first = False
if len(maint) == 1:
- doc += '<tr><th class="metahead">Maintainer</th><td>%s</td></tr>' % maintdoc
+ doc += (
+ '<tr><th class="metahead">Maintainer</th><td>%s</td></tr>'
+ % maintdoc
+ )
elif len(maint) > 0:
- doc += '<tr><th class="metahead">Maintainers</th><td>%s</td></tr>' % maintdoc
+ doc += (
+ '<tr><th class="metahead">Maintainers</th><td>%s</td></tr>'
+ % maintdoc
+ )
return doc
-def specHistory(m, subject):
- entries = {}
+def releaseChangeset(m, release, prefix=""):
+ changeset = findOne(m, release, dcs.changeset, None)
+ if changeset is None:
+ return ""
+
+ entry = ""
+ # entry = '<dd><ul>\n'
+ for i in sorted(findStatements(m, getObject(changeset), dcs.item, None)):
+ item = getObject(i)
+ label = findOne(m, item, rdfs.label, None)
+ if not label:
+ print("error: dcs:item has no rdfs:label")
+ continue
+
+ text = getLiteralString(getObject(label))
+ if prefix:
+ text = prefix + ": " + text
+
+ entry += "<li>%s</li>\n" % text
+
+ # entry += '</ul></dd>\n'
+ return entry
+
+
+def specHistoryEntries(m, subject, entries):
for r in findStatements(m, subject, doap.release, None):
release = getObject(r)
revNode = findOne(m, release, doap.revision, None)
if not revNode:
- print "error: doap:release has no doap:revision"
+ print("error: doap:release has no doap:revision")
continue
rev = getLiteralString(getObject(revNode))
created = findOne(m, release, doap.created, None)
- dist = findOne(m, release, doap['file-release'], None)
+ dist = findOne(m, release, doap["file-release"], None)
if dist:
entry = '<dt><a href="%s">Version %s</a>' % (getObject(dist), rev)
else:
- entry = '<dt>Version %s' % rev
- #print "warning: doap:release has no doap:file-release"
+ entry = "<dt>Version %s" % rev
+ # print("warning: doap:release has no doap:file-release")
if created:
- entry += ' (%s)</dt>' % getLiteralString(getObject(created))
+ entry += " (%s)</dt>\n" % getLiteralString(getObject(created))
else:
entry += ' (<span class="warning">EXPERIMENTAL</span>)</dt>'
- changeset = findOne(m, release, dcs.changeset, None)
- if changeset:
- entry += '<dd><ul>'
- for i in findStatements(m, getObject(changeset), dcs.item, None):
- item = getObject(i)
- label = findOne(m, item, rdfs.label, None)
- if not label:
- print "error: dcs:item has no rdfs:label"
- continue
+ entry += "<dd><ul>\n%s" % releaseChangeset(m, release)
- entry += '<li>%s</li>' % getLiteralString(getObject(label))
+ if dist is not None:
+ entries[(getObject(created), getObject(dist))] = entry
- entry += '</ul></dd>\n'
+ return entries
- entries[rev] = entry
+def specHistoryMarkup(entries):
if len(entries) > 0:
- history = '<dl>'
+ history = "<dl>\n"
for e in sorted(entries.keys(), reverse=True):
- history += entries[e]
- history += '</dl>'
+ history += entries[e] + "</ul></dd>"
+ history += "</dl>\n"
return history
else:
- return ''
+ return ""
+
+
+def specHistory(m, subject):
+ return specHistoryMarkup(specHistoryEntries(m, subject, {}))
def specVersion(m, subject):
@@ -962,7 +1141,7 @@ def specVersion(m, subject):
latest_doap_revision = revision
latest_doap_release = getObject(i)
date = ""
- if latest_doap_release != None:
+ if latest_doap_release is not None:
for i in findStatements(m, latest_doap_release, doap.created, None):
date = getLiteralString(getObject(i))
@@ -990,16 +1169,19 @@ def getInstances(model, classes, properties):
if inst not in instances and str(inst) != spec_url:
instances.append(inst)
for i in findStatements(model, None, rdf.type, None):
- if ((not isResource(getSubject(i)))
+ if (
+ (not isResource(getSubject(i)))
or (getSubject(i) in classes)
or (getSubject(i) in instances)
- or (getSubject(i) in properties)):
+ or (getSubject(i) in properties)
+ ):
continue
full_uri = str(getSubject(i))
- if (full_uri.startswith(spec_ns_str)):
+ if full_uri.startswith(spec_ns_str):
instances.append(getSubject(i))
return instances
+
def load_tags(path, docdir):
"Build a (symbol => URI) map from a Doxygen tag file."
@@ -1009,45 +1191,175 @@ def load_tags(path, docdir):
def getChildText(elt, tagname):
"Return the content of the first child node with a certain tag name."
for e in elt.childNodes:
- if e.nodeType == xml.dom.Node.ELEMENT_NODE and e.tagName == tagname:
+ if (
+ e.nodeType == xml.dom.Node.ELEMENT_NODE
+ and e.tagName == tagname
+ ):
return e.firstChild.nodeValue
- return ''
-
- def linkTo(sym, url):
- return '<span><a href="%s/%s">%s</a></span>' % (docdir, url, sym)
-
- tagdoc = xml.dom.minidom.parse(path)
- root = tagdoc.documentElement
+ return ""
+
+ def linkTo(filename, anchor, sym):
+ if anchor:
+ return '<span><a href="%s/%s#%s">%s</a></span>' % (
+ docdir,
+ filename,
+ anchor,
+ sym,
+ )
+ else:
+ return '<span><a href="%s/%s">%s</a></span>' % (
+ docdir,
+ filename,
+ sym,
+ )
+
+ tagdoc = xml.dom.minidom.parse(path)
+ root = tagdoc.documentElement
linkmap = {}
for cn in root.childNodes:
- if (cn.nodeType == xml.dom.Node.ELEMENT_NODE
- and cn.tagName == 'compound'
- and cn.getAttribute('kind') != 'page'):
+ if (
+ cn.nodeType == xml.dom.Node.ELEMENT_NODE
+ and cn.tagName == "compound"
+ and cn.getAttribute("kind") != "page"
+ ):
+
+ name = getChildText(cn, "name")
+ filename = getChildText(cn, "filename")
+ anchor = getChildText(cn, "anchor")
+ if not filename.endswith(".html"):
+ filename += ".html"
+
+ if cn.getAttribute("kind") != "group":
+ linkmap[name] = linkTo(filename, anchor, name)
+
+ prefix = ""
+ if cn.getAttribute("kind") == "struct":
+ prefix = name + "::"
+
+ members = cn.getElementsByTagName("member")
+ for m in members:
+ mname = prefix + getChildText(m, "name")
+ mafile = getChildText(m, "anchorfile")
+ manchor = getChildText(m, "anchor")
+ linkmap[mname] = linkTo(mafile, manchor, mname)
- name = getChildText(cn, 'name')
- filename = getChildText(cn, 'filename')
- if not filename.endswith('.html'):
- filename += '.html'
+ return linkmap
- linkmap[name] = linkTo(name, filename)
- prefix = ''
- if cn.getAttribute('kind') != 'file':
- prefix = name + '::'
+def writeIndex(model, specloc, index_path, root_path, root_uri):
+ # Get extension URI
+ ext_node = model.value(None, rdf.type, lv2.Specification)
+ if not ext_node:
+ ext_node = model.value(None, rdf.type, owl.Ontology)
+ if not ext_node:
+ print("no extension found in %s" % bundle)
+ sys.exit(1)
- members = cn.getElementsByTagName('member')
- for m in members:
- mname = prefix + getChildText(m, 'name')
- mafile = getChildText(m, 'anchorfile')
- manchor = getChildText(m, 'anchor')
- linkmap[mname] = linkTo(
- mname, '%s#%s' % (mafile, manchor))
+ ext = str(ext_node)
- return linkmap
+ # Get version
+ minor = 0
+ micro = 0
+ try:
+ minor = int(model.value(ext_node, lv2.minorVersion, None))
+ micro = int(model.value(ext_node, lv2.microVersion, None))
+ except Exception:
+ print("warning: %s: failed to find version for %s" % (bundle, ext))
+
+ # Get date
+ date = None
+ for r in model.triples([ext_node, doap.release, None]):
+ revision = model.value(r[2], doap.revision, None)
+ if str(revision) == ("%d.%d" % (minor, micro)):
+ date = model.value(r[2], doap.created, None)
+ break
+
+ # Verify that this date is the latest
+ for r in model.triples([ext_node, doap.release, None]):
+ this_date = model.value(r[2], doap.created, None)
+ if this_date > date:
+ print(
+ "warning: %s revision %d.%d (%s) is not the latest release"
+ % (ext_node, minor, micro, date)
+ )
+ break
+
+ # Get name and short description
+ name = model.value(ext_node, doap.name, None)
+ shortdesc = model.value(ext_node, doap.shortdesc, None)
+
+ # Chop 'LV2' prefix from name for cleaner index
+ if name.startswith("LV2 "):
+ name = name[4:]
+
+ # Find relative link target
+ if root_uri and ext_node.startswith(root_uri):
+ target = ext_node[len(root_uri) :] + ".html"
+ else:
+ target = os.path.relpath(ext_node, root_path) + ".html"
+
+ stem = os.path.splitext(os.path.basename(target))[0]
+
+ # Specification (comment is to act as a sort key)
+ row = '<tr><!-- %s --><td><a rel="rdfs:seeAlso" href="%s">%s</a></td>' % (
+ b,
+ target,
+ name,
+ )
+
+ # API
+ row += "<td>"
+ row += '<a rel="rdfs:seeAlso" href="../doc/html/group__%s.html">%s</a>' % (
+ stem,
+ name,
+ )
+ row += "</td>"
+
+ # Description
+ if shortdesc:
+ row += "<td>" + str(shortdesc) + "</td>"
+ else:
+ row += "<td></td>"
-def specgen(specloc, indir, style_uri, docdir, tags, instances=False, offline=False):
+ # Version
+ version_str = "%s.%s" % (minor, micro)
+ if minor == 0 or (micro % 2 != 0):
+ row += '<td><span style="color: red">' + version_str + "</span></td>"
+ else:
+ row += "<td>" + version_str + "</td>"
+
+ # Status
+ deprecated = model.value(ext_node, owl.deprecated, None)
+ if minor == 0:
+ row += '<td><span class="error">Experimental</span></td>'
+ elif deprecated and str(deprecated[2]) != "false":
+ row += '<td><span class="warning">Deprecated</span></td>'
+ elif micro % 2 == 0:
+ row += '<td><span class="success">Stable</span></td>'
+
+ row += "</tr>"
+
+ index = open(index_path, "w")
+ index.write(row)
+ index.close()
+
+
+def specgen(
+ specloc,
+ indir,
+ style_uri,
+ docdir,
+ tags,
+ opts,
+ instances=False,
+ root_link=None,
+ index_path=None,
+ root_path=None,
+ root_uri=None,
+):
"""The meat and potatoes: Everything starts here."""
+ global spec_bundle
global spec_url
global spec_ns_str
global spec_ns
@@ -1056,6 +1368,7 @@ def specgen(specloc, indir, style_uri, docdir, tags, instances=False, offline=Fa
global specgendir
global linkmap
+ spec_bundle = "file://%s/" % os.path.abspath(os.path.dirname(specloc))
specgendir = os.path.abspath(indir)
# Template
@@ -1063,53 +1376,62 @@ def specgen(specloc, indir, style_uri, docdir, tags, instances=False, offline=Fa
template = None
f = open(temploc, "r")
template = f.read()
+ f.close()
# Load code documentation link map from tags file
linkmap = load_tags(tags, docdir)
m = rdflib.ConjunctiveGraph()
- manifest_path = os.path.join(os.path.dirname(specloc), 'manifest.ttl')
+ manifest_path = os.path.join(os.path.dirname(specloc), "manifest.ttl")
if os.path.exists(manifest_path):
- m.parse(manifest_path, format='n3')
- m.parse(specloc, format='n3')
+ m.parse(manifest_path, format="n3")
+ m.parse(specloc, format="n3")
- bundle_path = os.path.split(specloc[specloc.find(':') + 1:])[0]
- abs_bundle_path = os.path.abspath(bundle_path)
spec_url = getOntologyNS(m)
spec = rdflib.URIRef(spec_url)
- # Parse all seeAlso files in the bundle
- for uri in specProperties(m, spec, rdfs.seeAlso):
- if uri[:7] == 'file://':
- path = uri[7:]
- if (path != os.path.abspath(specloc)
- and path.endswith('.ttl')):
- m.parse(path, format='n3')
+ # Load all seeAlso files recursively
+ seeAlso = set()
+ done = False
+ while not done:
+ done = True
+ for uri in specProperties(m, spec, rdfs.seeAlso):
+ if uri[:7] == "file://":
+ path = uri[7:]
+ if (
+ path != os.path.abspath(specloc)
+ and path.endswith("ttl")
+ and path not in seeAlso
+ ):
+ seeAlso.add(path)
+ m.parse(path, format="n3")
+ done = False
spec_ns_str = spec_url
- if (spec_ns_str[-1] != "/" and spec_ns_str[-1] != "#"):
+ if spec_ns_str[-1] != "/" and spec_ns_str[-1] != "#":
spec_ns_str += "#"
spec_ns = rdflib.Namespace(spec_ns_str)
namespaces = getNamespaces(m)
- keys = namespaces.keys()
- keys.sort()
+ keys = sorted(namespaces.keys())
prefixes_html = "<span>"
for i in keys:
uri = namespaces[i]
- if uri.startswith('file:'):
- continue;
+ if uri.startswith("file:"):
+ continue
ns_list[str(uri)] = i
- if (str(uri) == spec_url + '#' or
- str(uri) == spec_url + '/' or
- str(uri) == spec_url):
+ if (
+ str(uri) == spec_url + "#"
+ or str(uri) == spec_url + "/"
+ or str(uri) == spec_url
+ ):
spec_pre = i
prefixes_html += '<a href="%s">%s</a> ' % (uri, i)
prefixes_html += "</span>"
if spec_pre is None:
- print('No namespace prefix for %s defined' % specloc)
+ print("No namespace prefix for %s defined" % specloc)
sys.exit(1)
ns_list[spec_ns_str] = spec_pre
@@ -1120,94 +1442,134 @@ def specgen(specloc, indir, style_uri, docdir, tags, instances=False, offline=Fa
instalist = None
if instances:
- instalist = getInstances(m, classlist, proplist)
- instalist.sort(lambda x, y: cmp(getShortName(x).lower(), getShortName(y).lower()))
+ instalist = sorted(
+ getInstances(m, classlist, proplist),
+ key=lambda x: getShortName(x).lower(),
+ )
azlist = buildIndex(m, classlist, proplist, instalist)
# Generate Term HTML
- termlist = docTerms('Property', proplist, m, classlist, proplist, instalist)
- termlist = docTerms('Class', classlist, m, classlist, proplist, instalist) + termlist
+ classlist = docTerms("Class", classlist, m, classlist, proplist, instalist)
+ proplist = docTerms(
+ "Property", proplist, m, classlist, proplist, instalist
+ )
if instances:
- termlist += docTerms('Instance', instalist, m, classlist, proplist, instalist)
-
- template = template.replace('@NAME@', specProperty(m, spec, doap.name))
- template = template.replace('@SUBTITLE@', specProperty(m, spec, doap.shortdesc))
- template = template.replace('@URI@', spec)
- template = template.replace('@PREFIX@', spec_pre)
- if spec_pre == 'lv2':
- template = template.replace('@XMLNS@', '')
+ instlist = docTerms(
+ "Instance", instalist, m, classlist, proplist, instalist
+ )
+
+ termlist = ""
+ if classlist:
+ termlist += '<div class="section">'
+ termlist += '<h2><a id="ref-classes" />Classes</h2>' + classlist
+ termlist += "</div>"
+
+ if proplist:
+ termlist += '<div class="section">'
+ termlist += '<h2><a id="ref-properties" />Properties</h2>' + proplist
+ termlist += "</div>"
+
+ if instlist:
+ termlist += '<div class="section">'
+ termlist += '<h2><a id="ref-instances" />Instances</h2>' + instlist
+ termlist += "</div>"
+
+ name = specProperty(m, spec, doap.name)
+ title = name
+ if root_link:
+ name = '<a href="%s">%s</a>' % (root_link, name)
+
+ template = template.replace("@TITLE@", title)
+ template = template.replace("@NAME@", name)
+ template = template.replace(
+ "@SHORT_DESC@", specProperty(m, spec, doap.shortdesc)
+ )
+ template = template.replace("@URI@", spec)
+ template = template.replace("@PREFIX@", spec_pre)
+ if spec_pre == "lv2":
+ template = template.replace("@XMLNS@", "")
else:
- template = template.replace('@XMLNS@', ' xmlns:%s="%s"' % (spec_pre, spec_ns_str))
+ template = template.replace(
+ "@XMLNS@", ' xmlns:%s="%s"' % (spec_pre, spec_ns_str)
+ )
filename = os.path.basename(specloc)
- basename = filename[0:filename.rfind('.')]
-
- template = template.replace('@STYLE_URI@', style_uri)
- template = template.replace('@PREFIXES@', str(prefixes_html))
- template = template.replace('@BASE@', spec_ns_str)
- template = template.replace('@AUTHORS@', specAuthors(m, spec))
- template = template.replace('@INDEX@', azlist)
- template = template.replace('@REFERENCE@', termlist)
- template = template.replace('@FILENAME@', filename)
- template = template.replace('@HEADER@', basename + '.h')
- template = template.replace('@MAIL@', 'devel@lists.lv2plug.in')
- template = template.replace('@HISTORY@', specHistory(m, spec))
+ basename = filename[0 : filename.rfind(".")]
+
+ template = template.replace("@STYLE_URI@", style_uri)
+ template = template.replace("@PREFIXES@", str(prefixes_html))
+ template = template.replace("@BASE@", spec_ns_str)
+ template = template.replace("@AUTHORS@", specAuthors(m, spec))
+ template = template.replace("@INDEX@", azlist)
+ template = template.replace("@REFERENCE@", termlist)
+ template = template.replace("@FILENAME@", filename)
+ template = template.replace("@HEADER@", basename + ".h")
+ template = template.replace("@HISTORY@", specHistory(m, spec))
+
+ mail_row = ""
+ if "list_email" in opts:
+ mail_row = '<tr><th>Discuss</th><td><a href="mailto:%s">%s</a>' % (
+ opts["list_email"],
+ opts["list_email"],
+ )
+ if "list_page" in opts:
+ mail_row += ' <a href="%s">(subscribe)</a>' % opts["list_page"]
+ mail_row += "</td></tr>"
+ template = template.replace("@MAIL@", mail_row)
version = specVersion(m, spec) # (minor, micro, date)
date_string = version[2]
if date_string == "":
date_string = "Undated"
- version_string = "%s.%s (%s)" % (version[0], version[1], date_string)
- experimental = (version[0] == 0 or version[1] % 2 == 1)
+ version_string = "%s.%s" % (version[0], version[1])
+ experimental = version[0] == 0 or version[1] % 2 == 1
if experimental:
version_string += ' <span class="warning">EXPERIMENTAL</span>'
- deprecated = findOne(m, rdflib.URIRef(spec_url), owl.deprecated, None)
- if deprecated and str(deprecated[2]).find("true") > 0:
+ if isDeprecated(m, rdflib.URIRef(spec_url)):
version_string += ' <span class="warning">DEPRECATED</span>'
- template = template.replace('@REVISION@', version_string)
+ template = template.replace("@VERSION@", version_string)
- file_list = ''
- see_also_files = specProperties(m, spec, rdfs.seeAlso)
- see_also_files.sort()
- for f in see_also_files:
- uri = str(f)
- if uri[:7] == 'file://':
- uri = uri[7:]
- if uri[:len(abs_bundle_path)] == abs_bundle_path:
- uri = uri[len(abs_bundle_path) + 1:]
- else:
- continue # Skip seeAlso file outside bundle
+ content_links = ""
+ if docdir is not None:
+ content_links = '<li><a href="%s">API</a></li>' % os.path.join(
+ docdir, "group__%s.html" % basename
+ )
+ template = template.replace("@CONTENT_LINKS@", content_links)
- if offline:
- entry = uri
- else:
- entry = '<a href="%s">%s</a>' % (uri, uri)
- if uri.endswith('.h') or uri.endswith('.hpp'):
- name = os.path.basename(uri)
- entry += ' - <a href="%s">Documentation</a> ' % (
- docdir + '/' + name.replace('.', '_8') + '.html')
- file_list += '<li>%s</li>' % entry
- else:
- file_list += '<li>%s</li>' % entry
+ docs = getDetailedDocumentation(
+ m, rdflib.URIRef(spec_url), classlist, proplist, instalist
+ )
+ template = template.replace("@DESCRIPTION@", docs)
- files = ''
- if file_list:
- files += '<li>Files<ul>%s</ul></li>' % file_list
+ now = int(os.environ.get("SOURCE_DATE_EPOCH", time.time()))
+ build_date = datetime.datetime.utcfromtimestamp(now)
+ template = template.replace("@DATE@", build_date.strftime("%F"))
+ template = template.replace("@TIME@", build_date.strftime("%F %H:%M UTC"))
- template = template.replace('@FILES@', files)
+ # Write index row
+ if index_path is not None:
+ writeIndex(m, specloc, index_path, root_path, root_uri)
- comment = getComment(m, rdflib.URIRef(spec_url), classlist, proplist, instalist)
- if comment != '':
- template = template.replace('@COMMENT@', comment)
- else:
- template = template.replace('@COMMENT@', '')
-
- template = template.replace('@TIME@', datetime.datetime.utcnow().strftime('%F %H:%M UTC'))
+ # Validate complete output page
+ try:
+ oldcwd = os.getcwd()
+ os.chdir(specgendir)
+ etree.fromstring(
+ template.replace(
+ '"http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd"',
+ '"DTD/xhtml-rdfa-1.dtd"',
+ ).encode("utf-8"),
+ etree.XMLParser(dtd_validation=True, no_network=True),
+ )
+ except Exception as e:
+ sys.stderr.write("error: Validation failed for %s: %s" % (specloc, e))
+ finally:
+ os.chdir(oldcwd)
return template
@@ -1215,7 +1577,7 @@ def specgen(specloc, indir, style_uri, docdir, tags, instances=False, offline=Fa
def save(path, text):
try:
f = open(path, "w")
- f.write(text.encode("utf-8"))
+ f.write(text)
f.flush()
f.close()
except Exception:
@@ -1227,7 +1589,7 @@ def getNamespaces(m):
"""Return a prefix:URI dictionary of all namespaces seen during parsing"""
nspaces = {}
for prefix, uri in m.namespaces():
- if not re.match('default[0-9]*', prefix) and not prefix == 'xml':
+ if not re.match("default[0-9]*", prefix) and not prefix == "xml":
# Skip silly default namespaces added by rdflib
nspaces[prefix] = uri
return nspaces
@@ -1242,7 +1604,7 @@ def getOntologyNS(m):
if not isBlank(getSubject(s)):
ns = str(getSubject(s))
- if (ns == None):
+ if ns is None:
sys.exit("Impossible to get ontology's namespace")
else:
return ns
@@ -1250,53 +1612,157 @@ def getOntologyNS(m):
def usage():
script = os.path.basename(sys.argv[0])
- print("""Usage: %s ONTOLOGY INDIR STYLE OUTPUT [DOCDIR TAGS] [FLAGS]
-
- ONTOLOGY : Path to ontology file
- INDIR : Input directory containing template.html and style.css
- STYLE : Stylesheet URI
- OUTPUT : HTML output path
- DOCDIR : Doxygen HTML directory
- TAGS : Doxygen tags file
-
- Optional flags:
- -i : Document class/property instances (disabled by default)
- -p PREFIX : Set ontology namespace prefix from command line
-
-Example:
- %s lv2_foos.ttl template.html style.css lv2_foos.html ../doc -i -p foos
-""" % (script, script))
- sys.exit(-1)
+ return "Usage: %s ONTOLOGY_TTL OUTPUT_HTML [OPTION]..." % script
if __name__ == "__main__":
"""Ontology specification generator tool"""
- args = sys.argv[1:]
- if (len(args) < 3):
- usage()
- else:
- ontology = "file:" + str(args[0])
- indir = args[1]
- style = args[2]
- output = args[3]
- docdir = None
- tags = None
- if len(args) > 5:
- docdir = args[4]
- tags = args[5]
-
- # Flags
- instances = False
- if len(args) > 6:
- flags = args[6:]
- i = 0
- while i < len(flags):
- if flags[i] == '-i':
- instances = True
- elif flags[i] == '-p':
- spec_pre = flags[i + 1]
- i += 1
- i += 1
-
- save(output, specgen(ontology, indir, style, docdir, tags, instances=instances))
+ indir = os.path.abspath(os.path.dirname(sys.argv[0]))
+ if not os.path.exists(os.path.join(indir, "template.html")):
+ indir = os.path.join(os.path.dirname(indir), "share", "lv2specgen")
+
+ opt = optparse.OptionParser(
+ usage=usage(),
+ description="Write HTML documentation for an RDF ontology.",
+ )
+ opt.add_option(
+ "--list-email",
+ type="string",
+ dest="list_email",
+ help="Mailing list email address",
+ )
+ opt.add_option(
+ "--list-page",
+ type="string",
+ dest="list_page",
+ help="Mailing list info page address",
+ )
+ opt.add_option(
+ "--template-dir",
+ type="string",
+ dest="template_dir",
+ default=indir,
+ help="Template directory",
+ )
+ opt.add_option(
+ "--style-uri",
+ type="string",
+ dest="style_uri",
+ default="style.css",
+ help="Stylesheet URI",
+ )
+ opt.add_option(
+ "--docdir",
+ type="string",
+ dest="docdir",
+ default=None,
+ help="Doxygen output directory",
+ )
+ opt.add_option(
+ "--index",
+ type="string",
+ dest="index_path",
+ default=None,
+ help="Index row output file",
+ )
+ opt.add_option(
+ "--tags",
+ type="string",
+ dest="tags",
+ default=None,
+ help="Doxygen tags file",
+ )
+ opt.add_option(
+ "-r",
+ "--root-path",
+ type="string",
+ dest="root_path",
+ default="",
+ help="Root path",
+ )
+ opt.add_option(
+ "-R",
+ "--root-uri",
+ type="string",
+ dest="root_uri",
+ default="",
+ help="Root URI",
+ )
+ opt.add_option(
+ "-p",
+ "--prefix",
+ type="string",
+ dest="prefix",
+ help="Specification Turtle prefix",
+ )
+ opt.add_option(
+ "-i",
+ "--instances",
+ action="store_true",
+ dest="instances",
+ help="Document instances",
+ )
+ opt.add_option(
+ "--copy-style",
+ action="store_true",
+ dest="copy_style",
+ help="Copy style from template directory to output directory",
+ )
+
+ (options, args) = opt.parse_args()
+ opts = vars(options)
+
+ if len(args) < 2:
+ opt.print_help()
+ sys.exit(-1)
+
+ spec_pre = options.prefix
+ ontology = "file:" + str(args[0])
+ output = args[1]
+ index_path = options.index_path
+ docdir = options.docdir
+ tags = options.tags
+
+ out = "."
+ spec = args[0]
+ path = os.path.dirname(spec)
+ outdir = os.path.abspath(os.path.join(out, path))
+
+ bundle = str(outdir)
+ b = os.path.basename(outdir)
+
+ if not os.access(os.path.abspath(spec), os.R_OK):
+ print("warning: extension %s has no %s.ttl file" % (b, b))
+ sys.exit(1)
+
+ # Root link
+ root_path = opts["root_path"]
+ root_uri = opts["root_uri"]
+ root_link = os.path.join(root_path, "index.html")
+
+ # Generate spec documentation
+ specdoc = specgen(
+ spec,
+ indir,
+ opts["style_uri"],
+ docdir,
+ tags,
+ opts,
+ instances=True,
+ root_link=root_link,
+ index_path=index_path,
+ root_path=root_path,
+ root_uri=root_uri,
+ )
+
+ # Save to HTML output file
+ save(output, specdoc)
+
+ if opts["copy_style"]:
+ import shutil
+
+ shutil.copyfile(
+ os.path.join(indir, "style.css"),
+ os.path.join(os.path.dirname(output), "style.css"),
+ )