Merge pull request #1210 from rmehta/docs-update

[docs] update to auto documentation generator
This commit is contained in:
Anand Doshi 2015-07-22 16:39:21 +05:30
commit 4e593711dc
21 changed files with 301 additions and 169 deletions

View file

@ -4,7 +4,6 @@
from __future__ import unicode_literals, absolute_import
import sys
import os
import subprocess
import json
import click
import hashlib
@ -140,7 +139,7 @@ def reinstall(context):
frappe.clear_cache()
installed = frappe.get_installed_apps()
frappe.clear_cache()
except Exception, e:
except Exception:
installed = []
finally:
if frappe.db:
@ -191,8 +190,6 @@ def migrate(context, rebuild_website=False):
import frappe.translate
from frappe.desk.notifications import clear_notifications
verbose = context.verbose
for site in context.sites:
print 'Migrating', site
frappe.init(site=site)
@ -312,15 +309,16 @@ def destroy_all_sessions(context):
frappe.destroy()
@click.command('sync-www')
@click.option('--force', help='Rebuild all pages', is_flag=True, default=False)
@pass_context
def sync_www(context):
def sync_www(context, force=False):
"Sync files from static pages from www directory to Web Pages"
from frappe.website import statics
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
statics.sync_statics(rebuild=context.force)
statics.sync_statics(rebuild=force)
frappe.db.commit()
finally:
frappe.destroy()
@ -341,32 +339,17 @@ def build_website(context):
frappe.destroy()
@click.command('setup-docs')
@click.argument('app')
@click.argument('docs-app')
@click.argument('path')
@pass_context
def setup_docs(context,app, docs_app, path):
def setup_docs(context):
"Setup docs in target folder of target app"
from frappe.utils.setup_docs import setup_docs
from frappe.website import statics
for site in context.sites:
try:
frappe.init(site=site)
frappe.connect()
setup_docs(app, docs_app, path)
finally:
frappe.destroy()
@click.command('build-docs')
@click.argument('app')
@pass_context
def build_docs(context, app):
"Build docs from /src to /www folder in app"
from frappe.utils.autodoc import build
frappe.destroy()
for site in context.sites:
try:
frappe.init(site=site)
build(app)
setup_docs()
statics.sync_statics(rebuild=True)
finally:
frappe.destroy()
@ -826,7 +809,6 @@ commands = [
sync_www,
build_website,
setup_docs,
build_docs,
reset_perms,
execute,
celery,

View file

@ -11,7 +11,6 @@ from frappe.utils import get_fullname
class Comment(Document):
"""Comments are added to Documents via forms or views like blogs etc."""
__doclink__ = "https://frappe.io/docs/models/core/comment"
no_feed_on_delete = True
def get_feed(self):
@ -134,4 +133,3 @@ def on_doctype_update():
frappe.db.commit()
frappe.db.sql("""alter table `tabComment`
add index comment_doctype_docname_index(comment_doctype, comment_docname)""")

View file

@ -6,5 +6,4 @@ from __future__ import unicode_literals
from frappe.model.document import Document
class DocField(Document):
__doclink__ = "https://frappe.io/docs/models/core/docfield"
pass

View file

@ -7,5 +7,4 @@ import frappe
from frappe.model.document import Document
class DocPerm(Document):
__doclink__ = "https://frappe.io/docs/models/v5.x/core/docperm"
pass

View file

@ -18,7 +18,6 @@ form_grid_templates = {
}
class DocType(Document):
__doclink__ = "https://frappe.io/docs/models/core/doctype"
def get_feed(self):
return self.name
@ -467,4 +466,3 @@ def init_list(doctype):
doc = frappe.get_meta(doctype)
make_boilerplate("controller_list.js", doc)
make_boilerplate("controller_list.html", doc)

View file

@ -7,7 +7,6 @@ import frappe, os
from frappe.model.document import Document
class ModuleDef(Document):
__doclink__ = "https://frappe.io/docs/models/core/module_def"
def on_update(self):
"""If in `developer_mode`, create folder for module and
add in `modules.txt` of app if missing."""
@ -39,7 +38,3 @@ class ModuleDef(Document):
frappe.clear_cache()
frappe.setup_module_map()

View file

@ -19,7 +19,7 @@ def get_roles_and_doctypes():
name not in ('DocType') and
exists(select * from `tabDocField` where parent=dt.name)""")],
"roles": [d[0] for d in frappe.db.sql("""select name from tabRole where name not in
('Guest', 'Administrator')""")]
('Administrator')""")]
}
@frappe.whitelist()

