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.

wnf.py 17 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
13 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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("--move", default=False, action="store_true",
  98. help="Move site to different directory defined by --dest_dir")
  99. parser.add_argument("--dest_dir", nargs=1, metavar="DEST-DIR",
  100. help="Move site to different directory")
  101. parser.add_argument("--with_files", default=False, action="store_true",
  102. help="Also take backup of files")
  103. parser.add_argument("--docs", default=False, action="store_true",
  104. help="Build docs")
  105. parser.add_argument("--domain", nargs="*",
  106. help="Get or set domain in Website Settings")
  107. parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
  108. help="Create new conf.py file")
  109. parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1,
  110. help="Set administrator password")
  111. # clear
  112. parser.add_argument("--clear_web", default=False, action="store_true",
  113. help="Clear website cache")
  114. parser.add_argument("--clear_cache", default=False, action="store_true",
  115. help="Clear cache, doctype cache and defaults")
  116. parser.add_argument("--reset_perms", default=False, action="store_true",
  117. help="Reset permissions for all doctypes")
  118. # scheduler
  119. parser.add_argument("--run_scheduler", default=False, action="store_true",
  120. help="Trigger scheduler")
  121. parser.add_argument("--run_scheduler_event", nargs=1,
  122. metavar="all | daily | weekly | monthly",
  123. help="Run a scheduler event")
  124. # replace
  125. parser.add_argument("--replace", nargs=3,
  126. metavar=("SEARCH-REGEX", "REPLACE-BY", "FILE-EXTN"),
  127. help="Multi-file search-replace [-f]")
  128. # import/export
  129. parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"'))
  130. parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"),
  131. help="""Export doclist as json to the given path, use '-' as name for Singles.""")
  132. parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"),
  133. help="""Dump DocType as csv""")
  134. parser.add_argument("--import_doclist", nargs=1, metavar="PATH",
  135. help="""Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported""")
  136. def setup_git(parser):
  137. parser.add_argument("--pull", nargs="*", metavar=("REMOTE", "BRANCH"),
  138. help="Run git pull for both repositories")
  139. parser.add_argument("-p", "--push", nargs="*", metavar=("REMOTE", "BRANCH"),
  140. help="Run git push for both repositories")
  141. parser.add_argument("--status", default=False, action="store_true",
  142. help="Run git status for both repositories")
  143. parser.add_argument("--commit", nargs=1, metavar="COMMIT-MSG",
  144. help="Run git commit COMMIT-MSG for both repositories")
  145. parser.add_argument("--checkout", nargs=1, metavar="BRANCH",
  146. help="Run git checkout BRANCH for both repositories")
  147. parser.add_argument("--git", nargs="*", metavar="OPTIONS",
  148. help="Run git command for both repositories")
  149. def setup_translation(parser):
  150. parser.add_argument("--build_message_files", default=False, action="store_true",
  151. help="Build message files for translation")
  152. parser.add_argument("--export_messages", nargs=2, metavar=("LANG-CODE", "FILENAME"),
  153. help="""Export all messages for a language to translation in a csv file.
  154. Example, lib/wnf.py --export_messages hi hindi.csv""")
  155. parser.add_argument("--import_messages", nargs=2, metavar=("LANG-CODE", "FILENAME"),
  156. help="""Import messages for a language and make language files.
  157. Example, lib/wnf.py --import_messages hi hindi.csv""")
  158. parser.add_argument("--google_translate", nargs=3,
  159. metavar=("LANG-CODE", "INFILE", "OUTFILE"),
  160. help="Auto translate using Google Translate API")
  161. parser.add_argument("--translate", nargs=1, metavar="LANG-CODE",
  162. help="""Rebuild translation for the given langauge and
  163. use Google Translate to tranlate untranslated messages. use "all" """)
  164. # methods
  165. # install
  166. @cmd
  167. def install(db_name, source_sql=None, site=None, verbose=True, force=False, root_password=None):
  168. from webnotes.install_lib.install import Installer
  169. inst = Installer('root', db_name=db_name, site=site, root_password=root_password)
  170. inst.install(db_name, source_sql=source_sql, verbose=verbose, force=force)
  171. @cmd
  172. def reinstall(site=None, verbose=True):
  173. webnotes.init(site=site)
  174. install(webnotes.conf.db_name, site=site, verbose=verbose, force=True)
  175. @cmd
  176. def restore(db_name, source_sql, site=None, verbose=True, force=False):
  177. install(db_name, source_sql, site=site, verbose=verbose, force=force)
  178. @cmd
  179. def install_fixtures(site=None):
  180. webnotes.init(site=site)
  181. from webnotes.install_lib.install import install_fixtures
  182. install_fixtures()
  183. @cmd
  184. def make_demo(site=None):
  185. import utilities.demo.make_demo
  186. webnotes.init(site=site)
  187. utilities.demo.make_demo.make()
  188. @cmd
  189. def make_demo_fresh(site=None):
  190. import utilities.demo.make_demo
  191. webnotes.init(site=site)
  192. utilities.demo.make_demo.make(reset=True)
  193. # utilities
  194. @cmd
  195. def update(remote=None, branch=None, site=None):
  196. pull(remote=remote, branch=branch, site=site)
  197. # maybe there are new framework changes, any consequences?
  198. reload(webnotes)
  199. latest(site=site)
  200. @cmd
  201. def latest(site=None, verbose=True):
  202. import webnotes.modules.patch_handler
  203. import webnotes.model.sync
  204. webnotes.connect(site=site)
  205. try:
  206. # run patches
  207. webnotes.modules.patch_handler.log_list = []
  208. webnotes.modules.patch_handler.run_all()
  209. if verbose:
  210. print "\n".join(webnotes.modules.patch_handler.log_list)
  211. # sync
  212. webnotes.model.sync.sync_all()
  213. except webnotes.modules.patch_handler.PatchError, e:
  214. print "\n".join(webnotes.modules.patch_handler.log_list)
  215. raise e
  216. @cmd
  217. def sync_all(site=None, force=False):
  218. import webnotes.model.sync
  219. webnotes.connect(site=site)
  220. webnotes.model.sync.sync_all(force=force)
  221. @cmd
  222. def patch(patch_module, site=None, force=False):
  223. import webnotes.modules.patch_handler
  224. webnotes.connect(site=site)
  225. webnotes.modules.patch_handler.log_list = []
  226. webnotes.modules.patch_handler.run_single(patch_module, force=force)
  227. print "\n".join(webnotes.modules.patch_handler.log_list)
  228. @cmd
  229. def update_all_sites(remote=None, branch=None, verbose=True):
  230. pull(remote, branch)
  231. build()
  232. for site in get_sites():
  233. latest(site=site, verbose=verbose)
  234. @cmd
  235. def reload_doc(module, doctype, docname, site=None, force=False):
  236. webnotes.connect(site=site)
  237. webnotes.reload_doc(module, doctype, docname, force=force)
  238. @cmd
  239. def build():
  240. import webnotes.build
  241. webnotes.build.bundle(False)
  242. @cmd
  243. def watch():
  244. import webnotes.build
  245. webnotes.build.watch(True)
  246. @cmd
  247. def backup(site=None, with_files=False):
  248. from webnotes.utils.backups import scheduled_backup
  249. webnotes.connect(site=site)
  250. odb = scheduled_backup(ignore_files=not with_files)
  251. if __name__ == "__main__":
  252. print "backup taken -", odb.backup_path_db, "- on", now()
  253. return odb
  254. @cmd
  255. def move(site=None, dest_dir=None):
  256. import os
  257. if not dest_dir:
  258. raise Exception, "--dest_dir is required for --move"
  259. dest_dir = dest_dir[0]
  260. if not os.path.isdir(dest_dir):
  261. raise Exception, "destination is not a directory or does not exist"
  262. webnotes.init(site=site)
  263. old_path = webnotes.utils.get_site_path()
  264. new_path = os.path.join(dest_dir, site)
  265. # check if site dump of same name already exists
  266. site_dump_exists = True
  267. count = 0
  268. while site_dump_exists:
  269. final_new_path = new_path + (count and str(count) or "")
  270. site_dump_exists = os.path.exists(final_new_path)
  271. count = int(count or 0) + 1
  272. os.rename(old_path, final_new_path)
  273. webnotes.destroy()
  274. @cmd
  275. def docs():
  276. from core.doctype.documentation_tool.documentation_tool import write_static
  277. write_static()
  278. @cmd
  279. def domain(host_url=None, site=None):
  280. webnotes.connect(site=site)
  281. if host_url:
  282. webnotes.conn.set_value("Website Settings", None, "subdomain", host_url)
  283. webnotes.conn.commit()
  284. else:
  285. print webnotes.conn.get_value("Website Settings", None, "subdomain")
  286. @cmd
  287. def make_conf(db_name=None, db_password=None, site=None, site_config=None):
  288. from webnotes.install_lib.install import make_conf
  289. make_conf(db_name=db_name, db_password=db_password, site=site, site_config=site_config)
  290. # clear
  291. @cmd
  292. def clear_cache(site=None):
  293. import webnotes.sessions
  294. webnotes.connect(site=site)
  295. webnotes.sessions.clear_cache()
  296. @cmd
  297. def clear_web(site=None):
  298. import webnotes.webutils
  299. webnotes.connect(site=site)
  300. webnotes.webutils.clear_cache()
  301. @cmd
  302. def reset_perms(site=None):
  303. webnotes.connect(site=site)
  304. for d in webnotes.conn.sql_list("""select name from `tabDocType`
  305. where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""):
  306. webnotes.clear_cache(doctype=d)
  307. webnotes.reset_perms(d)
  308. # scheduler
  309. @cmd
  310. def run_scheduler(site=None):
  311. import webnotes.utils.scheduler
  312. webnotes.connect(site=site)
  313. print webnotes.utils.scheduler.execute()
  314. @cmd
  315. def run_scheduler_event(event, site=None):
  316. import webnotes.utils.scheduler
  317. webnotes.connect(site=site)
  318. print webnotes.utils.scheduler.trigger("execute_" + event)
  319. # replace
  320. @cmd
  321. def replace(search_regex, replacement, extn, force=False):
  322. print search_regex, replacement, extn
  323. replace_code('.', search_regex, replacement, extn, force=force)
  324. # import/export
  325. @cmd
  326. def export_doc(doctype, docname, site=None):
  327. import webnotes.modules
  328. webnotes.connect(site=site)
  329. webnotes.modules.export_doc(doctype, docname)
  330. @cmd
  331. def export_doclist(doctype, name, path, site=None):
  332. from core.page.data_import_tool import data_import_tool
  333. webnotes.connect(site=site)
  334. data_import_tool.export_json(doctype, name, path)
  335. @cmd
  336. def export_csv(doctype, path, site=None):
  337. from core.page.data_import_tool import data_import_tool
  338. webnotes.connect(site=site)
  339. data_import_tool.export_csv(doctype, path)
  340. @cmd
  341. def import_doclist(path, site=None):
  342. from core.page.data_import_tool import data_import_tool
  343. webnotes.connect(site=site)
  344. data_import_tool.import_doclist(path)
  345. # translation
  346. @cmd
  347. def build_message_files(site=None):
  348. import webnotes.translate
  349. webnotes.connect(site=site)
  350. webnotes.translate.build_message_files()
  351. @cmd
  352. def export_messages(lang, outfile, site=None):
  353. import webnotes.translate
  354. webnotes.connect(site=site)
  355. webnotes.translate.export_messages(lang, outfile)
  356. @cmd
  357. def import_messages(lang, infile, site=None):
  358. import webnotes.translate
  359. webnotes.connect(site=site)
  360. webnotes.translate.import_messages(lang, infile)
  361. @cmd
  362. def google_translate(lang, infile, outfile, site=None):
  363. import webnotes.translate
  364. webnotes.connect(site=site)
  365. webnotes.translate.google_translate(lang, infile, outfile)
  366. @cmd
  367. def translate(lang, site=None):
  368. import webnotes.translate
  369. webnotes.connect(site=site)
  370. webnotes.translate.translate(lang)
  371. # git
  372. @cmd
  373. def git(param):
  374. if isinstance(param, (list, tuple)):
  375. param = " ".join(param)
  376. import os
  377. os.system("""cd lib && git %s""" % param)
  378. os.system("""cd app && git %s""" % param)
  379. def get_remote_and_branch(remote=None, branch=None):
  380. if not (remote and branch):
  381. webnotes.init()
  382. if not webnotes.conf.branch:
  383. raise Exception("Please specify remote and branch")
  384. remote = remote or "origin"
  385. branch = branch or webnotes.conf.branch
  386. webnotes.destroy()
  387. return remote, branch
  388. @cmd
  389. def pull(remote=None, branch=None):
  390. remote, branch = get_remote_and_branch(remote, branch)
  391. git(("pull", remote, branch))
  392. @cmd
  393. def push(remote=None, branch=None):
  394. remote, branch = get_remote_and_branch(remote, branch)
  395. git(("push", remote, branch))
  396. @cmd
  397. def status():
  398. git("status")
  399. @cmd
  400. def commit(message):
  401. git("""commit -a -m "%s" """ % message.replace('"', '\"'))
  402. @cmd
  403. def checkout(branch):
  404. git(("checkout", branch))
  405. @cmd
  406. def set_admin_password(admin_password, site=None):
  407. import webnotes
  408. webnotes.connect(site=site)
  409. webnotes.conn.sql("""update __Auth set `password`=password(%s)
  410. where user='Administrator'""", (admin_password,))
  411. webnotes.conn.commit()
  412. webnotes.destroy()
  413. def replace_code(start, txt1, txt2, extn, search=None, force=False):
  414. """replace all txt1 by txt2 in files with extension (extn)"""
  415. import webnotes.utils
  416. import os, re
  417. esc = webnotes.utils.make_esc('[]')
  418. if not search: search = esc(txt1)
  419. for wt in os.walk(start, followlinks=1):
  420. for fn in wt[2]:
  421. if fn.split('.')[-1]==extn:
  422. fpath = os.path.join(wt[0], fn)
  423. with open(fpath, 'r') as f:
  424. content = f.read()
  425. if re.search(search, content):
  426. res = search_replace_with_prompt(fpath, txt1, txt2, force)
  427. if res == 'skip':
  428. return 'skip'
  429. def search_replace_with_prompt(fpath, txt1, txt2, force=False):
  430. """ Search and replace all txt1 by txt2 in the file with confirmation"""
  431. from termcolor import colored
  432. with open(fpath, 'r') as f:
  433. content = f.readlines()
  434. tmp = []
  435. for c in content:
  436. if c.find(txt1) != -1:
  437. print fpath
  438. print colored(txt1, 'red').join(c[:-1].split(txt1))
  439. a = ''
  440. if force:
  441. c = c.replace(txt1, txt2)
  442. else:
  443. while a.lower() not in ['y', 'n', 'skip']:
  444. a = raw_input('Do you want to Change [y/n/skip]?')
  445. if a.lower() == 'y':
  446. c = c.replace(txt1, txt2)
  447. elif a.lower() == 'skip':
  448. return 'skip'
  449. tmp.append(c)
  450. with open(fpath, 'w') as f:
  451. f.write(''.join(tmp))
  452. print colored('Updated', 'green')
  453. if __name__=="__main__":
  454. main()