@@ -4,6 +4,28 @@ import os | |||||
import frappe | import frappe | ||||
from frappe.commands import pass_context | from frappe.commands import pass_context | ||||
@click.command('write-docs') | |||||
@pass_context | |||||
@click.argument('app') | |||||
@click.option('--target', default=None) | |||||
@click.option('--local', default=False, is_flag=True, help='Run app locally') | |||||
def write_docs(context, app, target=None, local=False): | |||||
"Setup docs in target folder of target app" | |||||
from frappe.utils.setup_docs import setup_docs | |||||
if not target: | |||||
target = os.path.abspath(os.path.join("..", "docs", app)) | |||||
for site in context.sites: | |||||
try: | |||||
frappe.init(site=site) | |||||
frappe.connect() | |||||
make = setup_docs(app) | |||||
make.make_docs(target, local) | |||||
finally: | |||||
frappe.destroy() | |||||
@click.command('build-docs') | @click.command('build-docs') | ||||
@pass_context | @pass_context | ||||
@click.argument('app') | @click.argument('app') | ||||
@@ -51,5 +73,6 @@ def _build_docs_once(site, app, docs_version, target, local, only_content_update | |||||
frappe.destroy() | frappe.destroy() | ||||
commands = [ | commands = [ | ||||
build_docs | |||||
build_docs, | |||||
write_docs, | |||||
] | ] |
@@ -22,7 +22,7 @@ ERP for managing small and medium sized businesses. | |||||
[Get started with the Tutorial](https://frappe.github.io/frappe/user/) | [Get started with the Tutorial](https://frappe.github.io/frappe/user/) | ||||
""" | """ | ||||
docs_version = "6.x.x" | |||||
docs_version = "7.x.x" | |||||
def get_context(context): | def get_context(context): | ||||
context.top_bar_items = [ | context.top_bar_items = [ | ||||
@@ -14,8 +14,7 @@ To render a template, | |||||
From `erpnext/public/js/templates/address_list.js` | From `erpnext/public/js/templates/address_list.js` | ||||
<p><button class="btn btn-sm btn-default btn-address"> | |||||
{% raw %}<p><button class="btn btn-sm btn-default btn-address"> | |||||
<i class="icon-plus"></i> New Address</button></p> | <i class="icon-plus"></i> New Address</button></p> | ||||
{% for(var i=0, l=addr_list.length; i<l; i++) { %} | {% for(var i=0, l=addr_list.length; i<l; i++) { %} | ||||
<hr> | <hr> | ||||
@@ -34,7 +33,7 @@ From `erpnext/public/js/templates/address_list.js` | |||||
{% } %} | {% } %} | ||||
{% if(!addr_list.length) { %} | {% if(!addr_list.length) { %} | ||||
<p class="text-muted">{%= __("No address added yet.") %}</p> | <p class="text-muted">{%= __("No address added yet.") %}</p> | ||||
{% } %} | |||||
{% } %}{% endraw %} | |||||
@@ -14,8 +14,7 @@ To render a template, | |||||
From `erpnext/public/js/templates/address_list.js` | From `erpnext/public/js/templates/address_list.js` | ||||
<p><button class="btn btn-sm btn-default btn-address"> | |||||
{% raw %}<p><button class="btn btn-sm btn-default btn-address"> | |||||
<i class="icon-plus"></i> New Address</button></p> | <i class="icon-plus"></i> New Address</button></p> | ||||
{% for(var i=0, l=addr_list.length; i<l; i++) { %} | {% for(var i=0, l=addr_list.length; i<l; i++) { %} | ||||
<hr> | <hr> | ||||
@@ -34,9 +33,7 @@ From `erpnext/public/js/templates/address_list.js` | |||||
{% } %} | {% } %} | ||||
{% if(!addr_list.length) { %} | {% if(!addr_list.length) { %} | ||||
<p class="text-muted">{%= __("No address added yet.") %}</p> | <p class="text-muted">{%= __("No address added yet.") %}</p> | ||||
{% } %} | |||||
{% } %}{% endraw %} | |||||
<!-- markdown --> | <!-- markdown --> |
@@ -3,6 +3,18 @@ | |||||
# metadata | # metadata | ||||
''' | |||||
Load metadata (DocType) class | |||||
Example: | |||||
meta = frappe.get_meta('User') | |||||
if meta.has_field('first_name'): | |||||
print "DocType" table has field "first_name" | |||||
''' | |||||
from __future__ import unicode_literals | from __future__ import unicode_literals | ||||
import frappe, json | import frappe, json | ||||
from frappe.utils import cstr, cint | from frappe.utils import cstr, cint | ||||
@@ -33,7 +33,7 @@ def set_field_property(filters, key, value): | |||||
frappe.db.commit() | frappe.db.commit() | ||||
def render_include(content): | def render_include(content): | ||||
'''render {% include "app/path/filename" in js file %}''' | |||||
'''render {% raw %}{% include "app/path/filename" %}{% endraw %} in js file''' | |||||
content = cstr(content) | content = cstr(content) | ||||
@@ -246,6 +246,7 @@ body { | |||||
line-height: 1.65em; | line-height: 1.65em; | ||||
color: #454e57; | color: #454e57; | ||||
-webkit-font-smoothing: antialiased; | -webkit-font-smoothing: antialiased; | ||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; | |||||
} | } | ||||
.container { | .container { | ||||
max-width: 870px; | max-width: 870px; | ||||
@@ -5,9 +5,15 @@ | |||||
body { | body { | ||||
font-size: 16px; | font-size: 16px; | ||||
line-height: 1.65em; | line-height: 1.65em; | ||||
color: #454e57; | |||||
color: #454e57; | |||||
// position: relative; | // position: relative; | ||||
-webkit-font-smoothing: antialiased; | |||||
-webkit-font-smoothing: antialiased; | |||||
font-family: -apple-system, BlinkMacSystemFont, | |||||
"Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", | |||||
"Fira Sans", "Droid Sans", "Helvetica Neue", | |||||
sans-serif; | |||||
} | } | ||||
.container { | .container { | ||||
@@ -1,9 +1,9 @@ | |||||
{% if parents and parents|length > 0 %} | |||||
{% if parents and parents|length > 0 and (parents[-1].route) %} | |||||
<ul class="breadcrumb"> | <ul class="breadcrumb"> | ||||
<li> | <li> | ||||
<span class="icon icon-angle-left"></span> | <span class="icon icon-angle-left"></span> | ||||
<a href="{{ url_prefix }}{{ parents[-1].name | abs_url }}"> | |||||
{{ parents[-1].page_title or parents[-1].title or parents[-1].label or "" }}</a> | |||||
<a href="{{ url_prefix }}{{ parents[-1].route | abs_url }}"> | |||||
{{ parents[-1].title or parents[-1].label or "" }}</a> | |||||
</li> | </li> | ||||
{# | {# | ||||
<!-- {% for parent in parents %} | <!-- {% for parent in parents %} | ||||
@@ -2,7 +2,7 @@ | |||||
<ol> | <ol> | ||||
{% for item in children_map[route] %} | {% for item in children_map[route] %} | ||||
<li> | <li> | ||||
<a href="{{ url_prefix }}{{ item.route }}.html">{{ item.title }}</a> | |||||
<a href="{{ url_prefix }}{{ item.route }}">{{ item.title }}</a> | |||||
{% if children_map[item.route] %} | {% if children_map[item.route] %} | ||||
{{ make_item_list(item.route, children_map) }} | {{ make_item_list(item.route, children_map) }} | ||||
{% endif %} | {% endif %} | ||||
@@ -11,4 +11,4 @@ | |||||
</ol> | </ol> | ||||
{% endmacro %} | {% endmacro %} | ||||
{{ make_item_list(full_index) }} | |||||
{{ make_item_list(route, full_index) }} |
@@ -14,9 +14,11 @@ | |||||
<div class="page-content-wrapper"> | <div class="page-content-wrapper"> | ||||
<div class="row page-head"> | <div class="row page-head"> | ||||
<div class="col-sm-8"> | <div class="col-sm-8"> | ||||
{% if not no_breadcrumbs and self.breadcrumbs() %} | |||||
{% if not no_breadcrumbs and parents %} | |||||
<div class="page-breadcrumbs"> | <div class="page-breadcrumbs"> | ||||
{% block breadcrumbs %}{% endblock %} | |||||
{% block breadcrumbs %} | |||||
{% include 'templates/includes/breadcrumbs.html' %} | |||||
{% endblock %} | |||||
</div> | </div> | ||||
{% endif %} | {% endif %} | ||||
{% block header %}{% endblock %} | {% block header %}{% endblock %} | ||||
@@ -118,7 +118,7 @@ def get_jloader(): | |||||
if not frappe.local.jloader: | if not frappe.local.jloader: | ||||
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader | from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader | ||||
apps = frappe.get_installed_apps(sort=True) | |||||
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps(sort=True) | |||||
apps.reverse() | apps.reverse() | ||||
@@ -6,7 +6,7 @@ Call from command line: | |||||
""" | """ | ||||
import os, json, frappe, shutil | |||||
import os, json, frappe, shutil, re | |||||
import frappe.website.statics | import frappe.website.statics | ||||
from frappe.website.context import get_context | from frappe.website.context import get_context | ||||
from frappe.utils import markdown | from frappe.utils import markdown | ||||
@@ -17,6 +17,10 @@ class setup_docs(object): | |||||
and templates at `templates/autodoc` | and templates at `templates/autodoc` | ||||
""" | """ | ||||
self.app = app | self.app = app | ||||
frappe.local.flags.web_pages_folders = ['docs',] | |||||
frappe.local.flags.web_pages_apps = [self.app,] | |||||
self.hooks = frappe.get_hooks(app_name = self.app) | self.hooks = frappe.get_hooks(app_name = self.app) | ||||
self.app_title = self.hooks.get("app_title")[0] | self.app_title = self.hooks.get("app_title")[0] | ||||
self.setup_app_context() | self.setup_app_context() | ||||
@@ -268,41 +272,38 @@ class setup_docs(object): | |||||
"""render templates and write files to target folder""" | """render templates and write files to target folder""" | ||||
frappe.local.flags.home_page = "index" | frappe.local.flags.home_page = "index" | ||||
from frappe.website.router import get_pages, make_toc | |||||
pages = get_pages() | |||||
# clear the user, current folder in target | # clear the user, current folder in target | ||||
shutil.rmtree(os.path.join(self.target, "user"), ignore_errors=True) | shutil.rmtree(os.path.join(self.target, "user"), ignore_errors=True) | ||||
shutil.rmtree(os.path.join(self.target, "current"), ignore_errors=True) | shutil.rmtree(os.path.join(self.target, "current"), ignore_errors=True) | ||||
cnt = 0 | cnt = 0 | ||||
for page in frappe.db.sql("""select parent_website_route, | |||||
page_name from `tabWeb Page` where ifnull(template_path, '')!=''""", as_dict=True): | |||||
if page.parent_website_route: | |||||
path = page.parent_website_route + "/" + page.page_name | |||||
else: | |||||
path = page.page_name | |||||
for path, context in pages.iteritems(): | |||||
print "Writing {0}".format(path) | print "Writing {0}".format(path) | ||||
# set this for get_context / website libs | # set this for get_context / website libs | ||||
frappe.local.path = path | frappe.local.path = path | ||||
context = { | |||||
context.update({ | |||||
"page_links_with_extn": True, | "page_links_with_extn": True, | ||||
"relative_links": True, | "relative_links": True, | ||||
"docs_base_url": self.docs_base_url, | "docs_base_url": self.docs_base_url, | ||||
"url_prefix": self.docs_base_url, | "url_prefix": self.docs_base_url, | ||||
} | |||||
}) | |||||
context.update(self.app_context) | context.update(self.app_context) | ||||
context = get_context(path, context) | context = get_context(path, context) | ||||
target_path_fragment = context.template_path.split('/docs/', 1)[1] | |||||
target_filename = os.path.join(self.target, target_path_fragment) | |||||
if context.basename: | |||||
target_path_fragment = context.route + '.html' | |||||
else: | |||||
# index.html | |||||
target_path_fragment = context.route + '/index.html' | |||||
# rename .md files to .html | |||||
if target_filename.rsplit(".", 1)[1]=="md": | |||||
target_filename = target_filename[:-3] + ".html" | |||||
target_filename = os.path.join(self.target, target_path_fragment.strip('/')) | |||||
context.brand_html = context.top_bar_items = context.favicon = None | context.brand_html = context.top_bar_items = context.favicon = None | ||||
@@ -324,13 +325,23 @@ class setup_docs(object): | |||||
context.top_bar_items = [{"label": '<i class="octicon octicon-search"></i>', "url": "#", | context.top_bar_items = [{"label": '<i class="octicon octicon-search"></i>', "url": "#", | ||||
"right": 1}] + context.top_bar_items | "right": 1}] + context.top_bar_items | ||||
context.parents = [] | |||||
parent_route = os.path.dirname(context.route) | |||||
if pages[parent_route]: | |||||
context.parents = [pages[parent_route]] | |||||
if not context.favicon: | if not context.favicon: | ||||
context.favicon = "/assets/img/favicon.ico" | context.favicon = "/assets/img/favicon.ico" | ||||
context.only_static = True | context.only_static = True | ||||
context.base_template_path = "templates/autodoc/base_template.html" | context.base_template_path = "templates/autodoc/base_template.html" | ||||
html = frappe.get_template("templates/generators/web_page.html").render(context) | |||||
if '<code>' in context.source: | |||||
context.source = re.sub('\<code\>(.*)\</code\>', '<code>{% raw %}\g<1>{% endraw %}</code>', context.source) | |||||
html = frappe.render_template(context.source, context) | |||||
html = make_toc(context, html) | |||||
if not "<!-- autodoc -->" in html: | if not "<!-- autodoc -->" in html: | ||||
html = html.replace('<!-- edit-link -->', | html = html.replace('<!-- edit-link -->', | ||||
@@ -8,10 +8,12 @@ from frappe.website.doctype.website_settings.website_settings import get_website | |||||
from frappe.website.router import get_page_context | from frappe.website.router import get_page_context | ||||
def get_context(path, args=None): | def get_context(path, args=None): | ||||
context = get_page_context(path) | |||||
if args: | |||||
context.update(args) | |||||
if args and args.source: | |||||
context = args | |||||
else: | |||||
context = get_page_context(path) | |||||
if args: | |||||
context.update(args) | |||||
context = build_context(context) | context = build_context(context) | ||||
@@ -55,7 +55,9 @@ def get_page_context_from_template(path): | |||||
for app in frappe.get_installed_apps(): | for app in frappe.get_installed_apps(): | ||||
app_path = frappe.get_app_path(app) | app_path = frappe.get_app_path(app) | ||||
for start in ('www', 'templates/pages'): | |||||
folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages') | |||||
for start in folders: | |||||
search_path = os.path.join(app_path, start, path) | search_path = os.path.join(app_path, start, path) | ||||
options = (search_path, search_path + '.html', search_path + '.md', | options = (search_path, search_path + '.html', search_path + '.md', | ||||
search_path + '/index.html', search_path + '/index.md') | search_path + '/index.html', search_path + '/index.md') | ||||
@@ -111,10 +113,14 @@ def get_pages(): | |||||
'''Get all pages. Called for docs / sitemap''' | '''Get all pages. Called for docs / sitemap''' | ||||
pages = {} | pages = {} | ||||
frappe.local.flags.in_get_all_pages = True | frappe.local.flags.in_get_all_pages = True | ||||
for app in frappe.get_installed_apps(): | |||||
folders = frappe.local.flags.web_pages_folders or ('www', 'templates/pages') | |||||
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps() | |||||
for app in apps: | |||||
app_path = frappe.get_app_path(app) | app_path = frappe.get_app_path(app) | ||||
for start in ('templates/pages', 'www'): | |||||
for start in folders: | |||||
path = os.path.join(app_path, start) | path = os.path.join(app_path, start) | ||||
pages.update(get_pages_from_path(path, app, app_path)) | pages.update(get_pages_from_path(path, app, app_path)) | ||||
frappe.local.flags.in_get_all_pages = False | frappe.local.flags.in_get_all_pages = False | ||||
@@ -166,7 +172,7 @@ def get_page_info(path, app, basepath=None, app_path=None, fname=None): | |||||
page_info.template = os.path.relpath(os.path.join(basepath, fname), app_path) | page_info.template = os.path.relpath(os.path.join(basepath, fname), app_path) | ||||
if page_info.basename == 'index': | if page_info.basename == 'index': | ||||
page_info.basename = os.path.dirname(path).strip('.').strip('/') | |||||
page_info.basename = "" | |||||
page_info.route = page_info.name = page_info.page_name = os.path.join(os.path.relpath(basepath, path), | page_info.route = page_info.name = page_info.page_name = os.path.join(os.path.relpath(basepath, path), | ||||
page_info.basename).strip('/.') | page_info.basename).strip('/.') | ||||
@@ -180,16 +186,15 @@ def get_page_info(path, app, basepath=None, app_path=None, fname=None): | |||||
page_info.controller = controller | page_info.controller = controller | ||||
# get the source | # get the source | ||||
page_info.source = get_source(page_info) | |||||
setup_source(page_info) | |||||
if page_info.only_content: | if page_info.only_content: | ||||
# extract properties from HTML comments | # extract properties from HTML comments | ||||
load_properties(page_info) | load_properties(page_info) | ||||
return page_info | return page_info | ||||
def get_source(page_info): | |||||
def setup_source(page_info): | |||||
'''Get the HTML source of the template''' | '''Get the HTML source of the template''' | ||||
from markdown2 import markdown | from markdown2 import markdown | ||||
jenv = frappe.get_jenv() | jenv = frappe.get_jenv() | ||||
@@ -225,46 +230,52 @@ def get_source(page_info): | |||||
else: | else: | ||||
html = source | html = source | ||||
page_info.source = html | |||||
# show table of contents | # show table of contents | ||||
setup_index(page_info) | setup_index(page_info) | ||||
return html | |||||
def setup_index(page_info): | def setup_index(page_info): | ||||
'''Build page sequence from index.txt''' | |||||
if page_info.basename=='index': | |||||
# load index.txt if loading all pages | |||||
index_txt_path = os.path.join(page_info.basepath, 'index.txt') | |||||
if os.path.exists(index_txt_path): | |||||
page_info.index = open(index_txt_path, 'r').read().splitlines() | |||||
def make_toc(context, out): | |||||
'''Insert full index (table of contents) for {index} tag''' | '''Insert full index (table of contents) for {index} tag''' | ||||
from frappe.website.utils import get_full_index | from frappe.website.utils import get_full_index | ||||
if page_info.basename=='index': | |||||
if frappe.local.flags.in_get_all_pages: | |||||
# load index.txt if loading all pages | |||||
index_txt_path = os.path.join(page_info.basepath, 'index.txt') | |||||
if os.path.exists(index_txt_path): | |||||
page_info.index = open(index_txt_path, 'r').read().splitlines() | |||||
elif '\n{index}' in page_info.source: | |||||
html = frappe.get_template("templates/includes/full_index.html").render({ | |||||
"full_index": get_full_index(), | |||||
"url_prefix": None | |||||
}) | |||||
page_info.source.replace('{index}', html) | |||||
if (not frappe.local.flags.in_get_all_pages) and ('\n{next}' in page_info.source): | |||||
if '{index}' in out: | |||||
html = frappe.get_template("templates/includes/full_index.html").render({ | |||||
"full_index": get_full_index(), | |||||
"url_prefix": context.url_prefix or "/", | |||||
"route": context.route | |||||
}) | |||||
out = out.replace('{index}', html) | |||||
if '{next}' in out: | |||||
# insert next link | # insert next link | ||||
next_item = None | next_item = None | ||||
children_map = get_full_index() | children_map = get_full_index() | ||||
parent_route = os.path.dirname(page_info.route) | |||||
parent_route = os.path.dirname(context.route) | |||||
children = children_map[parent_route] | children = children_map[parent_route] | ||||
if parent_route and children: | if parent_route and children: | ||||
for i, c in enumerate(children): | for i, c in enumerate(children): | ||||
if c.route == page_info.route and i < (len(children) - 1): | |||||
if c.route == context.route and i < (len(children) - 1): | |||||
next_item = children[i+1] | next_item = children[i+1] | ||||
next_item.url_prefix = context.url_prefix or "/" | |||||
if next_item: | if next_item: | ||||
html = ('<p class="btn-next-wrapper">'+_("Next")\ | |||||
+': <a class="btn-next" href="{route}.html">{title}</a></p>').format(**next_item) | |||||
if next_item.route and next_item.title: | |||||
html = ('<p class="btn-next-wrapper">'+_("Next")\ | |||||
+': <a class="btn-next" href="{url_prefix}{route}">{title}</a></p>').format(**next_item) | |||||
page_info.source.replace('{next}', html) | |||||
out = out.replace('{next}', html) | |||||
return out | |||||
def load_properties(page_info): | def load_properties(page_info): | ||||
@@ -185,7 +185,7 @@ def abs_url(path): | |||||
def get_full_index(route=None, extn = False): | def get_full_index(route=None, extn = False): | ||||
"""Returns full index of the website for www upto the n-th level""" | """Returns full index of the website for www upto the n-th level""" | ||||
if not frappe.local.flags.children_map: | if not frappe.local.flags.children_map: | ||||
from frappe.websites.router import get_pages | |||||
from frappe.website.router import get_pages | |||||
children_map = {} | children_map = {} | ||||
pages = get_pages() | pages = get_pages() | ||||