View file

@ -2,12 +2,35 @@ from __future__ import unicode_literals
app_name = "frappe"
app_title = "Frappe Framework"
app_publisher = "Frappe Technologies Pvt. Ltd."
app_description = "Full Stack Web Application Framework in Python"
app_description = """## Frappe Framework
Frappe is a full stack web application framework written in Python,
Javascript, HTML/CSS with MySQL as the backend. It was built for ERPNext
but is pretty generic and can be used to build database driven apps.
The key differece in Frappe compared to other frameworks is that Frappe
is that meta-data is also treated as data and is used to build front-ends
very easily. Frappe comes with a full blown admin UI called the **Desk**
that handles forms, navigation, lists, menus, permissions, file attachment
and much more out of the box.
Frappe also has a plug-in architecture that can be used to build plugins
to ERPNext.
### Links:
- Project Home: [https://frappe.io](https://frappe.io)
- Tutorial: [https://frappe.io/tutorial](https://frappe.io/tutorial)
- GitHub: [https://github.com/frappe/frappe](https://github.com/frappe/frappe)
- Forum: [https://discuss.erpnext.com](https://discuss.erpnext.com)
"""
app_icon = "octicon octicon-circuit-board"
app_version = "5.1.3"
app_color = "orange"
github_link = "https://github.com/frappe/frappe"
app_email = "support@frappe.io"
app_email = "info@frappe.io"
before_install = "frappe.utils.install.before_install"
after_install = "frappe.utils.install.after_install"

View file

@ -0,0 +1,11 @@
<!-- title: {{ app.title }} API -->
{% from "templates/autodoc/macros.html" import github_link, version %}
<p>
{{ version(app.name) }}
{{ github_link(app, app.name, True) }}
</p>
<h3>Contents</h3>
{index}

View file

@ -0,0 +1,55 @@
<!-- title: {{ app.title }} Documentation -->
<!-- no-breadcrumbs -->
{% from "templates/autodoc/macros.html" import github_link, version, discuss_link %}
<p>
{{ version(app.name) }}
{{ github_link(app, app.name, True) }}
</p>
<table class="table table-bordered" style="max-width: 500px;">
<tr>
<td style="width: 40%">
App Name
</td>
<td>
<code>{{ app.name }}</code>
</td>
</tr>
<tr>
<td>
Publisher
</td>
<td>
<code>{{ app.publisher }}</code>
</td>
</tr>
<tr>
<td>
Version
</td>
<td>
<code>{{ app.version }}</code>
</td>
</tr>
</table>
<hr>
{{ app.description }}
<hr>
<h3>Contents</h3>
<ul>
<li>
<a href="{{ '{{ pathname }}' }}/models">Models (DocTypes)</a>
</li>
<li>
<a href="{{ '{{ pathname }}' }}/api">Server-side API</a>
</li>
</ul>
{{ discuss_link() }}
<!-- jinja --><!-- static -->

View file

