mirror of
https://github.com/espruino/Espruino.git
synced 2025-12-08 19:06:15 +00:00
468 lines
17 KiB
Python
Executable File
468 lines
17 KiB
Python
Executable File
#!/usr/bin/env python2.7
|
|
|
|
# This file is part of Espruino, a JavaScript interpreter for Microcontrollers
|
|
#
|
|
# Copyright (C) 2013 Gordon Williams <gw@pur3.co.uk>
|
|
#
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
#
|
|
# ----------------------------------------------------------------------------------------
|
|
# Builds HTML documentation for functions from the JSON included in comments in
|
|
# jswrap_*.c files
|
|
# ----------------------------------------------------------------------------------------
|
|
|
|
# Needs:
|
|
# pip install markdown
|
|
# pip install markdown-urlize
|
|
#
|
|
# See common.py -> get_jsondata for command line options
|
|
|
|
import subprocess;
|
|
import re;
|
|
import json;
|
|
import sys;
|
|
import os;
|
|
import common
|
|
import urllib2
|
|
import markdown
|
|
import htmlentitydefs
|
|
|
|
sys.path.append(".");
|
|
scriptdir = os.path.dirname(os.path.realpath(__file__))
|
|
basedir = scriptdir+"/../"
|
|
sys.path.append(basedir+"scripts");
|
|
sys.path.append(basedir+"boards");
|
|
|
|
# Scans files for comments of the form /*JSON......*/ and then writes out an HTML file describing
|
|
# all the functions
|
|
|
|
# htmldev default is False
|
|
# if htmldev is set to True indicating 'html development mode', script:
|
|
# - creates standalone html version for temporary html generation development by:
|
|
# --- inserting a searchbox text input to test top link function
|
|
# --- skipping MDN links dump and validation to complete quicker
|
|
|
|
htmldev = False
|
|
|
|
jsondatas = common.get_jsondata(True)
|
|
|
|
classes = [] # list of class names
|
|
libraries = []
|
|
for jsondata in jsondatas:
|
|
if "class" in jsondata:
|
|
if not jsondata["class"] in classes:
|
|
classes.append(jsondata["class"])
|
|
if jsondata["type"]=="library":
|
|
if not jsondata["class"] in libraries:
|
|
libraries.append(jsondata["class"])
|
|
|
|
# Load list of 'uses' in EspruinoDocs
|
|
code_uses = []
|
|
referenceFile = "../EspruinoDocs/references.json"
|
|
if os.path.isfile(referenceFile):
|
|
print("Found references.json - using this to link to examples")
|
|
code_uses = json.loads(open(referenceFile, "r").read())
|
|
|
|
# Load list of MDN URLs (to speed up processing)
|
|
valid_mdn_urls = { 'valid' : [], 'invalid' : [] };
|
|
mdnURLFile = "MDN_URLS.txt"
|
|
if os.path.isfile(mdnURLFile):
|
|
valid_mdn_urls = json.loads(open(mdnURLFile, "r").read())
|
|
|
|
# start writing
|
|
htmlFile = open('functions.html', 'w')
|
|
def html(s):
|
|
print(s);
|
|
htmlFile.write(s.encode('utf-8')+"\n");
|
|
|
|
def htmlify(d,current):
|
|
d = markdown.markdown(d, extensions=['mdx_urlize'], tab_length=2)
|
|
# replace <code> with newlines with pre
|
|
idx = d.find("<code>")
|
|
end = d.find("</code>", idx)
|
|
while idx>=0 and end>idx:
|
|
codeBlock = d[idx+6:end]
|
|
# search for known links in code
|
|
if codeBlock[-2:]=="()" and codeBlock[:-2] in links:
|
|
codeBlock = "<a href=\"#"+links[codeBlock[:-2]]+"\">"+codeBlock+"</a>";
|
|
elif codeBlock in links:
|
|
codeBlock = "<a href=\"#"+links[codeBlock]+"\">"+codeBlock+"</a>";
|
|
# ensure multi-line code is handled correctly
|
|
codeBlock = "<code>"+codeBlock+"</code>";
|
|
if codeBlock.find("\n")>=0:
|
|
codeBlock = "<pre>"+codeBlock+"</pre>"
|
|
d = d[0:idx]+codeBlock+d[end+7:];
|
|
# search again
|
|
idx = d.find("<code>", end)
|
|
end = d.find("</code>", idx)
|
|
return d;
|
|
|
|
def html_description(d,current):
|
|
if isinstance(d, list): d = "\n".join(d)
|
|
d = htmlify(d,current)
|
|
html("<div class=\"description\">\n" + d + "\n</div>\n")
|
|
|
|
def get_prefixed_name(jsondata):
|
|
s=""
|
|
if "class" in jsondata:
|
|
s=s+jsondata["class"]+"."
|
|
s=s+jsondata["name"]
|
|
return s
|
|
|
|
def get_fullname(jsondata):
|
|
s = common.get_prefix_name(jsondata)
|
|
if s!="": s = s + " "
|
|
if jsondata["type"]!="constructor":
|
|
if "class" in jsondata:
|
|
s=s+jsondata["class"]+"."
|
|
s=s+jsondata["name"]
|
|
return s
|
|
|
|
def get_arguments(jsondata):
|
|
if common.is_property(jsondata):
|
|
return ""
|
|
args = [];
|
|
if "params" in jsondata:
|
|
for param in jsondata["params"]:
|
|
args.append(param[0]);
|
|
if param[1]=="JsVarArray": args.append("...");
|
|
return "("+", ".join(args)+")"
|
|
|
|
def get_surround(jsondata):
|
|
s = common.get_prefix_name(jsondata)
|
|
if s!="": s = s + " "
|
|
if jsondata["type"]!="constructor":
|
|
if "class" in jsondata:
|
|
if jsondata["class"] in libraries: s=s+"require(\""+jsondata["class"]+"\")."
|
|
else: s=s+jsondata["class"]+"."
|
|
s=s+jsondata["name"]
|
|
if jsondata["type"]!="object":
|
|
s=s+get_arguments(jsondata)
|
|
return s
|
|
|
|
def get_code(jsondata):
|
|
if jsondata["type"]=="event":
|
|
return jsondata["class"]+".on('"+jsondata["name"]+"', function"+get_arguments(jsondata)+" { ... });";
|
|
if jsondata["type"]=="constructor":
|
|
return "new "+jsondata["name"]+get_arguments(jsondata);
|
|
return get_surround(jsondata)
|
|
|
|
def get_link(jsondata):
|
|
s="l_";
|
|
if "class" in jsondata:
|
|
s=s+jsondata["class"]+"_"
|
|
else:
|
|
s=s+"_global_"
|
|
s=s+jsondata["name"]
|
|
return s
|
|
|
|
def replace_with_ifdef_description(m):
|
|
contents = m.group(1)
|
|
return common.get_ifdef_description(contents)
|
|
|
|
def html_escape(text):
|
|
escaped_chars = ""
|
|
for c in text:
|
|
if (ord(c) < 32) or (ord(c) > 126):
|
|
c = '&{};'.format(htmlentitydefs.codepoint2name[ord(c)])
|
|
escaped_chars = escaped_chars + c
|
|
return escaped_chars
|
|
|
|
# If MDN doesn't 404 then include a link to it
|
|
def insert_mdn_link(jsondata):
|
|
if "class" in jsondata and "name" in jsondata:
|
|
url = "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/"+jsondata["class"]
|
|
if jsondata["type"]!="constructor": url = url +"/"+jsondata["name"]
|
|
if url in valid_mdn_urls['valid']:
|
|
code = 200
|
|
elif url in valid_mdn_urls['invalid']:
|
|
code = 404
|
|
else:
|
|
print("Checking URL "+url)
|
|
try:
|
|
connection = urllib2.urlopen(url)
|
|
code = connection.getcode()
|
|
connection.close()
|
|
except urllib2.HTTPError, e:
|
|
code = e.getcode()
|
|
if code==200: valid_mdn_urls['valid'].append(url)
|
|
else: valid_mdn_urls['invalid'].append(url)
|
|
if code==200:
|
|
html("<p><a href=\""+url+"\">View MDN documentation</a></p>")
|
|
|
|
|
|
# Remove duplicates
|
|
existingSymbols = {}
|
|
unduplicatedjsondatas = []
|
|
for jsondata in jsondatas:
|
|
duplicate = False
|
|
if "name" in jsondata:
|
|
n = get_fullname(jsondata)
|
|
if n in existingSymbols:
|
|
print("WARNING: Duplicate symbol "+n);
|
|
duplicate = True
|
|
existingSymbols[n] = jsondata
|
|
if not duplicate: unduplicatedjsondatas.append(jsondata)
|
|
jsondatas = unduplicatedjsondatas
|
|
|
|
title = ""
|
|
if common.board:
|
|
title = common.board.info["name"]+" Software Reference"
|
|
else:
|
|
title = "Espruino Software Reference"
|
|
|
|
html("<html>")
|
|
html(" <head>")
|
|
html(" <title>"+title+"</title>")
|
|
html(" <style>")
|
|
html(" body { font: 71%/1.5em Verdana, 'Trebuchet MS', Arial, Sans-serif; color: #666666; }")
|
|
html(" h1, h2, h3, h4 { color: #000000; margin-left: 0px; }")
|
|
html(" h4 { padding-left: 20px; }")
|
|
html(" ul { list-style-position: inside; }")
|
|
html(" .class { page-break-before: always; width:95%; border-top: 1px solid black; border-bottom: 1px solid black; padding-top: 20px; padding-bottom: 20px; margin-top: 50px; }")
|
|
html(" .instance { font-weight: bold; }");
|
|
html(" .detail { width:90%; border-bottom: 1px solid black; margin-top: 50px; }")
|
|
html(" .githublink { text-decoration:none; color:#CCC; }")
|
|
html(" .top { float:right; }")
|
|
html(" .call { padding-left: 50px; }")
|
|
html(" .description { padding-left: 50px; }")
|
|
html(" .param { padding-left: 50px; }")
|
|
html(" .return { padding-left: 50px; }")
|
|
html(" .examples { padding-left: 50px; }")
|
|
html(" .blush {")
|
|
html(" -webkit-transition: background 0.75s ease-out;")
|
|
html(" -moz-transition: background 0.75s ease-out;")
|
|
html(" -ms-transition: background 0.75s ease-out;")
|
|
html(" -o-transition: background 0.75s ease-out;")
|
|
html(" transition: background 0.75s ease-out;")
|
|
html(" }")
|
|
html(" .fwrdblush { background-color: gold; }")
|
|
html(" .bwrdblush { background-color: lightblue; }")
|
|
html(" </style>")
|
|
html(" <script>function vpos(nme,f) {")
|
|
html(" }")
|
|
html(" function blush(node){")
|
|
html(" var clazz = (node.name && node.name.substr(0,2) === \"t_\") ? \"bwrdblush\" : \"fwrdblush\";")
|
|
html(" node.className = node.className + \" \" + clazz;")
|
|
html(" setTimeout(function(){ node.className = node.className.replace(new RegExp(\"\\s*\" + clazz + \"\\s*\",\"g\"),\"\"); },750);")
|
|
html(" }")
|
|
html(" function place(nme){")
|
|
html(" var ns = document.getElementsByName(nme);")
|
|
html(" if (ns.length > 0) {")
|
|
html(" var n = ns[0], t = 0 - Math.floor(window.innerHeight * 0.2);")
|
|
html(" if (n.offsetParent) { do { t += n.offsetTop; } while ( n = n.offsetParent) }")
|
|
html(" blush(ns[0]);")
|
|
html(" setTimeout(function(){ window.scroll(0,(t < 0) ? 0 : t); },10);")
|
|
html(" }")
|
|
html(" return true;")
|
|
html(" }")
|
|
html(" function toppos(){")
|
|
html(" document.location=\"#top\"")
|
|
html(" var ns = document.getElementsByName(\"searchbox\"), n;")
|
|
html(" if ((ns.length > 0) && (typeof (n = ns[0]).value !== \"undefined\")) {")
|
|
html(" n.focus(); n.select();")
|
|
html(" }")
|
|
html(" }</script>")
|
|
html(" </head>")
|
|
html(" <body>")
|
|
html(" <h1>"+title+"</h1>")
|
|
html(" <p style=\"text-align:right;\">Version "+common.get_version()+"</p>")
|
|
|
|
if htmldev == True:
|
|
html(" <div>")
|
|
html(" <input class=\"searchbox\" name=\"searchbox\" size=\"60\" type=\"text\" autocomplete=\"off\">");
|
|
html(" </div>")
|
|
|
|
detail = []
|
|
links = {}
|
|
def add_link(jsondata):
|
|
if not "no_create_links" in jsondata:
|
|
link = get_prefixed_name(jsondata);
|
|
if link!="global":
|
|
links[link] = get_link(jsondata)
|
|
jsondatas = sorted(jsondatas, key=lambda s: common.get_name_or_space(s).lower())
|
|
|
|
html(' <div id="contents">')
|
|
html(" <h2><a name=\"contents\"></a>Contents</h2>")
|
|
html(" <ul>")
|
|
html(" <li><a class=\"blush\" name=\"t__global\" href=\"#_global\" onclick=\"place('_global');\">Globals</a></li>")
|
|
for jsondata in jsondatas:
|
|
if "name" in jsondata and not "class" in jsondata:
|
|
add_link(jsondata)
|
|
detail.append(jsondata)
|
|
for className in sorted(classes, key=lambda s: s.lower()):
|
|
html(" <li><a class=\"blush\" name=\"t_"+className+"\" href=\"#"+className+"\" onclick=\"place('"+className+"');\">"+className+"</a></li>")
|
|
links[className] = className
|
|
for jsondata in jsondatas:
|
|
if "name" in jsondata and "class" in jsondata and jsondata["class"]==className:
|
|
add_link(jsondata)
|
|
detail.append(jsondata)
|
|
html(" </ul>")
|
|
html(' </div><!-- Contents -->')
|
|
|
|
html(" <a class=\"blush\" name=\"top\"\>");
|
|
#html(" <h2>Detail</h2>")
|
|
lastClass = "XXX"
|
|
for jsondata in detail:
|
|
className = ""
|
|
niceName = ""
|
|
linkName = ""
|
|
if "class" in jsondata:
|
|
className=jsondata["class"]
|
|
if className in libraries:
|
|
niceName=className+" Library"
|
|
else:
|
|
niceName=className+" Class"
|
|
linkName=className
|
|
else:
|
|
className=""
|
|
niceName="Globals"
|
|
linkName="_global"
|
|
|
|
# If we're on to a different class now, put up the heading for it
|
|
if className!=lastClass:
|
|
lastClass=className
|
|
html("<h2 class=\"class\"><a class=\"blush\" name=\""+linkName+"\" href=\"#t_"+linkName+"\" onclick=\"place('t_"+linkName+"');\">"+niceName+"</a></h2>")
|
|
html(" <p class=\"top\"><a href=\"javascript:toppos();\">(top)</a></p>")
|
|
for j in jsondatas:
|
|
if (j["type"]=="class" or j["type"]=="library") and j["class"]==className and "description" in j:
|
|
html_description(j["description"], className)
|
|
|
|
instances = []
|
|
for j in jsondatas:
|
|
if "instanceof" in j and j["instanceof"]==className:
|
|
instances.append(j)
|
|
if len(instances)>0:
|
|
html(" <h4>Instances</h4>")
|
|
text = ""
|
|
for j in instances:
|
|
text = text + " * [`"+j["name"]+"`](#l__global_"+j["name"]+")";
|
|
if "description" in j:
|
|
text = text + " " + j["description"].split("\n")[0]
|
|
text = text + "\n"
|
|
html_description(text, "")
|
|
|
|
html(" <h4>Methods and Fields</h4>")
|
|
html(" <ul>")
|
|
for j in jsondatas:
|
|
if ("name" in j) and ((className=="" and not "class" in j) or ("class" in j and j["class"]==className)):
|
|
link = get_link(j)
|
|
html(" <li><a name=\"t_"+link+"\" href=\"#"+link+"\">"+get_surround(j)+"</a></li>")
|
|
html(" </ul>")
|
|
|
|
# Otherwise just output detail
|
|
link = get_link(jsondata)
|
|
html(" <h3 class=\"detail\"><a class=\"blush\" name=\""+link+"\" href=\"#t_"+link+"\" onclick=\"place('t_"+link+"','"+linkName+"');\">"+get_fullname(jsondata)+"</a>")
|
|
#html("<!-- "+json.dumps(jsondata, sort_keys=True, indent=2)+"-->");
|
|
if "githublink" in jsondata:
|
|
html('<a class="githublink" title="Link to source code on GitHub" href="'+jsondata["githublink"]+'">⇒</a>');
|
|
html("</h3>")
|
|
insert_mdn_link(jsondata);
|
|
html(" <p class=\"top\"><a href=\"javascript:toppos();\">(top)</a></p>")
|
|
if jsondata["type"]!="object":
|
|
html(" <h4>Call type:</h4>")
|
|
html(" <div class=\"call\"><code>"+get_code(jsondata)+"</code></div>")
|
|
elif "instanceof" in jsondata:
|
|
html(" <h4>Instance of <a href=\"#"+jsondata["instanceof"]+"\"><code>"+jsondata["instanceof"]+"</code></a>")
|
|
if "params" in jsondata:
|
|
html(" <h4>Parameters</h4>")
|
|
for param in jsondata["params"]:
|
|
desc = ""
|
|
if len(param)>2: desc=param[2]
|
|
if isinstance(desc, list): desc = '<br/>'.join(desc)
|
|
extra = ""
|
|
if param[1]=="JsVarArray": extra = ", ...";
|
|
html(" <div class=\"param\">"+htmlify("`"+param[0]+extra+"` - "+desc,"")+"</div>")
|
|
if "return" in jsondata:
|
|
html(" <h4>Returns</h4>")
|
|
desc = ""
|
|
if len(jsondata["return"])>1: desc=jsondata["return"][1]
|
|
if desc=="": desc="See description above"
|
|
html(" <div class=\"return\">"+htmlify(desc,"")+"</div>")
|
|
if "description" in jsondata:
|
|
html(" <h4>Description</h4>")
|
|
desc = jsondata["description"]
|
|
if not isinstance(desc, list): desc = [ desc ]
|
|
if "ifdef" in jsondata:
|
|
desc.append("\n\n**Note:** This is only available in "+common.get_ifdef_description(jsondata["ifdef"]));
|
|
if "ifndef" in jsondata:
|
|
desc.append("\n\n**Note:** This is not available in "+common.get_ifdef_description(jsondata["ifndef"]));
|
|
if "#if" in jsondata:
|
|
d = jsondata["#if"];
|
|
dprefix = "This is only available in ";
|
|
if re.match('^!defined\((.+?)\) && !defined\((.+?)\)$', d):
|
|
dprefix = "This is not available in ";
|
|
d = re.sub('^!defined\((.+?)\) && !defined\((.+?)\)$', "defined(\\1) or defined(\\2)", d)
|
|
if re.match('^!defined\((.+?)\)$', d):
|
|
dprefix = "This is not available in ";
|
|
d = re.sub('^!defined\((.+?)\)$', "defined(\\1)", d)
|
|
d = re.sub('!defined\(', "not defined(", d)
|
|
d = d.replace("||", " and ").replace("&&", " with ")
|
|
d = re.sub('defined\((.+?)\)', replace_with_ifdef_description, d)
|
|
d = re.sub('(.*)_COUNT>=(.*)', "devices with more than \\2 \\1 peripherals", d)
|
|
desc.append("\n\n**Note:** "+dprefix+d);
|
|
html_description(desc, jsondata["name"])
|
|
|
|
|
|
url = "http://www.espruino.com/Reference#"+get_link(jsondata)
|
|
if url in code_uses:
|
|
uses = code_uses[url]
|
|
html(" <h4>Examples</h4>")
|
|
html(" <p class=\"examples\">This function is used in the following places in Espruino's documentation</p>")
|
|
html(" <ul class=\"examples\">")
|
|
for link in uses:
|
|
html(' <li><a href="'+link["url"]+'">'+html_escape(link["title"])+'</a></li>')
|
|
html(" </ul>")
|
|
|
|
html(" </body>")
|
|
html("</html>")
|
|
|
|
# --------------------------------------------------------------------------
|
|
# Write/create keywords
|
|
# --------------------------------------------------------------------------
|
|
keywords = {}
|
|
for j in jsondatas:
|
|
jkeywords = []
|
|
if ("name" in j):
|
|
item = { "title" : get_surround(j), "path": "/Reference#"+get_link(j) };
|
|
jkeywords = [ j["name"] ]
|
|
if get_prefixed_name(j)!=j["name"]: jkeywords.append(get_prefixed_name(j))
|
|
if "class" in j: jkeywords.append(j["class"])
|
|
elif j["type"]=="library":
|
|
item = { "title" : j["class"]+" Library", "path": "/Reference#"+j["class"] };
|
|
jkeywords = [ j["class"] ]
|
|
elif j["type"]=="class":
|
|
item = { "title" : j["class"]+" Class", "path": "/Reference#"+j["class"] };
|
|
jkeywords = [ j["class"] ]
|
|
|
|
for k in jkeywords:
|
|
k = k.lower()
|
|
if not k in keywords:
|
|
keywords[k] = [ item ]
|
|
else:
|
|
keywords[k].append(item)
|
|
|
|
#print(json.dumps(keywords, sort_keys=True, indent=2))
|
|
keywordFile = open('function_keywords.js', 'w')
|
|
keywordFile.write(json.dumps(keywords, sort_keys=True, indent=2));
|
|
|
|
|
|
# ---------------------------- Just random helper stuff
|
|
builtins = []
|
|
for jsondata in jsondatas:
|
|
if jsondata["type"]=="staticmethod" or jsondata["type"]=="staticproperty" or jsondata["type"]=="class":
|
|
if not jsondata["class"] in builtins:
|
|
builtins.append(jsondata["class"])
|
|
elif jsondata["type"]=="function" or jsondata["type"]=="variable" or jsondata["type"]=="class":
|
|
if not jsondata["name"] in builtins:
|
|
builtins.append(jsondata["name"])
|
|
print("------------------------------------------------------")
|
|
print('Global classes and functions: '+' '.join(builtins));
|
|
print("------------------------------------------------------")
|
|
|
|
# Writing MDN URL file
|
|
if htmldev == False:
|
|
open(mdnURLFile, "w").write(json.dumps(valid_mdn_urls, sort_keys=True, indent=2))
|