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.
 
 
 
 
 
 

497 lines
15 KiB

  1. #!/usr/bin/env python
  2. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
  3. # MIT License. See license.txt
  4. from __future__ import unicode_literals
  5. import sys
  6. if __name__=="__main__":
  7. sys.path = [".", "lib", "app"] + sys.path
  8. import webnotes
  9. def main():
  10. parsed_args = webnotes._dict(vars(setup_parser()))
  11. fn = get_function(parsed_args)
  12. if parsed_args.get("site")=="all":
  13. for site in get_sites():
  14. args = parsed_args.copy()
  15. args["site"] = site
  16. run(fn, args)
  17. else:
  18. run(fn, parsed_args)
  19. def cmd(fn):
  20. def new_fn(*args, **kwargs):
  21. import inspect
  22. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  23. new_kwargs = {}
  24. for a in fnargs:
  25. if a in kwargs:
  26. new_kwargs[a] = kwargs.get(a)
  27. return fn(*args, **new_kwargs)
  28. return new_fn
  29. def run(fn, args):
  30. if isinstance(args.get(fn), (list, tuple)):
  31. out = globals().get(fn)(*args.get(fn), **args)
  32. else:
  33. out = globals().get(fn)(**args)
  34. webnotes.destroy()
  35. return out
  36. def get_function(args):
  37. for fn, val in args.items():
  38. if (val or isinstance(val, list)) and globals().get(fn):
  39. return fn
  40. def get_sites():
  41. import os
  42. import conf
  43. return [site for site in os.listdir(conf.sites_dir)
  44. if not os.path.islink(os.path.join(conf.sites_dir, site))]
  45. def setup_parser():
  46. import argparse
  47. parser = argparse.ArgumentParser(description="Run webnotes utility functions")
  48. setup_install(parser)
  49. setup_utilities(parser)
  50. setup_translation(parser)
  51. setup_git(parser)
  52. # common
  53. parser.add_argument("-f", "--force", default=False, action="store_true",
  54. help="Force execution where applicable (look for [-f] in help)")
  55. parser.add_argument("--quiet", default=True, action="store_false", dest="verbose",
  56. help="Show verbose output where applicable")
  57. parser.add_argument("--site", nargs="?", metavar="SITE-NAME or all",
  58. help="Run for a particular site")
  59. return parser.parse_args()
  60. def setup_install(parser):
  61. parser.add_argument("--install", metavar="DB-NAME", nargs=1,
  62. help="Install a new app")
  63. parser.add_argument("--root-password", nargs=1,
  64. help="Root password for new app")
  65. parser.add_argument("--reinstall", default=False, action="store_true",
  66. help="Install a fresh app in db_name specified in conf.py")
  67. parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2,
  68. help="Restore from an sql file")
  69. parser.add_argument("--install_fixtures", default=False, action="store_true",
  70. help="(Re)Install install-fixtures from app/startup/install_fixtures")
  71. parser.add_argument("--make_demo", default=False, action="store_true",
  72. help="Install demo in demo_db_name specified in conf.py")
  73. parser.add_argument("--make_demo_fresh", default=False, action="store_true",
  74. help="(Re)Install demo in demo_db_name specified in conf.py")
  75. def setup_utilities(parser):
  76. # update
  77. parser.add_argument("-u", "--update", nargs="*", metavar=("REMOTE", "BRANCH"),
  78. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  79. parser.add_argument("--patch", nargs=1, metavar="PATCH-MODULE",
  80. help="Run a particular patch [-f]")
  81. parser.add_argument("-l", "--latest", default=False, action="store_true",
  82. help="Run patches, sync schema and rebuild files/translations")
  83. parser.add_argument("--sync_all", default=False, action="store_true",
  84. help="Reload all doctypes, pages, etc. using txt files [-f]")
  85. parser.add_argument("--update_all_sites", nargs="*", metavar=("REMOTE", "BRANCH"),
  86. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  87. parser.add_argument("--reload_doc", nargs=3,
  88. metavar=('"MODULE"', '"DOCTYPE"', '"DOCNAME"'))
  89. # build
  90. parser.add_argument("-b", "--build", default=False, action="store_true",
  91. help="Minify + concatenate JS and CSS files, build translations")
  92. parser.add_argument("-w", "--watch", default=False, action="store_true",
  93. help="Watch and concatenate JS and CSS files as and when they change")
  94. # misc
  95. parser.add_argument("--backup", default=False, action="store_true",
  96. help="Take backup of database in backup folder [--with_files]")
  97. parser.add_argument("--with_files", default=False, action="store_true",
  98. help="Also take backup of files")
  99. parser.add_argument("--docs", default=False, action="store_true",
  100. help="Build docs")
  101. parser.add_argument("--domain", nargs="*",
  102. help="Get or set domain in Website Settings")
  103. parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
  104. help="Create new conf.py file")
  105. # clear
  106. parser.add_argument("--clear_web", default=False, action="store_true",
  107. help="Clear website cache")
  108. parser.add_argument("--clear_cache", default=False, action="store_true",
  109. help="Clear cache, doctype cache and defaults")
  110. parser.add_argument("--reset_perms", default=False, action="store_true",
  111. help="Reset permissions for all doctypes")
  112. # scheduler
  113. parser.add_argument("--run_scheduler", default=False, action="store_true",
  114. help="Trigger scheduler")
  115. parser.add_argument("--run_scheduler_event", nargs=1,
  116. metavar="all | daily | weekly | monthly",
  117. help="Run a scheduler event")
  118. # replace
  119. parser.add_argument("--replace", nargs=3,
  120. metavar=("SEARCH-REGEX", "REPLACE-BY", "FILE-EXTN"),
  121. help="Multi-file search-replace [-f]")
  122. # import/export
  123. parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"'))
  124. parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"),
  125. help="""Export doclist as json to the given path, use '-' as name for Singles.""")
  126. parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"),
  127. help="""Dump DocType as csv""")
  128. parser.add_argument("--import_doclist", nargs=1, metavar="PATH",
  129. help="""Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported""")
  130. def setup_git(parser):
  131. parser.add_argument("--pull", nargs="*", metavar=("REMOTE", "BRANCH"),
  132. help="Run git pull for both repositories")
  133. parser.add_argument("-p", "--push", nargs="*", metavar=("REMOTE", "BRANCH"),
  134. help="Run git push for both repositories")
  135. parser.add_argument("--status", default=False, action="store_true",
  136. help="Run git status for both repositories")
  137. parser.add_argument("--commit", nargs=1, metavar="COMMIT-MSG",
  138. help="Run git commit COMMIT-MSG for both repositories")
  139. parser.add_argument("--checkout", nargs=1, metavar="BRANCH",
  140. help="Run git checkout BRANCH for both repositories")
  141. parser.add_argument("--git", nargs="*", metavar="OPTIONS",
  142. help="Run git command for both repositories")
  143. def setup_translation(parser):
  144. parser.add_argument("--build_message_files", default=False, action="store_true",
  145. help="Build message files for translation")
  146. parser.add_argument("--export_messages", nargs=2, metavar=("LANG-CODE", "FILENAME"),
  147. help="""Export all messages for a language to translation in a csv file.
  148. Example, lib/wnf.py --export_messages hi hindi.csv""")
  149. parser.add_argument("--import_messages", nargs=2, metavar=("LANG-CODE", "FILENAME"),
  150. help="""Import messages for a language and make language files.
  151. Example, lib/wnf.py --import_messages hi hindi.csv""")
  152. parser.add_argument("--google_translate", nargs=3,
  153. metavar=("LANG-CODE", "INFILE", "OUTFILE"),
  154. help="Auto translate using Google Translate API")
  155. parser.add_argument("--translate", nargs=1, metavar="LANG-CODE",
  156. help="""Rebuild translation for the given langauge and
  157. use Google Translate to tranlate untranslated messages. use "all" """)
  158. # methods
  159. # install
  160. @cmd
  161. def install(db_name, site=None, verbose=True, force=False, root_password=None):
  162. from webnotes.install_lib.install import Installer
  163. inst = Installer('root', db_name=db_name, site=site, root_password=root_password)
  164. inst.install(db_name, verbose=verbose, force=force)
  165. @cmd
  166. def reinstall(site=None, verbose=True):
  167. webnotes.init(site=site)
  168. install(webnotes.conf.db_name, site=site, verbose=verbose, force=True)
  169. @cmd
  170. def restore(db_name, source_sql, site=None, verbose=True, force=False):
  171. install(db_name, source_sql, site=site, verbose=verbose, force=force)
  172. @cmd
  173. def install_fixtures(site=None):
  174. webnotes.init(site=site)
  175. from webnotes.install_lib.install import install_fixtures
  176. install_fixtures()
  177. @cmd
  178. def make_demo(site=None):
  179. import utilities.demo.make_demo
  180. webnotes.init(site=site)
  181. utilities.demo.make_demo.make()
  182. @cmd
  183. def make_demo_fresh(site=None):
  184. import utilities.demo.make_demo
  185. webnotes.init(site=site)
  186. utilities.demo.make_demo.make(reset=True)
  187. # utilities
  188. @cmd
  189. def update(remote=None, branch=None, site=None):
  190. pull(remote=remote, branch=branch, site=site)
  191. # maybe there are new framework changes, any consequences?
  192. reload(webnotes)
  193. latest(site=site)
  194. @cmd
  195. def latest(site=None, verbose=True):
  196. import webnotes.modules.patch_handler
  197. import webnotes.model.sync
  198. webnotes.connect(site=site)
  199. try:
  200. # run patches
  201. webnotes.modules.patch_handler.log_list = []
  202. webnotes.modules.patch_handler.run_all()
  203. if verbose:
  204. print "\n".join(webnotes.modules.patch_handler.log_list)
  205. # sync
  206. webnotes.model.sync.sync_all()
  207. except webnotes.modules.patch_handler.PatchError, e:
  208. print "\n".join(webnotes.modules.patch_handler.log_list)
  209. raise e
  210. @cmd
  211. def sync_all(site=None, force=False):
  212. import webnotes.model.sync
  213. webnotes.connect(site=site)
  214. webnotes.model.sync.sync_all(force=force)
  215. @cmd
  216. def patch(patch_module, site=None, force=False):
  217. import webnotes.modules.patch_handler
  218. webnotes.connect(site=site)
  219. webnotes.modules.patch_handler.log_list = []
  220. webnotes.modules.patch_handler.run_single(patch_module, force=force)
  221. print "\n".join(webnotes.modules.patch_handler.log_list)
  222. @cmd
  223. def update_all_sites(remote=None, branch=None):
  224. pull(remote, branch)
  225. build()
  226. latest(site="all")
  227. @cmd
  228. def reload_doc(module, doctype, docname, site=None, force=False):
  229. webnotes.connect(site=site)
  230. webnotes.reload_doc(module, doctype, docname, force=force)
  231. @cmd
  232. def build():
  233. import webnotes.build
  234. webnotes.build.bundle(False)
  235. @cmd
  236. def watch():
  237. import webnotes.build
  238. webnotes.build.watch(True)
  239. @cmd
  240. def backup(site=None, with_files=False):
  241. from webnotes.utils.backups import scheduled_backup
  242. webnotes.connect(site=site)
  243. scheduled_backup(ignore_files=not with_files)
  244. @cmd
  245. def docs():
  246. from core.doctype.documentation_tool.documentation_tool import write_static
  247. write_static()
  248. @cmd
  249. def domain(host_url=None, site=None):
  250. webnotes.connect(site=site)
  251. if host_url:
  252. webnotes.conn.set_value("Website Settings", None, "subdomain", host_url)
  253. webnotes.conn.commit()
  254. else:
  255. print webnotes.conn.get_value("Website Settings", None, "subdomain")
  256. @cmd
  257. def make_conf(db_name=None, db_password=None, site=None, site_config=None):
  258. from webnotes.install_lib.install import make_conf
  259. make_conf(db_name=db_name, db_password=db_password, site=site, site_config=site_config)
  260. # clear
  261. @cmd
  262. def clear_cache(site=None):
  263. import webnotes.sessions
  264. webnotes.connect(site=site)
  265. webnotes.sessions.clear_cache()
  266. @cmd
  267. def clear_web(site=None):
  268. import webnotes.webutils
  269. webnotes.connect(site=site)
  270. webnotes.webutils.clear_cache()
  271. @cmd
  272. def reset_perms(site=None):
  273. webnotes.connect(site=site)
  274. for d in webnotes.conn.sql_list("""select name from `tabDocType`
  275. where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""):
  276. webnotes.clear_cache(doctype=d)
  277. webnotes.reset_perms(d)
  278. # scheduler
  279. @cmd
  280. def run_scheduler(site=None):
  281. import webnotes.utils.scheduler
  282. webnotes.connect(site=site)
  283. print webnotes.utils.scheduler.execute()
  284. @cmd
  285. def run_scheduler_event(event, site=None):
  286. import webnotes.utils.scheduler
  287. webnotes.connect(site=site)
  288. print webnotes.utils.scheduler.trigger("execute_" + event)
  289. # replace
  290. @cmd
  291. def replace(search_regex, replacement, extn, force=False):
  292. print search_regex, replacement, extn
  293. replace_code('.', search_regex, replacement, extn, force=force)
  294. # import/export
  295. @cmd
  296. def export_doc(doctype, docname, site=None):
  297. import webnotes.modules
  298. webnotes.connect(site=site)
  299. webnotes.modules.export_doc(doctype, docname)
  300. @cmd
  301. def export_doclist(doctype, name, path, site=None):
  302. from core.page.data_import_tool import data_import_tool
  303. webnotes.connect(site=site)
  304. data_import_tool.export_json(doctype, name, path)
  305. @cmd
  306. def export_csv(doctype, path, site=None):
  307. from core.page.data_import_tool import data_import_tool
  308. webnotes.connect(site=site)
  309. data_import_tool.export_csv(doctype, path)
  310. @cmd
  311. def import_doclist(path, site=None):
  312. from core.page.data_import_tool import data_import_tool
  313. webnotes.connect(site=site)
  314. data_import_tool.import_doclist(path)
  315. # translation
  316. @cmd
  317. def build_message_files(site=None):
  318. import webnotes.translate
  319. webnotes.connect(site=site)
  320. webnotes.translate.build_message_files()
  321. @cmd
  322. def export_messages(lang, outfile, site=None):
  323. import webnotes.translate
  324. webnotes.connect(site=site)
  325. webnotes.translate.export_messages(lang, outfile)
  326. @cmd
  327. def import_messages(lang, infile, site=None):
  328. import webnotes.translate
  329. webnotes.connect(site=site)
  330. webnotes.translate.import_messages(lang, infile)
  331. @cmd
  332. def google_translate(lang, infile, outfile, site=None):
  333. import webnotes.translate
  334. webnotes.connect(site=site)
  335. webnotes.translate.google_translate(lang, infile, outfile)
  336. @cmd
  337. def translate(lang, site=None):
  338. import webnotes.translate
  339. webnotes.connect(site=site)
  340. webnotes.translate.translate(lang)
  341. # git
  342. @cmd
  343. def git(param):
  344. if isinstance(param, (list, tuple)):
  345. param = " ".join(param)
  346. import os
  347. os.system("""cd lib && git %s""" % param)
  348. os.system("""cd app && git %s""" % param)
  349. def get_remote_and_branch(remote=None, branch=None):
  350. if not (remote and branch):
  351. webnotes.init()
  352. if not webnotes.conf.branch:
  353. raise Exception("Please specify remote and branch")
  354. remote = remote or "origin"
  355. branch = branch or webnotes.conf.branch
  356. return remote, branch
  357. @cmd
  358. def pull(remote=None, branch=None):
  359. remote, branch = get_remote_and_branch(remote, branch)
  360. git(("pull", remote, branch))
  361. @cmd
  362. def push(remote=None, branch=None):
  363. remote, branch = get_remote_and_branch(remote, branch)
  364. git(("push", remote, branch))
  365. @cmd
  366. def status():
  367. git("status")
  368. @cmd
  369. def commit(message):
  370. git("""commit -a -m "%s" """ % message.replace('"', '\"'))
  371. @cmd
  372. def checkout(branch):
  373. git(("checkout", branch))
  374. def replace_code(start, txt1, txt2, extn, search=None, force=False):
  375. """replace all txt1 by txt2 in files with extension (extn)"""
  376. import webnotes.utils
  377. import os, re
  378. esc = webnotes.utils.make_esc('[]')
  379. if not search: search = esc(txt1)
  380. for wt in os.walk(start, followlinks=1):
  381. for fn in wt[2]:
  382. if fn.split('.')[-1]==extn:
  383. fpath = os.path.join(wt[0], fn)
  384. with open(fpath, 'r') as f:
  385. content = f.read()
  386. if re.search(search, content):
  387. res = search_replace_with_prompt(fpath, txt1, txt2, force)
  388. if res == 'skip':
  389. return 'skip'
  390. def search_replace_with_prompt(fpath, txt1, txt2, force=False):
  391. """ Search and replace all txt1 by txt2 in the file with confirmation"""
  392. from termcolor import colored
  393. with open(fpath, 'r') as f:
  394. content = f.readlines()
  395. tmp = []
  396. for c in content:
  397. if c.find(txt1) != -1:
  398. print fpath
  399. print colored(txt1, 'red').join(c[:-1].split(txt1))
  400. a = ''
  401. if force:
  402. c = c.replace(txt1, txt2)
  403. else:
  404. while a.lower() not in ['y', 'n', 'skip']:
  405. a = raw_input('Do you want to Change [y/n/skip]?')
  406. if a.lower() == 'y':
  407. c = c.replace(txt1, txt2)
  408. elif a.lower() == 'skip':
  409. return 'skip'
  410. tmp.append(c)
  411. with open(fpath, 'w') as f:
  412. f.write(''.join(tmp))
  413. print colored('Updated', 'green')
  414. if __name__=="__main__":
  415. main()