@ -1,14 +1,21 @@
{% from "templates/autodoc/macros.html" import automodule, version %}
{% macro render_doctype(name) %}
{% set doc = frappe.get_doc("DocType", name) %}
{% set controller = autodoc.get_controller(name) %}
{{ version(name) }}
<!-- title: {{ doctype }} -->
{% from "templates/autodoc/macros.html" import automodule, version,
github_link, doctype_link, discuss_link %}
{% set doc = frappe.get_doc("DocType", doctype) %}
{% set controller = autodoc.get_controller(doctype) %}
<p>
{{ version(doctype) }}
{{ github_link(app, app.name + "/" + scrub(doc.module)
+ "/doctype/" + scrub(doctype), True) }}
</p>
{% if doc.issingle %}<span class="label label-info">Single</span>{% endif %}
{% if doc.istable %}<span class="label label-info">Child Table</span>{% endif %}
{% if not doc.issingle %}
<p><b>Table Name:</b> <code>tab{{ name }}</code></p>
<p><b>Table Name:</b> <code>tab{{ doctype }}</code></p>
{% endif %}
{{ doc.description or "" }}
@ -39,7 +46,7 @@
</td>
<td>{% if df.options and df.fieldtype not in ("HTML") %}
{% if df.fieldtype in ("Table", "Link") %}
<a href="{{ autodoc.get_doclink(df.options) or "#" }}">{{ df.options }}</a>
{{ doctype_link(df.options) }}
{% else %}<pre>{{ df.options }}</pre>{% endif %}
{% endif %}</td>
</tr>
@ -54,26 +61,28 @@
{{ automodule(controller.__module__) }}
{% set parents = frappe.get_list("DocField",
{"options": name, "fieldtype": "Link"}, ["distinct parent"]) %}
filters = {"options": doctype, "fieldtype": "Link"}, fields = ["distinct parent"]) %}
{% if parents %}
<h4>Linked In:</h4>
<ul>
{% for parent in parents %}
<li><a href="{{ autodoc.get_doclink(parent.parent) }}">{{ parent.parent }}</a></li>
<li>{{ doctype_link(parent.parent) }}</li>
{% endfor %}
</ul>
{% endif %}
{% else %}
{% set parents = frappe.get_list("DocField",
{"options": name, "fieldtype": "Table"}, ["parent"]) %}
filters = {"options": doctype, "fieldtype": "Table"}, fields = ["parent"]) %}
{% if parents %}
<h4>Child Table Of</h4>
<ul>
{% for parent in parents %}
<li><a href="{{ autodoc.get_doclink(parent.parent) }}">{{ parent.parent }}</a></li>
<li>{{ doctype_link(parent.parent) }}</li>
{% endfor %}
</ul>
{% endif %}
{% endif %}
{% endmacro %}
<!-- jinja --><!-- static -->
{{ discuss_link() }}

View file

@ -1,6 +1,5 @@
{% macro automodule(name) %}
{% set m = autodoc.automodule(name) %}
{{ version(name) }}
{% for obj in m.members %}
{% if obj.type=="function" %}
{{ render_function(obj, name) }}
@ -10,12 +9,6 @@
{% endfor %}
{% endmacro %}
{% macro version(name) %}
<p>
<span class="label label-default">Version {{ autodoc.get_version(name) }}</span>
</p>
{% endmacro %}
{% macro render_class(obj) %}
<h3 style="font-weight: normal;">Class <b>{{ obj.name }}</b></h3>
{% if obj.bases %}
@ -51,3 +44,26 @@
{{ arg }}{% if default_idx >= 0 %}={{ args[3][default_idx] }}{% endif %}{% if not loop.last %}, {% endif %}
{%- endfor %}
{%- endmacro %}
{% macro version(name) %}
<a class="btn btn-default btn-sm" disabled style="margin-bottom: 10px;">
Version {{ autodoc.get_version(name) }}</a>
{% endmacro %}
{% macro github_link(app, file_path, tree=False) %}
<a class="btn btn-default btn-sm" href="{{ app.github_link }}/{{ "tree" if tree else "blob" }}/v{{ app.version }}/{{ file_path }}"
target="_blank" style="margin-left: 10px; margin-bottom: 10px;"><i class="octicon octicon-mark-github"></i> Source</a>
{% endmacro %}
{% macro discuss_link() %}
<br>
<a href="https://discuss.erpnext.com" target="_blank">Discuss this on the forum</a>
{% endmacro %}
{% macro doctype_link(doctype) %}
{% set module = frappe.db.get_value("DocType", doctype, "module") %}
{% if doctype and module %}
<a href="/{{'{{ pathname.split("/")[0] }}'}}/models/{{
scrub(module) }}/{{ scrub(doctype) }}">{{ doctype }}</a>
{% endif %}
{% endmacro %}

