Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 
 
 
 

447 righe
14 KiB

  1. """Automatically setup docs for a project
  2. Call from command line:
  3. bench setup-docs app path
  4. """
  5. import os, json, frappe, shutil, re
  6. import frappe.website.statics
  7. from frappe.website.context import get_context
  8. from frappe.utils import markdown
  9. class setup_docs(object):
  10. def __init__(self, app):
  11. """Generate source templates for models reference and module API
  12. and templates at `templates/autodoc`
  13. """
  14. self.app = app
  15. frappe.local.flags.web_pages_folders = ['docs',]
  16. frappe.local.flags.web_pages_apps = [self.app,]
  17. self.hooks = frappe.get_hooks(app_name = self.app)
  18. self.app_title = self.hooks.get("app_title")[0]
  19. self.setup_app_context()
  20. def setup_app_context(self):
  21. self.docs_config = frappe.get_module(self.app + ".config.docs")
  22. version = self.hooks.get("app_version")[0]
  23. self.app_context = {
  24. "app": frappe._dict({
  25. "name": self.app,
  26. "title": self.app_title,
  27. "description": self.hooks.get("app_description")[0],
  28. "version": version,
  29. "publisher": self.hooks.get("app_publisher")[0],
  30. "icon": self.hooks.get("app_icon")[0],
  31. "email": self.hooks.get("app_email")[0],
  32. "headline": self.docs_config.headline,
  33. "sub_heading": self.docs_config.sub_heading,
  34. "source_link": self.docs_config.source_link,
  35. "hide_install": getattr(self.docs_config, "hide_install", False),
  36. "docs_base_url": self.docs_config.docs_base_url,
  37. "long_description": markdown(getattr(self.docs_config, "long_description", "")),
  38. "license": self.hooks.get("app_license")[0],
  39. "branch": getattr(self.docs_config, "branch", None) or "develop",
  40. "style": getattr(self.docs_config, "style", "")
  41. }),
  42. "metatags": {
  43. "description": self.hooks.get("app_description")[0],
  44. },
  45. "get_doctype_app": frappe.get_doctype_app
  46. }
  47. def build(self, docs_version):
  48. """Build templates for docs models and Python API"""
  49. self.docs_path = frappe.get_app_path(self.app, "docs")
  50. self.path = os.path.join(self.docs_path, docs_version)
  51. self.app_context["app"]["docs_version"] = docs_version
  52. self.app_title = self.hooks.get("app_title")[0]
  53. self.app_path = frappe.get_app_path(self.app)
  54. print "Deleting current..."
  55. shutil.rmtree(self.path, ignore_errors = True)
  56. os.makedirs(self.path)
  57. self.make_home_pages()
  58. for basepath, folders, files in os.walk(self.app_path):
  59. # make module home page
  60. if "/doctype/" not in basepath and "doctype" in folders:
  61. module = os.path.basename(basepath)
  62. module_folder = os.path.join(self.models_base_path, module)
  63. self.make_folder(module_folder,
  64. template = "templates/autodoc/module_home.html",
  65. context = {"name": module})
  66. self.update_index_txt(module_folder)
  67. # make for model files
  68. if "/doctype/" in basepath:
  69. parts = basepath.split("/")
  70. #print parts
  71. module, doctype = parts[-3], parts[-1]
  72. if doctype not in ("doctype", "boilerplate"):
  73. self.write_model_file(basepath, module, doctype)
  74. # standard python module
  75. if self.is_py_module(basepath, folders, files):
  76. self.write_modules(basepath, folders, files)
  77. self.build_user_docs()
  78. def make_home_pages(self):
  79. """Make standard home pages for docs, developer docs, api and models
  80. from templates"""
  81. # make dev home page
  82. with open(os.path.join(self.docs_path, "index.html"), "w") as home:
  83. home.write(frappe.render_template("templates/autodoc/docs_home.html",
  84. self.app_context))
  85. # make dev home page
  86. with open(os.path.join(self.path, "index.html"), "w") as home:
  87. home.write(frappe.render_template("templates/autodoc/dev_home.html",
  88. self.app_context))
  89. # make folders
  90. self.models_base_path = os.path.join(self.path, "models")
  91. self.make_folder(self.models_base_path,
  92. template = "templates/autodoc/models_home.html")
  93. self.api_base_path = os.path.join(self.path, "api")
  94. self.make_folder(self.api_base_path,
  95. template = "templates/autodoc/api_home.html")
  96. # make /user
  97. user_path = os.path.join(self.docs_path, "user")
  98. if not os.path.exists(user_path):
  99. os.makedirs(user_path)
  100. # make /assets/img
  101. img_path = os.path.join(self.docs_path, "assets", "img")
  102. if not os.path.exists(img_path):
  103. os.makedirs(img_path)
  104. def build_user_docs(self):
  105. """Build templates for user docs pages, if missing."""
  106. #user_docs_path = os.path.join(self.docs_path, "user")
  107. # license
  108. with open(os.path.join(self.app_path, "..", "license.txt"), "r") as license_file:
  109. self.app_context["license_text"] = markdown(license_file.read())
  110. html = frappe.render_template("templates/autodoc/license.html",
  111. context = self.app_context)
  112. with open(os.path.join(self.docs_path, "license.html"), "w") as license_file:
  113. license_file.write(html.encode("utf-8"))
  114. # contents
  115. shutil.copy(os.path.join(frappe.get_app_path("frappe", "templates", "autodoc",
  116. "contents.html")), os.path.join(self.docs_path, "contents.html"))
  117. shutil.copy(os.path.join(frappe.get_app_path("frappe", "templates", "autodoc",
  118. "contents.py")), os.path.join(self.docs_path, "contents.py"))
  119. # install
  120. html = frappe.render_template("templates/autodoc/install.md",
  121. context = self.app_context)
  122. with open(os.path.join(self.docs_path, "install.md"), "w") as f:
  123. f.write(html)
  124. self.update_index_txt(self.docs_path)
  125. def make_docs(self, target, local = False):
  126. self.target = target
  127. self.local = local
  128. if self.local:
  129. self.docs_base_url = ""
  130. else:
  131. self.docs_base_url = self.docs_config.docs_base_url
  132. # add for processing static files (full-index)
  133. frappe.local.docs_base_url = self.docs_base_url
  134. # write in target path
  135. self.write_files()
  136. # copy assets/js, assets/css, assets/img
  137. self.copy_assets()
  138. def is_py_module(self, basepath, folders, files):
  139. return "__init__.py" in files \
  140. and (not "/doctype" in basepath) \
  141. and (not "/patches" in basepath) \
  142. and (not "/change_log" in basepath) \
  143. and (not "/report" in basepath) \
  144. and (not "/page" in basepath) \
  145. and (not "/templates" in basepath) \
  146. and (not "/tests" in basepath) \
  147. and (not "/docs" in basepath)
  148. def write_modules(self, basepath, folders, files):
  149. module_folder = os.path.join(self.api_base_path, os.path.relpath(basepath, self.app_path))
  150. self.make_folder(module_folder)
  151. for f in files:
  152. if f.endswith(".py"):
  153. module_name = os.path.relpath(os.path.join(basepath, f),
  154. self.app_path)[:-3].replace("/", ".").replace(".__init__", "")
  155. module_doc_path = os.path.join(module_folder,
  156. self.app + "." + module_name + ".html")
  157. self.make_folder(basepath)
  158. if not os.path.exists(module_doc_path):
  159. print "Writing " + module_doc_path
  160. with open(module_doc_path, "w") as f:
  161. context = {"name": self.app + "." + module_name}
  162. context.update(self.app_context)
  163. f.write(frappe.render_template("templates/autodoc/pymodule.html",
  164. context))
  165. self.update_index_txt(module_folder)
  166. def make_folder(self, path, template=None, context=None):
  167. if not template:
  168. template = "templates/autodoc/package_index.html"
  169. if not os.path.exists(path):
  170. os.makedirs(path)
  171. index_txt_path = os.path.join(path, "index.txt")
  172. print "Writing " + index_txt_path
  173. with open(index_txt_path, "w") as f:
  174. f.write("")
  175. index_html_path = os.path.join(path, "index.html")
  176. if not context:
  177. name = os.path.basename(path)
  178. if name==".":
  179. name = self.app
  180. context = {
  181. "title": name
  182. }
  183. context.update(self.app_context)
  184. print "Writing " + index_html_path
  185. with open(index_html_path, "w") as f:
  186. f.write(frappe.render_template(template, context))
  187. def update_index_txt(self, path):
  188. index_txt_path = os.path.join(path, "index.txt")
  189. pages = filter(lambda d: ((d.endswith(".html") or d.endswith(".md")) and d not in ("index.html",)) \
  190. or os.path.isdir(os.path.join(path, d)), os.listdir(path))
  191. pages = [d.rsplit(".", 1)[0] for d in pages]
  192. index_parts = []
  193. if os.path.exists(index_txt_path):
  194. with open(index_txt_path, "r") as f:
  195. index_parts = filter(None, f.read().splitlines())
  196. if not set(pages).issubset(set(index_parts)):
  197. print "Updating " + index_txt_path
  198. with open(index_txt_path, "w") as f:
  199. f.write("\n".join(pages))
  200. def write_model_file(self, basepath, module, doctype):
  201. model_path = os.path.join(self.models_base_path, module, doctype + ".html")
  202. if not os.path.exists(model_path):
  203. model_json_path = os.path.join(basepath, doctype + ".json")
  204. if os.path.exists(model_json_path):
  205. with open(model_json_path, "r") as j:
  206. doctype_real_name = json.loads(j.read()).get("name")
  207. print "Writing " + model_path
  208. with open(model_path, "w") as f:
  209. context = {"doctype": doctype_real_name}
  210. context.update(self.app_context)
  211. f.write(frappe.render_template("templates/autodoc/doctype.html",
  212. context).encode("utf-8"))
  213. def write_files(self):
  214. """render templates and write files to target folder"""
  215. frappe.local.flags.home_page = "index"
  216. from frappe.website.router import get_pages, make_toc
  217. pages = get_pages()
  218. # clear the user, current folder in target
  219. shutil.rmtree(os.path.join(self.target, "user"), ignore_errors=True)
  220. shutil.rmtree(os.path.join(self.target, "current"), ignore_errors=True)
  221. cnt = 0
  222. for path, context in pages.iteritems():
  223. print "Writing {0}".format(path)
  224. # set this for get_context / website libs
  225. frappe.local.path = path
  226. context.update({
  227. "page_links_with_extn": True,
  228. "relative_links": True,
  229. "docs_base_url": self.docs_base_url,
  230. "url_prefix": self.docs_base_url,
  231. })
  232. context.update(self.app_context)
  233. context = get_context(path, context)
  234. if context.basename:
  235. target_path_fragment = context.route + '.html'
  236. else:
  237. # index.html
  238. target_path_fragment = context.route + '/index.html'
  239. target_filename = os.path.join(self.target, target_path_fragment.strip('/'))
  240. context.brand_html = context.top_bar_items = context.favicon = None
  241. self.docs_config.get_context(context)
  242. if not context.brand_html:
  243. if context.docs_icon:
  244. context.brand_html = '<i class="{0}"></i> {1}'.format(context.docs_icon, context.app.title)
  245. else:
  246. context.brand_html = context.app.title
  247. if not context.top_bar_items:
  248. context.top_bar_items = [
  249. # {"label": "Contents", "url": self.docs_base_url + "/contents.html", "right": 1},
  250. {"label": "User Guide", "url": self.docs_base_url + "/user", "right": 1},
  251. {"label": "Developer Docs", "url": self.docs_base_url + "/current", "right": 1},
  252. ]
  253. context.top_bar_items = [{"label": '<i class="octicon octicon-search"></i>', "url": "#",
  254. "right": 1}] + context.top_bar_items
  255. context.parents = []
  256. parent_route = os.path.dirname(context.route)
  257. if pages[parent_route]:
  258. context.parents = [pages[parent_route]]
  259. if not context.favicon:
  260. context.favicon = "/assets/img/favicon.ico"
  261. context.only_static = True
  262. context.base_template_path = "templates/autodoc/base_template.html"
  263. if '<code>' in context.source:
  264. context.source = re.sub('\<code\>(.*)\</code\>', '<code>{% raw %}\g<1>{% endraw %}</code>', context.source)
  265. html = frappe.render_template(context.source, context)
  266. html = make_toc(context, html)
  267. if not "<!-- autodoc -->" in html:
  268. html = html.replace('<!-- edit-link -->',
  269. edit_link.format(\
  270. source_link = self.docs_config.source_link,
  271. app_name = self.app,
  272. branch = context.app.branch,
  273. target = target_path_fragment))
  274. if not os.path.exists(os.path.dirname(target_filename)):
  275. os.makedirs(os.path.dirname(target_filename))
  276. with open(target_filename, "w") as htmlfile:
  277. htmlfile.write(html.encode("utf-8"))
  278. cnt += 1
  279. print "Wrote {0} files".format(cnt)
  280. def copy_assets(self):
  281. """Copy jquery, bootstrap and other assets to files"""
  282. print "Copying assets..."
  283. assets_path = os.path.join(self.target, "assets")
  284. # copy assets from docs
  285. source_assets = frappe.get_app_path(self.app, "docs", "assets")
  286. if os.path.exists(source_assets):
  287. for basepath, folders, files in os.walk(source_assets):
  288. target_basepath = os.path.join(assets_path, os.path.relpath(basepath, source_assets))
  289. # make the base folder
  290. if not os.path.exists(target_basepath):
  291. os.makedirs(target_basepath)
  292. # copy all files in the current folder
  293. for f in files:
  294. shutil.copy(os.path.join(basepath, f), os.path.join(target_basepath, f))
  295. # make missing folders
  296. for fname in ("js", "css", "img"):
  297. path = os.path.join(assets_path, fname)
  298. if not os.path.exists(path):
  299. os.makedirs(path)
  300. copy_files = {
  301. "js/lib/jquery/jquery.min.js": "js/jquery.min.js",
  302. "js/lib/bootstrap.min.js": "js/bootstrap.min.js",
  303. "js/lib/highlight.pack.js": "js/highlight.pack.js",
  304. "js/docs.js": "js/docs.js",
  305. "css/bootstrap.css": "css/bootstrap.css",
  306. "css/font-awesome.css": "css/font-awesome.css",
  307. "css/docs.css": "css/docs.css",
  308. "css/hljs.css": "css/hljs.css",
  309. "css/font": "css/font",
  310. "css/octicons": "css/octicons",
  311. # always overwrite octicons.css to fix the path
  312. "css/octicons/octicons.css": "css/octicons/octicons.css",
  313. "images/frappe-bird-grey.svg": "img/frappe-bird-grey.svg",
  314. "images/background.png": "img/background.png",
  315. "images/smiley.png": "img/smiley.png",
  316. "images/up.png": "img/up.png"
  317. }
  318. for source, target in copy_files.iteritems():
  319. source_path = frappe.get_app_path("frappe", "public", source)
  320. if os.path.isdir(source_path):
  321. if not os.path.exists(os.path.join(assets_path, target)):
  322. shutil.copytree(source_path, os.path.join(assets_path, target))
  323. else:
  324. shutil.copy(source_path, os.path.join(assets_path, target))
  325. # fix path for font-files, background
  326. files = (
  327. os.path.join(assets_path, "css", "octicons", "octicons.css"),
  328. os.path.join(assets_path, "css", "font-awesome.css"),
  329. os.path.join(assets_path, "css", "docs.css"),
  330. )
  331. for path in files:
  332. with open(path, "r") as css_file:
  333. text = css_file.read()
  334. with open(path, "w") as css_file:
  335. if "docs.css" in path:
  336. css_file.write(text.replace("/assets/img/",
  337. self.docs_base_url + '/assets/img/'))
  338. else:
  339. css_file.write(text.replace("/assets/frappe/", self.docs_base_url + '/assets/'))
  340. edit_link = '''
  341. <div class="page-container">
  342. <div class="page-content">
  343. <div class="edit-container text-center">
  344. <i class="icon icon-smile"></i>
  345. <a class="text-muted edit" href="{source_link}/blob/{branch}/{app_name}/docs/{target}">
  346. Improve this page
  347. </a>
  348. </div>
  349. </div>
  350. </div>'''