codon/docs/docgen.py

228 lines
8.0 KiB
Python
Executable File

#%%
import json
import itertools
import os
import os.path
import sys
import subprocess as sp
import collections
from pprint import pprint
from sphinx.ext.napoleon.docstring import GoogleDocstring
from sphinx.ext.napoleon import Config
napoleon_config=Config(napoleon_use_param=True,napoleon_use_rtype=True)
json_path=os.path.abspath(sys.argv[1])
out_path=os.path.abspath(sys.argv[2])
roots=sys.argv[3:]
print(f"Generating documentation for {json_path}...")
with open(json_path) as f:
j=json.load(f)
print(f"Load done!")
# sys.exit(0)
# with open('x.json','w') as f:
# json.dump(j,f,indent=2)
# 2. Get the list of modules and create the documentation tree
modules={k:v["path"] for k,v in j.items() if v["kind"]=="module"}
prefix=os.path.commonprefix(list(modules.values()))
parsed_modules=collections.defaultdict(set)
# os.system("rm -rf stdlib/*")
root=""
for mid,module in modules.items():
while module not in roots:
directory,name=os.path.split(module)
directory=os.path.relpath(directory,root) # remove the prefix
os.makedirs(f"{out_path}/{directory}",exist_ok=True)
if name.endswith('.codon'):
name=name[:-6] # drop suffix
if name!='__init__':
parsed_modules[directory].add((name,mid))
print(root,mid,module, '->',name)
module=os.path.split(module)[0]
print(f"Module read done!")
for directory,modules in parsed_modules.items():
module=directory.replace('/','.')
with open(f'{out_path}/{directory}/index.rst','w') as f:
if module!='.':
print(f".. codon:module:: {module}\n",file=f)
print(f"{module}",file=f)
else:
print("Standard Library Reference",file=f)
print(f"========\n",file=f)
print(".. toctree::\n",file=f)
for m in sorted(set(m for m,_ in modules)):
if os.path.isdir(f'{root}/{directory}/{m}'):
print(f" {m}/index",file=f)
else:
print(f" {m}",file=f)
print(f" - Done with directory tree")
def parse_docstr(s,level=1):
"""Parse docstr s and indent it with level spaces"""
lines=GoogleDocstring(s,napoleon_config).lines()
if isinstance(lines,str): # Napoleon failed
s=s.split('\n')
while s and s[0]=='':
s=s[1:]
while s and s[-1]=='':
s=s[:-1]
if not s:
return ''
i=0
indent=len(list(itertools.takewhile(lambda i:i==' ',s[0])))
lines=[l[indent:] for l in s]
return '\n'.join((' '*level)+l for l in lines)
def parse_type(a):
"""Parse type signature"""
if not a:
return ''
s=''
if isinstance(a,list):
head,tail=a[0],a[1:]
else:
head,tail=a,[]
if head not in j:
return '?'
s+=j[head]["name"] if head[0].isdigit() else head
if tail:
for ti,t in enumerate(tail):
s+="[" if not ti else ", "
s+=parse_type(t)
s+="]"
return s
def parse_fn(v,skip_self=False,skip_braces=False):
"""Parse function signature after the name"""
s=""
if 'generics' in v and v['generics']:
s+=f'[{", ".join(v["generics"])}]'
if not skip_braces:
s+="("
cnt=0
for ai,a in enumerate(v['args']):
if ai==0 and a["name"]=="self" and skip_self:
continue
s+="" if not cnt else ", "
cnt+=1
s+=f'{a["name"]}'
if "type" in a:
print(a)
s+=" : "+parse_type(a["type"])
if "default" in a:
s+=" = "+a["default"]+""
if not skip_braces:
s+=')'
if "ret" in v:
s+=" -> "+parse_type(v["ret"])
# if "extern" in v:
# s += f" (_{v['extern']} function_)"
# s += "\n"
return s
# 3. Create documentation for each module
for directory,(name,mid) in {(d,m) for d,mm in parsed_modules.items() for m in mm}:
module=directory.replace('/','.')+f".{name}"
file,mode=f'{out_path}/{directory}/{name}.rst','w'
if os.path.isdir(f'{root}/{directory}/{name}'):
continue
if name=='__init__':
file,mode=f'{out_path}/{directory}/index.rst','a'
with open(file,mode) as f:
print(f".. codon:module:: {module}\n",file=f)
print(f":codon:mod:`{module}`",file=f)
print("-"*(len(module)+13)+"\n",file=f)
directory_prefix=directory+'/' if directory!='.' else ''
print(f"Source code: `{directory_prefix}{name}.codon <https://github.com/exaloop/codon/blob/master/stdlib/{directory}/{name}.codon>`_\n",file=f)
if 'doc' in j[mid]:
print(parse_docstr(j[mid]['doc']),file=f)
for i in j[mid]['children']:
v=j[i]
if v['kind']=='class' and v['type']=='extension':
v['name']=j[v['parent']]['name']
if v['name'].startswith('_'):
continue
if v['kind']=='class':
if v['name'].endswith('Error'):
v["type"]="exception"
f.write(f'.. codon:{v["type"]}:: {v["name"]}')
if 'generics' in v and v['generics']:
f.write(f'[{",".join(v["generics"])}]')
elif v['kind']=='function':
f.write(f'.. codon:function:: {v["name"]}{parse_fn(v)}')
elif v['kind']=='variable':
f.write(f'.. codon:data:: {v["name"]}')
# if v['kind'] == 'class' and v['type'] == 'extension':
# f.write(f'**`{getLink(v["parent"])}`**')
# else:
# f.write(f'{m}.**`{v["name"]}`**')
f.write("\n")
# f.write("\n")
# if v['kind'] == 'function' and 'attrs' in v and v['attrs']:
# f.write("**Attributes:**" + ', '.join(f'`{x}`' for x in v['attrs']))
# f.write("\n")
if 'doc' in v:
f.write("\n"+parse_docstr(v['doc'])+"\n")
f.write("\n")
if v['kind']=='class':
# if 'args' in v and any(c['name'][0] != '_' for c in v['args']):
# f.write('#### Arguments:\n')
# for c in v['args']:
# if c['name'][0] == '_':
# continue
# f.write(f'- **`{c["name"]} : `**')
# f.write(parse_type(c["type"]) + "\n")
# if 'doc' in c:
# f.write(parse_docstr(c['doc'], 1) + "\n")
# f.write("\n")
mt=[c for c in v['members'] if j[c]['kind']=='function']
props=[c for c in mt if 'property' in j[c].get('attrs',[])]
if props:
print(' **Properties:**\n',file=f)
for c in props:
v=j[c]
f.write(f' .. codon:attribute:: {v["name"]}\n')
if 'doc' in v:
f.write("\n"+parse_docstr(v['doc'],4)+"\n\n")
f.write("\n")
magics=[c for c in mt if len(j[c]['name'])>4 and j[c]['name'].startswith('__') and j[c]['name'].endswith('__')]
if magics:
print(' **Magic methods:**\n',file=f)
for c in magics:
v=j[c]
f.write(f' .. codon:method:: {v["name"]}{parse_fn(v,True)}\n')
f.write(' :noindex:\n')
if 'doc' in v:
f.write("\n"+parse_docstr(v['doc'],4)+"\n\n")
f.write("\n")
methods=[c for c in mt if j[c]['name'][0]!='_' and c not in props]
if methods:
print(' **Methods:**\n',file=f)
for c in methods:
v=j[c]
f.write(f' .. codon:method:: {v["name"]}{parse_fn(v,True)}\n')
if 'doc' in v:
f.write("\n"+parse_docstr(v['doc'],4)+"\n\n")
f.write("\n")
f.write("\n\n")
f.write("\n\n")
print(f" - Done with modules")