View file

@ -0,0 +1,13 @@
<!-- title: {{ app.title }} Models (DocTypes) -->
{% from "templates/autodoc/macros.html" import github_link, version %}
<p>
{{ version(app.name) }}
{{ github_link(app, app.name, True) }}
</p>
<p>Browse DocTypes by Module</p>
<h3>Contents</h3>
{index}

View file

@ -0,0 +1,11 @@
<!-- title: Module {{ name }} -->
{% from "templates/autodoc/macros.html" import github_link, version %}
<p>
{{ version(app.name) }}
{{ github_link(app, app.name + "/" + scrub(name), True) }}
</p>
<h3>DocTypes for {{ name }}</h3>
{index}

View file

@ -0,0 +1,11 @@
<!-- title: {{ title }} -->
{% from "templates/autodoc/macros.html" import github_link, version %}
<p>
{{ version(app.name) }}
{{ github_link(app, title, True) }}
</p>
<h3>Package Contents</h3>
{index}

View file

@ -0,0 +1,12 @@
<!-- title: {{ name }} -->
{%- from "templates/autodoc/macros.html" import automodule, github_link,
version, discuss_link -%}
<p>
{{ version(app.name) }}
{{ github_link(app, name.replace(".", "/") + ".py") }}
</p>
{{ automodule(name) }}
{{ discuss_link() }}

View file

@ -19,3 +19,4 @@
</div>
</div>
{%- endif %}
<!-- no-breadcrumbs -->

View file

@ -8,50 +8,9 @@ frappe.utils.autodoc
Inspect elements of a given module and return its objects
"""
import inspect, importlib, re, frappe, os, shutil
import inspect, importlib, re, frappe
from frappe.model.document import get_controller
from markdown2 import markdown
def build(app):
app_path = frappe.get_app_path(app)
source = frappe.get_app_path(app, "src")
dest = frappe.get_app_path(app, "www")
for basepath, folders, files in os.walk(source):
destpath = os.path.join(dest, os.path.relpath(basepath, source))
# make target dir if missing
if not os.path.exists(destpath):
os.makedirs(destpath)
# delete removed folders in source from dest
for destfolder in os.listdir(destpath):
if os.path.isdir(os.path.join(destpath, destfolder)):
if destfolder not in folders:
os.path.join(destpath, destfolder)
shutil.rmtree(os.path.join(destpath, destfolder))
for fname in files:
# delete file
if os.path.exists(os.path.join(destpath, fname)):
os.remove(os.path.join(destpath, fname))
print fname
if fname.rsplit(".", 1)[-1] in ("md", "html"):
# render template and build file
with open(os.path.join(destpath, fname.rsplit(".", 1)[0] + ".html"), "w") as destfile:
if fname.endswith(".md"):
# convert markdown to html before rendering
with open(os.path.join(basepath, fname), "r") as template_file:
template = markdown(template_file.read())
html = frappe.render_template(template, {}).encode("utf-8")
destfile.write(html)
else:
template_path = os.path.relpath(os.path.join(basepath, fname), app_path)
html = frappe.render_template(template_path, {}, is_path=True).encode("utf-8")
destfile.write(html)
else:
# not a template, copy
shutil.copyfile(os.path.join(basepath, fname), os.path.join(destpath, fname))
def automodule(name):
"""Returns a list of attributes for given module string.
@ -85,16 +44,22 @@ def automodule(name):
"members": filter(None, attributes),
}
installed = None
def get_version(name):
print name
global installed
if not installed:
installed = frappe.get_installed_apps()
def _for_module(m):
return importlib.import_module(m.split(".")[0]).__version__
if "." in name or name=="frappe":
if "." in name or name in installed:
return _for_module(name)
else:
return _for_module(get_controller(name).__module__)
def get_class_info(class_obj, module_name):
members = []
for attrname in dir(class_obj):
@ -117,15 +82,14 @@ def get_class_info(class_obj, module_name):
}
def get_function_info(value):
docs = getattr(value, "__doc__", "")
if docs:
return {
"name": value.__name__,
"type": "function",
"args": inspect.getargspec(value),
"docs": parse(docs),
"whitelisted": value in frappe.whitelisted
}
docs = getattr(value, "__doc__")
return {
"name": value.__name__,
"type": "function",
"args": inspect.getargspec(value),
"docs": parse(docs) if docs else '<span class="text-muted">No docs</span>',
"whitelisted": value in frappe.whitelisted
}
def parse(docs):
"""Parse __docs__ text into markdown. Will parse directives like `:param name:` etc"""
@ -179,17 +143,3 @@ def strip_leading_tabs(docs):
def automodel(doctype):
"""return doctype template"""
pass
def get_doclink(name):
"""Returns `__doclink__` property of a module or DocType if exists"""
if name=="[Select]": return ""
if "." in name:
obj = frappe.get_attr(name)
else:
obj = get_controller(name)
if hasattr(obj, "__doclink__"):
return obj.__doclink__
else:
return ""

