Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

398 linhas
11 KiB

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