You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

374 line
10 KiB

  1. # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals, print_function
  4. from six.moves import input
  5. import frappe, os, re, git
  6. from frappe.utils import touch_file, cstr
  7. def make_boilerplate(dest, app_name):
  8. if not os.path.exists(dest):
  9. print("Destination directory does not exist")
  10. return
  11. # app_name should be in snake_case
  12. app_name = frappe.scrub(app_name)
  13. hooks = frappe._dict()
  14. hooks.app_name = app_name
  15. app_title = hooks.app_name.replace("_", " ").title()
  16. for key in ("App Title (default: {0})".format(app_title),
  17. "App Description", "App Publisher", "App Email",
  18. "App Icon (default 'octicon octicon-file-directory')",
  19. "App Color (default 'grey')",
  20. "App License (default 'MIT')"):
  21. hook_key = key.split(" (")[0].lower().replace(" ", "_")
  22. hook_val = None
  23. while not hook_val:
  24. hook_val = cstr(input(key + ": "))
  25. if not hook_val:
  26. defaults = {
  27. "app_title": app_title,
  28. "app_icon": "octicon octicon-file-directory",
  29. "app_color": "grey",
  30. "app_license": "MIT"
  31. }
  32. if hook_key in defaults:
  33. hook_val = defaults[hook_key]
  34. if hook_key=="app_name" and hook_val.lower().replace(" ", "_") != hook_val:
  35. print("App Name must be all lowercase and without spaces")
  36. hook_val = ""
  37. elif hook_key=="app_title" and not re.match(r"^(?![\W])[^\d_\s][\w -]+$", hook_val, re.UNICODE):
  38. print("App Title should start with a letter and it can only consist of letters, numbers, spaces and underscores")
  39. hook_val = ""
  40. hooks[hook_key] = hook_val
  41. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, frappe.scrub(hooks.app_title)),
  42. with_init=True)
  43. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates"), with_init=True)
  44. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "www"))
  45. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
  46. "pages"), with_init=True)
  47. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "templates",
  48. "includes"))
  49. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "config"), with_init=True)
  50. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "public",
  51. "css"))
  52. frappe.create_folder(os.path.join(dest, hooks.app_name, hooks.app_name, "public",
  53. "js"))
  54. with open(os.path.join(dest, hooks.app_name, hooks.app_name, "__init__.py"), "w") as f:
  55. f.write(frappe.as_unicode(init_template))
  56. with open(os.path.join(dest, hooks.app_name, "MANIFEST.in"), "w") as f:
  57. f.write(frappe.as_unicode(manifest_template.format(**hooks)))
  58. with open(os.path.join(dest, hooks.app_name, ".gitignore"), "w") as f:
  59. f.write(frappe.as_unicode(gitignore_template.format(app_name = hooks.app_name)))
  60. with open(os.path.join(dest, hooks.app_name, "setup.py"), "w") as f:
  61. f.write(frappe.as_unicode(setup_template.format(**hooks)))
  62. with open(os.path.join(dest, hooks.app_name, "requirements.txt"), "w") as f:
  63. f.write("frappe")
  64. with open(os.path.join(dest, hooks.app_name, "README.md"), "w") as f:
  65. f.write(frappe.as_unicode("## {0}\n\n{1}\n\n#### License\n\n{2}".format(hooks.app_title,
  66. hooks.app_description, hooks.app_license)))
  67. with open(os.path.join(dest, hooks.app_name, "license.txt"), "w") as f:
  68. f.write(frappe.as_unicode("License: " + hooks.app_license))
  69. with open(os.path.join(dest, hooks.app_name, hooks.app_name, "modules.txt"), "w") as f:
  70. f.write(frappe.as_unicode(hooks.app_title))
  71. with open(os.path.join(dest, hooks.app_name, hooks.app_name, "hooks.py"), "w") as f:
  72. f.write(frappe.as_unicode(hooks_template.format(**hooks)))
  73. touch_file(os.path.join(dest, hooks.app_name, hooks.app_name, "patches.txt"))
  74. with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "desktop.py"), "w") as f:
  75. f.write(frappe.as_unicode(desktop_template.format(**hooks)))
  76. with open(os.path.join(dest, hooks.app_name, hooks.app_name, "config", "docs.py"), "w") as f:
  77. f.write(frappe.as_unicode(docs_template.format(**hooks)))
  78. # initialize git repository
  79. app_directory = os.path.join(dest, hooks.app_name)
  80. app_repo = git.Repo.init(app_directory)
  81. app_repo.git.add(A=True)
  82. app_repo.index.commit("feat: Initialize App")
  83. print("'{app}' created at {path}".format(app=app_name, path=app_directory))
  84. manifest_template = """include MANIFEST.in
  85. include requirements.txt
  86. include *.json
  87. include *.md
  88. include *.py
  89. include *.txt
  90. recursive-include {app_name} *.css
  91. recursive-include {app_name} *.csv
  92. recursive-include {app_name} *.html
  93. recursive-include {app_name} *.ico
  94. recursive-include {app_name} *.js
  95. recursive-include {app_name} *.json
  96. recursive-include {app_name} *.md
  97. recursive-include {app_name} *.png
  98. recursive-include {app_name} *.py
  99. recursive-include {app_name} *.svg
  100. recursive-include {app_name} *.txt
  101. recursive-exclude {app_name} *.pyc"""
  102. init_template = """
  103. __version__ = '0.0.1'
  104. """
  105. hooks_template = """from . import __version__ as app_version
  106. app_name = "{app_name}"
  107. app_title = "{app_title}"
  108. app_publisher = "{app_publisher}"
  109. app_description = "{app_description}"
  110. app_icon = "{app_icon}"
  111. app_color = "{app_color}"
  112. app_email = "{app_email}"
  113. app_license = "{app_license}"
  114. # Includes in <head>
  115. # ------------------
  116. # include js, css files in header of desk.html
  117. # app_include_css = "/assets/{app_name}/css/{app_name}.css"
  118. # app_include_js = "/assets/{app_name}/js/{app_name}.js"
  119. # include js, css files in header of web template
  120. # web_include_css = "/assets/{app_name}/css/{app_name}.css"
  121. # web_include_js = "/assets/{app_name}/js/{app_name}.js"
  122. # include custom scss in every website theme (without file extension ".scss")
  123. # website_theme_scss = "{app_name}/public/scss/website"
  124. # include js, css files in header of web form
  125. # webform_include_js = {{"doctype": "public/js/doctype.js"}}
  126. # webform_include_css = {{"doctype": "public/css/doctype.css"}}
  127. # include js in page
  128. # page_js = {{"page" : "public/js/file.js"}}
  129. # include js in doctype views
  130. # doctype_js = {{"doctype" : "public/js/doctype.js"}}
  131. # doctype_list_js = {{"doctype" : "public/js/doctype_list.js"}}
  132. # doctype_tree_js = {{"doctype" : "public/js/doctype_tree.js"}}
  133. # doctype_calendar_js = {{"doctype" : "public/js/doctype_calendar.js"}}
  134. # Home Pages
  135. # ----------
  136. # application home page (will override Website Settings)
  137. # home_page = "login"
  138. # website user home page (by Role)
  139. # role_home_page = {{
  140. # "Role": "home_page"
  141. # }}
  142. # Generators
  143. # ----------
  144. # automatically create page for each record of this doctype
  145. # website_generators = ["Web Page"]
  146. # Jinja
  147. # ----------
  148. # add methods and filters to jinja environment
  149. # jinja = {{
  150. # "methods": "{app_name}.utils.jinja_methods",
  151. # "filters": "{app_name}.utils.jinja_filters"
  152. # }}
  153. # Installation
  154. # ------------
  155. # before_install = "{app_name}.install.before_install"
  156. # after_install = "{app_name}.install.after_install"
  157. # Desk Notifications
  158. # ------------------
  159. # See frappe.core.notifications.get_notification_config
  160. # notification_config = "{app_name}.notifications.get_notification_config"
  161. # Permissions
  162. # -----------
  163. # Permissions evaluated in scripted ways
  164. # permission_query_conditions = {{
  165. # "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions",
  166. # }}
  167. #
  168. # has_permission = {{
  169. # "Event": "frappe.desk.doctype.event.event.has_permission",
  170. # }}
  171. # DocType Class
  172. # ---------------
  173. # Override standard doctype classes
  174. # override_doctype_class = {{
  175. # "ToDo": "custom_app.overrides.CustomToDo"
  176. # }}
  177. # Document Events
  178. # ---------------
  179. # Hook on document methods and events
  180. # doc_events = {{
  181. # "*": {{
  182. # "on_update": "method",
  183. # "on_cancel": "method",
  184. # "on_trash": "method"
  185. # }}
  186. # }}
  187. # Scheduled Tasks
  188. # ---------------
  189. # scheduler_events = {{
  190. # "all": [
  191. # "{app_name}.tasks.all"
  192. # ],
  193. # "daily": [
  194. # "{app_name}.tasks.daily"
  195. # ],
  196. # "hourly": [
  197. # "{app_name}.tasks.hourly"
  198. # ],
  199. # "weekly": [
  200. # "{app_name}.tasks.weekly"
  201. # ],
  202. # "monthly": [
  203. # "{app_name}.tasks.monthly"
  204. # ],
  205. # }}
  206. # Testing
  207. # -------
  208. # before_tests = "{app_name}.install.before_tests"
  209. # Overriding Methods
  210. # ------------------------------
  211. #
  212. # override_whitelisted_methods = {{
  213. # "frappe.desk.doctype.event.event.get_events": "{app_name}.event.get_events"
  214. # }}
  215. #
  216. # each overriding function accepts a `data` argument;
  217. # generated from the base implementation of the doctype dashboard,
  218. # along with any modifications made in other Frappe apps
  219. # override_doctype_dashboards = {{
  220. # "Task": "{app_name}.task.get_dashboard_data"
  221. # }}
  222. # exempt linked doctypes from being automatically cancelled
  223. #
  224. # auto_cancel_exempted_doctypes = ["Auto Repeat"]
  225. # User Data Protection
  226. # --------------------
  227. # user_data_fields = [
  228. # {
  229. # "doctype": "{{doctype_1}}",
  230. # "filter_by": "{{filter_by}}",
  231. # "redact_fields": ["{{field_1}}", "{{field_2}}"],
  232. # "partial": 1,
  233. # },
  234. # {
  235. # "doctype": "{{doctype_2}}",
  236. # "filter_by": "{{filter_by}}",
  237. # "partial": 1,
  238. # },
  239. # {
  240. # "doctype": "{{doctype_3}}",
  241. # "strict": False,
  242. # },
  243. # {
  244. # "doctype": "{{doctype_4}}"
  245. # }
  246. # ]
  247. # Authentication and authorization
  248. # --------------------------------
  249. # auth_hooks = [
  250. # "{app_name}.auth.validate"
  251. # ]
  252. """
  253. desktop_template = """from frappe import _
  254. def get_data():
  255. return [
  256. {{
  257. "module_name": "{app_title}",
  258. "color": "{app_color}",
  259. "icon": "{app_icon}",
  260. "type": "module",
  261. "label": _("{app_title}")
  262. }}
  263. ]
  264. """
  265. setup_template = """from setuptools import setup, find_packages
  266. with open('requirements.txt') as f:
  267. install_requires = f.read().strip().split('\\n')
  268. # get version from __version__ variable in {app_name}/__init__.py
  269. from {app_name} import __version__ as version
  270. setup(
  271. name='{app_name}',
  272. version=version,
  273. description='{app_description}',
  274. author='{app_publisher}',
  275. author_email='{app_email}',
  276. packages=find_packages(),
  277. zip_safe=False,
  278. include_package_data=True,
  279. install_requires=install_requires
  280. )
  281. """
  282. gitignore_template = """.DS_Store
  283. *.pyc
  284. *.egg-info
  285. *.swp
  286. tags
  287. {app_name}/docs/current"""
  288. docs_template = '''"""
  289. Configuration for docs
  290. """
  291. # source_link = "https://github.com/[org_name]/{app_name}"
  292. # docs_base_url = "https://[org_name].github.io/{app_name}"
  293. # headline = "App that does everything"
  294. # sub_heading = "Yes, you got that right the first time, everything"
  295. def get_context(context):
  296. context.brand_html = "{app_title}"
  297. '''