View file

@ -32,9 +32,10 @@ def get_allowed_functions_for_jenv():
import frappe
import frappe.utils
import frappe.utils.data
from frappe.utils.autodoc import automodule, get_doclink, get_version
from frappe.utils.autodoc import automodule, get_version
from frappe.model.document import get_controller
from frappe.website.utils import get_shade
from frappe.modules import scrub
datautils = {}
for key, obj in frappe.utils.data.__dict__.items():
@ -76,14 +77,14 @@ def get_allowed_functions_for_jenv():
},
"autodoc": {
"get_version": get_version,
"get_doclink": get_doclink,
"automodule": automodule,
"get_controller": get_controller
},
"get_visible_columns": \
frappe.get_attr("frappe.templates.pages.print.get_visible_columns"),
"_": frappe._,
"get_shade": get_shade
"get_shade": get_shade,
"scrub": scrub
}
def get_jloader():

View file

@ -6,20 +6,50 @@ Call from command line:
"""
import os, json, frappe
import os, json, frappe, markdown2, shutil
class setup_docs(object):
def __init__(self, app, docs_app, path):
"""Generate source templates for models reference and module API.
Must set globals `self.models_base_path`, `self.api_base_path` and `self.app_path`.
def __init__(self):
"""Generate source templates for models reference and module API
and templates at `templates/autodoc`
"""
self.app = app
self.app_path = frappe.get_app_path(app)
if path[0]=="/": path = path[1:]
path = frappe.get_app_path(docs_app, path)
self.app = frappe.get_hooks("autodoc").get("for_app")[0]
docs_app = frappe.get_hooks("autodoc").get("docs_app")[0]
hooks = frappe.get_hooks(app_name = self.app)
self.app_title = hooks.get("app_title")[0]
self.app_path = frappe.get_app_path(self.app)
path = frappe.get_app_path(docs_app, "www", "current")
print "Deleting current..."
shutil.rmtree(path, ignore_errors = True)
os.makedirs(path)
self.app_context = {
"app": {
"name": self.app,
"title": self.app_title,
"description": markdown2.markdown(hooks.get("app_description")[0]),
"version": hooks.get("app_version")[0],
"publisher": hooks.get("app_publisher")[0],
"github_link": hooks.get("github_link")[0],
}
}
# make home page
with open(os.path.join(path, "index.html"), "w") as home:
home.write(frappe.render_template("templates/autodoc/docs_home.html",
self.app_context))
# make folders
self.models_base_path = os.path.join(path, "models")
self.make_folder(self.models_base_path,
template = "templates/autodoc/models_home.html")
self.api_base_path = os.path.join(path, "api")
self.make_folder(self.api_base_path,
template = "templates/autodoc/api_home.html")
for basepath, folders, files in os.walk(self.app_path):
if "doctype" not in basepath:
@ -28,7 +58,9 @@ class setup_docs(object):
module_folder = os.path.join(self.models_base_path, module)
self.make_folder(module_folder)
self.make_folder(module_folder,
template = "templates/autodoc/module_home.html",
context = {"name": module})
self.update_index_txt(module_folder)
if "doctype" in basepath:
@ -70,35 +102,37 @@ class setup_docs(object):
if not os.path.exists(module_doc_path):
print "Writing " + module_doc_path
with open(module_doc_path, "w") as f:
f.write("""<h1>%(name)s</h1>
<!-- title: %(name)s -->
{%%- from "templates/autodoc/macros.html" import automodule -%%}
{{ automodule("%(name)s") }}""" % {"name": self.app + "." + module_name})
context = {"name": self.app + "." + module_name}
context.update(self.app_context)
f.write(frappe.render_template("templates/autodoc/pymodule.html",
context))
self.update_index_txt(module_folder)
def make_folder(self, path):
def make_folder(self, path, template=None, context=None):
if not template:
template = "templates/autodoc/package_index.html"
if not os.path.exists(path):
os.makedirs(path)
index_txt_path = os.path.join(path, "index.txt")
if not os.path.exists(index_txt_path):
index_txt_path = os.path.join(path, "index.txt")
print "Writing " + index_txt_path
with open(index_txt_path, "w") as f:
f.write("")
index_md_path = os.path.join(path, "index.html")
if not os.path.exists(index_md_path):
name = os.path.basename(path)
if name==".":
name = self.app
print "Writing " + index_md_path
with open(index_md_path, "w") as f:
f.write("""<h1>{0}</h1>
<!-- title: {0} -->
{{index}}""".format(name))
index_html_path = os.path.join(path, "index.html")
if not context:
name = os.path.basename(path)
if name==".":
name = self.app
context = {
"title": name
}
context.update(self.app_context)
print "Writing " + index_html_path
with open(index_html_path, "w") as f:
f.write(frappe.render_template(template, context))
def update_index_txt(self, path):
index_txt_path = os.path.join(path, "index.txt")
@ -126,12 +160,7 @@ class setup_docs(object):
print "Writing " + model_path
with open(model_path, "w") as f:
f.write("""<h1>%(doctype)s</h1>
<!-- title: %(doctype)s -->
{%% from "templates/autodoc/doctype.html" import render_doctype %%}
{{ render_doctype("%(doctype)s") }}
<!-- jinja --><!-- static -->
""" % {"doctype": doctype_real_name})
context = {"doctype": doctype_real_name}
context.update(self.app_context)
f.write(frappe.render_template("templates/autodoc/doctype.html",
context).encode("utf-8"))

View file

@ -200,22 +200,41 @@
"icon": "icon-file-alt",
"idx": 1,
"max_attachments": 20,
"modified": "2015-07-13 04:46:59.435179",
"modified": "2015-07-22 12:38:08.696692",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Page",
"owner": "Administrator",
"permissions": [
{
"cancel": 0,
"create": 0,
"delete": 0,
"email": 0,
"permlevel": 0,
"print": 0,
"read": 1,
"report": 0,
"role": "Website Manager",
"share": 1,
"submit": 0,
"write": 0
},
{
"amend": 0,
"apply_user_permissions": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Website Manager",
"role": "Guest",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1