Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

cli.py 24 KiB

11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
11 år sedan
13 år sedan
11 år sedan
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804
  1. #!/usr/bin/env python2.7
  2. # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
  3. # MIT License. See license.txt
  4. from __future__ import unicode_literals
  5. import sys, os
  6. import webnotes
  7. site_arg_optional = ['serve']
  8. def main():
  9. parsed_args = webnotes._dict(vars(setup_parser()))
  10. fn = get_function(parsed_args)
  11. if not parsed_args.get("sites_path"):
  12. parsed_args["sites_path"] = "."
  13. if not parsed_args.get("make_app"):
  14. if parsed_args.get("site")=="all":
  15. for site in get_sites(parsed_args["sites_path"]):
  16. args = parsed_args.copy()
  17. args["site"] = site
  18. webnotes.init(site)
  19. run(fn, args)
  20. else:
  21. if not fn in site_arg_optional:
  22. if not parsed_args.get("site") and os.path.exists("currentsite.txt"):
  23. with open("currentsite.txt", "r") as sitefile:
  24. site = sitefile.read()
  25. else:
  26. site = parsed_args.get("site")
  27. if not site:
  28. print "Site argument required"
  29. exit(1)
  30. if fn != 'install' and not os.path.exists(site):
  31. print "Did not find folder '{}'. Are you in sites folder?".format(parsed_args.get("site"))
  32. exit(1)
  33. webnotes.init(site)
  34. run(fn, parsed_args)
  35. else:
  36. run(fn, parsed_args)
  37. def cmd(fn):
  38. def new_fn(*args, **kwargs):
  39. import inspect
  40. fnargs, varargs, varkw, defaults = inspect.getargspec(fn)
  41. new_kwargs = {}
  42. for i, a in enumerate(fnargs):
  43. # should not pass an argument more than once
  44. if i >= len(args) and a in kwargs:
  45. new_kwargs[a] = kwargs.get(a)
  46. return fn(*args, **new_kwargs)
  47. return new_fn
  48. def run(fn, args):
  49. if isinstance(args.get(fn), (list, tuple)):
  50. out = globals().get(fn)(*args.get(fn), **args)
  51. else:
  52. out = globals().get(fn)(**args)
  53. return out
  54. def get_function(args):
  55. for fn, val in args.items():
  56. if (val or isinstance(val, list)) and globals().get(fn):
  57. return fn
  58. def get_sites(sites_path=None):
  59. import os
  60. if not sites_path:
  61. sites_path = '.'
  62. return [site for site in os.listdir(sites_path)
  63. if not os.path.islink(os.path.join(sites_path, site))
  64. and os.path.isdir(os.path.join(sites_path, site))
  65. and not site in ('assets',)]
  66. def setup_parser():
  67. import argparse
  68. parser = argparse.ArgumentParser(description="Run webnotes utility functions")
  69. setup_install(parser)
  70. setup_utilities(parser)
  71. setup_translation(parser)
  72. setup_test(parser)
  73. parser.add_argument("site", nargs="?")
  74. # common
  75. parser.add_argument("-f", "--force", default=False, action="store_true",
  76. help="Force execution where applicable (look for [-f] in help)")
  77. parser.add_argument("-v", "--verbose", default=False, action="store_true", dest="verbose",
  78. help="Show verbose output where applicable")
  79. return parser.parse_args()
  80. def setup_install(parser):
  81. parser.add_argument("--make_app", default=False, action="store_true",
  82. help="Make a new application with boilerplate")
  83. parser.add_argument("--install", metavar="DB-NAME", nargs=1,
  84. help="Install a new db")
  85. parser.add_argument("--install_app", metavar="APP-NAME", nargs=1,
  86. help="Install a new app")
  87. parser.add_argument("--root-password", nargs=1,
  88. help="Root password for new app")
  89. parser.add_argument("--reinstall", default=False, action="store_true",
  90. help="Install a fresh app in db_name specified in conf.py")
  91. parser.add_argument("--restore", metavar=("DB-NAME", "SQL-FILE"), nargs=2,
  92. help="Restore from an sql file")
  93. parser.add_argument("--add_system_manager", nargs="+",
  94. metavar=("EMAIL", "[FIRST-NAME] [LAST-NAME]"), help="Add a user with all roles")
  95. def setup_test(parser):
  96. parser.add_argument("--run_tests", default=False, action="store_true",
  97. help="Run tests options [-d doctype], [-m module]")
  98. parser.add_argument("--app", metavar="APP-NAME", nargs=1,
  99. help="Run command for specified app")
  100. parser.add_argument("-d", "--doctype", metavar="DOCTYPE", nargs=1,
  101. help="Run command for specified doctype")
  102. parser.add_argument("-m", "--module", metavar="MODULE", nargs=1,
  103. help="Run command for specified module")
  104. def setup_utilities(parser):
  105. # update
  106. parser.add_argument("-u", "--update", nargs="*", metavar=("REMOTE", "BRANCH"),
  107. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  108. parser.add_argument("--reload_gunicorn", default=False, action="store_true", help="reload gunicorn on update")
  109. parser.add_argument("--patch", nargs=1, metavar="PATCH-MODULE",
  110. help="Run a particular patch [-f]")
  111. parser.add_argument("-l", "--latest", default=False, action="store_true",
  112. help="Run patches, sync schema and rebuild files/translations")
  113. parser.add_argument("--sync_all", default=False, action="store_true",
  114. help="Reload all doctypes, pages, etc. using txt files [-f]")
  115. parser.add_argument("--update_all_sites", nargs="*", metavar=("REMOTE", "BRANCH"),
  116. help="Perform git pull, run patches, sync schema and rebuild files/translations")
  117. parser.add_argument("--reload_doc", nargs=3,
  118. metavar=('"MODULE"', '"DOCTYPE"', '"DOCNAME"'))
  119. # build
  120. parser.add_argument("-b", "--build", default=False, action="store_true",
  121. help="Minify + concatenate JS and CSS files, build translations")
  122. parser.add_argument("-w", "--watch", default=False, action="store_true",
  123. help="Watch and concatenate JS and CSS files as and when they change")
  124. # misc
  125. parser.add_argument("--backup", default=False, action="store_true",
  126. help="Take backup of database in backup folder [--with_files]")
  127. parser.add_argument("--move", default=False, action="store_true",
  128. help="Move site to different directory defined by --dest_dir")
  129. parser.add_argument("--dest_dir", nargs=1, metavar="DEST-DIR",
  130. help="Move site to different directory")
  131. parser.add_argument("--with_files", default=False, action="store_true",
  132. help="Also take backup of files")
  133. parser.add_argument("--domain", nargs="*",
  134. help="Get or set domain in Website Settings")
  135. parser.add_argument("--make_conf", nargs="*", metavar=("DB-NAME", "DB-PASSWORD"),
  136. help="Create new conf.py file")
  137. parser.add_argument("--make_custom_server_script", nargs=1, metavar="DOCTYPE",
  138. help="Create new conf.py file")
  139. parser.add_argument("--set_admin_password", metavar='ADMIN-PASSWORD', nargs=1,
  140. help="Set administrator password")
  141. parser.add_argument("--request", metavar='URL-ARGS', nargs=1, help="Run request as admin")
  142. parser.add_argument("--mysql", action="store_true", help="get mysql shell for a site")
  143. parser.add_argument("--serve", action="store_true", help="Run development server")
  144. parser.add_argument("--profile", action="store_true", help="enable profiling in development server")
  145. parser.add_argument("--smtp", action="store_true", help="Run smtp debug server",
  146. dest="smtp_debug_server")
  147. parser.add_argument("--python", action="store_true", help="get python shell for a site")
  148. parser.add_argument("--flush_memcache", action="store_true", help="flush memcached")
  149. parser.add_argument("--ipython", action="store_true", help="get ipython shell for a site")
  150. parser.add_argument("--get_site_status", action="store_true", help="Get site details")
  151. parser.add_argument("--update_site_config", nargs=1,
  152. metavar="site-CONFIG-JSON",
  153. help="Update site_config.json for a given site")
  154. parser.add_argument("--port", default=8000, type=int, help="port for development server")
  155. parser.add_argument("--use", action="store_true", help="Set current site for development.")
  156. # clear
  157. parser.add_argument("--clear_web", default=False, action="store_true",
  158. help="Clear website cache")
  159. parser.add_argument("--build_sitemap", default=False, action="store_true",
  160. help="Build Website Sitemap")
  161. parser.add_argument("--clear_cache", default=False, action="store_true",
  162. help="Clear cache, doctype cache and defaults")
  163. parser.add_argument("--reset_perms", default=False, action="store_true",
  164. help="Reset permissions for all doctypes")
  165. # scheduler
  166. parser.add_argument("--run_scheduler", default=False, action="store_true",
  167. help="Trigger scheduler")
  168. parser.add_argument("--run_scheduler_event", nargs=1,
  169. metavar="all | daily | weekly | monthly",
  170. help="Run a scheduler event")
  171. # replace
  172. parser.add_argument("--replace", nargs=3,
  173. metavar=("SEARCH-REGEX", "REPLACE-BY", "FILE-EXTN"),
  174. help="Multi-file search-replace [-f]")
  175. # import/export
  176. parser.add_argument("--export_doc", nargs=2, metavar=('"DOCTYPE"', '"DOCNAME"'))
  177. parser.add_argument("--export_doclist", nargs=3, metavar=("DOCTYPE", "NAME", "PATH"),
  178. help="""Export doclist as json to the given path, use '-' as name for Singles.""")
  179. parser.add_argument("--export_csv", nargs=2, metavar=("DOCTYPE", "PATH"),
  180. help="""Dump DocType as csv""")
  181. parser.add_argument("--import_doclist", nargs=1, metavar="PATH",
  182. help="""Import (insert/update) doclist. If the argument is a directory, all files ending with .json are imported""")
  183. def setup_translation(parser):
  184. parser.add_argument("--build_message_files", default=False, action="store_true",
  185. help="Build message files for translation.")
  186. parser.add_argument("--get_untranslated", nargs=2, metavar=("LANG-CODE", "TARGET-FILE-PATH"),
  187. help="""Get untranslated strings for lang.""")
  188. parser.add_argument("--update_translations", nargs=3,
  189. metavar=("LANG-CODE", "UNTRANSLATED-FILE-PATH", "TRANSLATED-FILE-PATH"),
  190. help="""Update translated strings.""")
  191. # methods
  192. @cmd
  193. def make_app():
  194. from webnotes.utils.boilerplate import make_boilerplate
  195. make_boilerplate()
  196. @cmd
  197. def use():
  198. with open("currentsite.txt", "w") as sitefile:
  199. sitefile.write(webnotes.local.site)
  200. # install
  201. @cmd
  202. def install(db_name, root_login="root", root_password=None, source_sql=None,
  203. admin_password = 'admin', verbose=True, force=False, site_config=None, reinstall=False):
  204. from webnotes.installer import install_db, install_app, make_site_dirs
  205. install_db(root_login=root_login, root_password=root_password, db_name=db_name, source_sql=source_sql,
  206. admin_password = admin_password, verbose=verbose, force=force, site_config=site_config, reinstall=reinstall)
  207. make_site_dirs()
  208. install_app("webnotes", verbose=verbose)
  209. webnotes.destroy()
  210. @cmd
  211. def install_app(app_name, verbose=False):
  212. from webnotes.installer import install_app
  213. webnotes.connect()
  214. install_app(app_name, verbose=verbose)
  215. webnotes.destroy()
  216. @cmd
  217. def reinstall(verbose=True):
  218. install(db_name=webnotes.conf.db_name, verbose=verbose, force=True, reinstall=True)
  219. @cmd
  220. def restore(db_name, source_sql, verbose=True, force=False):
  221. install(db_name, source_sql=source_sql, verbose=verbose, force=force)
  222. @cmd
  223. def install_fixtures():
  224. from webnotes.install_lib.install import install_fixtures
  225. install_fixtures()
  226. webnotes.destroy()
  227. @cmd
  228. def add_system_manager(email, first_name=None, last_name=None):
  229. webnotes.connect()
  230. webnotes.profile.add_system_manager(email, first_name, last_name)
  231. webnotes.conn.commit()
  232. webnotes.destroy()
  233. # utilities
  234. @cmd
  235. def update(remote=None, branch=None, reload_gunicorn=False):
  236. pull(remote=remote, branch=branch)
  237. # maybe there are new framework changes, any consequences?
  238. reload(webnotes)
  239. build()
  240. latest()
  241. if reload_gunicorn:
  242. import subprocess
  243. subprocess.check_output("killall -HUP gunicorn".split())
  244. @cmd
  245. def latest(verbose=True):
  246. import webnotes.modules.patch_handler
  247. import webnotes.model.sync
  248. from webnotes.website import rebuild_config
  249. webnotes.connect()
  250. try:
  251. # run patches
  252. webnotes.local.patch_log_list = []
  253. webnotes.modules.patch_handler.run_all()
  254. if verbose:
  255. print "\n".join(webnotes.local.patch_log_list)
  256. # sync
  257. webnotes.model.sync.sync_all()
  258. # build website config if any changes in templates etc.
  259. rebuild_config()
  260. except webnotes.modules.patch_handler.PatchError, e:
  261. print "\n".join(webnotes.local.patch_log_list)
  262. raise
  263. finally:
  264. webnotes.destroy()
  265. @cmd
  266. def sync_all(force=False):
  267. import webnotes.model.sync
  268. webnotes.connect()
  269. webnotes.model.sync.sync_all(force=force)
  270. webnotes.destroy()
  271. @cmd
  272. def patch(patch_module, force=False):
  273. import webnotes.modules.patch_handler
  274. webnotes.connect()
  275. webnotes.local.patch_log_list = []
  276. webnotes.modules.patch_handler.run_single(patch_module, force=force)
  277. print "\n".join(webnotes.local.patch_log_list)
  278. webnotes.destroy()
  279. @cmd
  280. def update_all_sites(remote=None, branch=None, verbose=True):
  281. pull(remote, branch)
  282. # maybe there are new framework changes, any consequences?
  283. reload(webnotes)
  284. build()
  285. for site in get_sites():
  286. webnotes.init(site)
  287. latest(verbose=verbose)
  288. @cmd
  289. def reload_doc(module, doctype, docname, force=False):
  290. webnotes.connect()
  291. webnotes.reload_doc(module, doctype, docname, force=force)
  292. webnotes.conn.commit()
  293. webnotes.destroy()
  294. @cmd
  295. def build():
  296. import webnotes.build
  297. import webnotes
  298. webnotes.build.bundle(False)
  299. @cmd
  300. def watch():
  301. import webnotes.build
  302. webnotes.build.watch(True)
  303. @cmd
  304. def backup(with_files=False, verbose=True, backup_path_db=None, backup_path_files=None):
  305. from webnotes.utils.backups import scheduled_backup
  306. webnotes.connect()
  307. odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files)
  308. if verbose:
  309. from webnotes.utils import now
  310. print "database backup taken -", odb.backup_path_db, "- on", now()
  311. if with_files:
  312. print "files backup taken -", odb.backup_path_files, "- on", now()
  313. webnotes.destroy()
  314. return odb
  315. @cmd
  316. def move(dest_dir=None):
  317. import os
  318. if not dest_dir:
  319. raise Exception, "--dest_dir is required for --move"
  320. if not os.path.isdir(dest_dir):
  321. raise Exception, "destination is not a directory or does not exist"
  322. old_path = webnotes.utils.get_site()
  323. new_path = os.path.join(dest_dir, site)
  324. # check if site dump of same name already exists
  325. site_dump_exists = True
  326. count = 0
  327. while site_dump_exists:
  328. final_new_path = new_path + (count and str(count) or "")
  329. site_dump_exists = os.path.exists(final_new_path)
  330. count = int(count or 0) + 1
  331. os.rename(old_path, final_new_path)
  332. webnotes.destroy()
  333. return os.path.basename(final_new_path)
  334. @cmd
  335. def domain(host_url=None):
  336. webnotes.connect()
  337. if host_url:
  338. webnotes.conn.set_value("Website Settings", None, "subdomain", host_url)
  339. webnotes.conn.commit()
  340. else:
  341. print webnotes.conn.get_value("Website Settings", None, "subdomain")
  342. webnotes.destroy()
  343. @cmd
  344. def make_conf(db_name=None, db_password=None, site_config=None):
  345. from webnotes.install_lib.install import make_conf
  346. make_conf(db_name=db_name, db_password=db_password, site_config=site_config)
  347. @cmd
  348. def make_custom_server_script(doctype):
  349. from webnotes.core.doctype.custom_script.custom_script import make_custom_server_script_file
  350. webnotes.connect()
  351. make_custom_server_script_file(doctype)
  352. webnotes.destroy()
  353. # clear
  354. @cmd
  355. def clear_cache():
  356. import webnotes.sessions
  357. webnotes.connect()
  358. webnotes.clear_cache()
  359. webnotes.destroy()
  360. @cmd
  361. def clear_web():
  362. import webnotes.webutils
  363. webnotes.connect()
  364. webnotes.webutils.clear_cache()
  365. webnotes.destroy()
  366. @cmd
  367. def build_sitemap():
  368. from webnotes.website import rebuild_config
  369. webnotes.connect()
  370. rebuild_config()
  371. webnotes.destroy()
  372. @cmd
  373. def reset_perms():
  374. webnotes.connect()
  375. for d in webnotes.conn.sql_list("""select name from `tabDocType`
  376. where ifnull(istable, 0)=0 and ifnull(custom, 0)=0"""):
  377. webnotes.clear_cache(doctype=d)
  378. webnotes.reset_perms(d)
  379. webnotes.destroy()
  380. # scheduler
  381. @cmd
  382. def run_scheduler():
  383. from webnotes.utils.file_lock import create_lock, delete_lock
  384. import webnotes.utils.scheduler
  385. if create_lock('scheduler'):
  386. webnotes.connect()
  387. print webnotes.utils.scheduler.execute()
  388. delete_lock('scheduler')
  389. webnotes.destroy()
  390. @cmd
  391. def run_scheduler_event(event):
  392. import webnotes.utils.scheduler
  393. webnotes.connect()
  394. print webnotes.utils.scheduler.trigger(event)
  395. webnotes.destroy()
  396. # replace
  397. @cmd
  398. def replace(search_regex, replacement, extn, force=False):
  399. print search_regex, replacement, extn
  400. replace_code('.', search_regex, replacement, extn, force=force)
  401. # import/export
  402. @cmd
  403. def export_doc(doctype, docname):
  404. import webnotes.modules
  405. webnotes.connect()
  406. webnotes.modules.export_doc(doctype, docname)
  407. webnotes.destroy()
  408. @cmd
  409. def export_doclist(doctype, name, path):
  410. from webnotes.core.page.data_import_tool import data_import_tool
  411. webnotes.connect()
  412. data_import_tool.export_json(doctype, name, path)
  413. webnotes.destroy()
  414. @cmd
  415. def export_csv(doctype, path):
  416. from webnotes.core.page.data_import_tool import data_import_tool
  417. webnotes.connect()
  418. data_import_tool.export_csv(doctype, path)
  419. webnotes.destroy()
  420. @cmd
  421. def import_doclist(path, force=False):
  422. from webnotes.core.page.data_import_tool import data_import_tool
  423. webnotes.connect()
  424. data_import_tool.import_doclist(path, overwrite=force)
  425. webnotes.destroy()
  426. # translation
  427. @cmd
  428. def build_message_files():
  429. import webnotes.translate
  430. webnotes.connect()
  431. webnotes.translate.rebuild_all_translation_files()
  432. webnotes.destroy()
  433. @cmd
  434. def get_untranslated(lang, untranslated_file):
  435. import webnotes.translate
  436. webnotes.connect()
  437. webnotes.translate.get_untranslated(lang, untranslated_file)
  438. webnotes.destroy()
  439. @cmd
  440. def update_translations(lang, untranslated_file, translated_file):
  441. import webnotes.translate
  442. webnotes.connect()
  443. webnotes.translate.update_translations(lang, untranslated_file, translated_file)
  444. webnotes.destroy()
  445. # git
  446. @cmd
  447. def git(param):
  448. if isinstance(param, (list, tuple)):
  449. param = " ".join(param)
  450. import os
  451. os.system("""cd lib && git %s""" % param)
  452. os.system("""cd app && git %s""" % param)
  453. def get_remote_and_branch(remote=None, branch=None):
  454. if not (remote and branch):
  455. if not webnotes.conf.branch:
  456. raise Exception("Please specify remote and branch")
  457. remote = remote or "origin"
  458. branch = branch or webnotes.conf.branch
  459. webnotes.destroy()
  460. return remote, branch
  461. @cmd
  462. def pull(remote=None, branch=None):
  463. remote, branch = get_remote_and_branch(remote, branch)
  464. git(("pull", remote, branch))
  465. @cmd
  466. def push(remote=None, branch=None):
  467. remote, branch = get_remote_and_branch(remote, branch)
  468. git(("push", remote, branch))
  469. @cmd
  470. def status():
  471. git("status")
  472. @cmd
  473. def commit(message):
  474. git("""commit -a -m "%s" """ % message.replace('"', '\"'))
  475. @cmd
  476. def checkout(branch):
  477. git(("checkout", branch))
  478. @cmd
  479. def set_admin_password(admin_password):
  480. import webnotes
  481. webnotes.connect()
  482. webnotes.conn.sql("""update __Auth set `password`=password(%s)
  483. where user='Administrator'""", (admin_password,))
  484. webnotes.conn.commit()
  485. webnotes.destroy()
  486. @cmd
  487. def mysql():
  488. import webnotes
  489. import commands, os
  490. msq = commands.getoutput('which mysql')
  491. os.execv(msq, [msq, '-u', webnotes.conf.db_name, '-p'+webnotes.conf.db_password, webnotes.conf.db_name, '-h', webnotes.conf.db_host or "localhost", "-A"])
  492. webnotes.destroy()
  493. @cmd
  494. def python(site):
  495. import webnotes
  496. import commands, os
  497. python = commands.getoutput('which python')
  498. if site:
  499. os.environ["site"] = site
  500. os.environ["PYTHONSTARTUP"] = os.path.join(os.path.dirname(webnotes.__file__), "pythonrc.py")
  501. os.execv(python, [python])
  502. webnotes.destroy()
  503. @cmd
  504. def ipython():
  505. import webnotes
  506. webnotes.connect()
  507. import IPython
  508. IPython.embed()
  509. @cmd
  510. def smtp_debug_server():
  511. import commands, os
  512. python = commands.getoutput('which python')
  513. os.execv(python, [python, '-m', "smtpd", "-n", "-c", "DebuggingServer", "localhost:25"])
  514. @cmd
  515. def run_tests(app=None, module=None, doctype=None, verbose=False):
  516. import webnotes.test_runner
  517. ret = webnotes.test_runner.main(app and app[0], module and module[0], doctype and doctype[0], verbose)
  518. if len(ret.failures) > 0 or len(ret.errors) > 0:
  519. exit(1)
  520. @cmd
  521. def serve(port=8000, profile=False, site=None):
  522. import webnotes.app
  523. webnotes.app.serve(port=port, profile=profile, site=site)
  524. @cmd
  525. def request(args):
  526. import webnotes.handler
  527. webnotes.connect()
  528. webnotes.form_dict = webnotes._dict([a.split("=") for a in args.split("&")])
  529. webnotes.handler.execute_cmd(webnotes.form_dict.cmd)
  530. print webnotes.response
  531. webnotes.destroy()
  532. @cmd
  533. def flush_memcache():
  534. webnotes.cache().flush_all()
  535. def replace_code(start, txt1, txt2, extn, search=None, force=False):
  536. """replace all txt1 by txt2 in files with extension (extn)"""
  537. import webnotes.utils
  538. import os, re
  539. esc = webnotes.utils.make_esc('[]')
  540. if not search: search = esc(txt1)
  541. for wt in os.walk(start, followlinks=1):
  542. for fn in wt[2]:
  543. if fn.split('.')[-1]==extn:
  544. fpath = os.path.join(wt[0], fn)
  545. with open(fpath, 'r') as f:
  546. content = f.read()
  547. if re.search(search, content):
  548. res = search_replace_with_prompt(fpath, txt1, txt2, force)
  549. if res == 'skip':
  550. return 'skip'
  551. def search_replace_with_prompt(fpath, txt1, txt2, force=False):
  552. """ Search and replace all txt1 by txt2 in the file with confirmation"""
  553. from termcolor import colored
  554. with open(fpath, 'r') as f:
  555. content = f.readlines()
  556. tmp = []
  557. for c in content:
  558. if c.find(txt1) != -1:
  559. print fpath
  560. print colored(txt1, 'red').join(c[:-1].split(txt1))
  561. a = ''
  562. if force:
  563. c = c.replace(txt1, txt2)
  564. else:
  565. while a.lower() not in ['y', 'n', 'skip']:
  566. a = raw_input('Do you want to Change [y/n/skip]?')
  567. if a.lower() == 'y':
  568. c = c.replace(txt1, txt2)
  569. elif a.lower() == 'skip':
  570. return 'skip'
  571. tmp.append(c)
  572. with open(fpath, 'w') as f:
  573. f.write(''.join(tmp))
  574. print colored('Updated', 'green')
  575. @cmd
  576. def get_site_status(verbose=False):
  577. import webnotes
  578. import webnotes.utils
  579. from webnotes.profile import get_system_managers
  580. from webnotes.core.doctype.profile.profile import get_total_users, get_active_users, \
  581. get_website_users, get_active_website_users
  582. import json
  583. webnotes.connect()
  584. ret = {
  585. 'last_backup_on': webnotes.local.conf.last_backup_on,
  586. 'active_users': get_active_users(),
  587. 'total_users': get_total_users(),
  588. 'active_website_users': get_active_website_users(),
  589. 'website_users': get_website_users(),
  590. 'system_managers': "\n".join(get_system_managers()),
  591. 'default_company': webnotes.conn.get_default("company"),
  592. 'disk_usage': webnotes.utils.get_disk_usage(),
  593. 'working_directory': webnotes.local.site_path
  594. }
  595. # country, timezone, industry
  596. control_panel_details = webnotes.conn.get_value("Control Panel", "Control Panel",
  597. ["country", "time_zone", "industry"], as_dict=True)
  598. if control_panel_details:
  599. ret.update(control_panel_details)
  600. # basic usage/progress analytics
  601. for doctype in ("Company", "Customer", "Item", "Quotation", "Sales Invoice",
  602. "Journal Voucher", "Stock Ledger Entry"):
  603. key = doctype.lower().replace(" ", "_") + "_exists"
  604. ret[key] = 1 if webnotes.conn.count(doctype) else 0
  605. webnotes.destroy()
  606. if verbose:
  607. print json.dumps(ret, indent=1, sort_keys=True)
  608. return ret
  609. @cmd
  610. def update_site_config(site_config, verbose=False):
  611. import json
  612. if isinstance(site_config, basestring):
  613. site_config = json.loads(site_config)
  614. config = webnotes.get_site_config()
  615. config.update(site_config)
  616. site_config_path = os.path.join(webnotes.local.site_path, "site_config.json")
  617. with open(site_config_path, "w") as f:
  618. json.dump(config, f, indent=1, sort_keys=True)
  619. webnotes.destroy()
  620. @cmd
  621. def bump(repo, bump_type):
  622. import json
  623. assert repo in ['lib', 'app']
  624. assert bump_type in ['minor', 'major', 'patch']
  625. def validate(repo_path):
  626. import git
  627. repo = git.Repo(repo_path)
  628. if repo.active_branch != 'master':
  629. raise Exception, "Current branch not master in {}".format(repo_path)
  630. def bump_version(version, version_type):
  631. import semantic_version
  632. v = semantic_version.Version(version)
  633. if version_type == 'minor':
  634. v.minor += 1
  635. elif version_type == 'major':
  636. v.major += 1
  637. elif version_type == 'patch':
  638. v.patch += 1
  639. return unicode(v)
  640. def add_tag(repo_path, version):
  641. import git
  642. repo = git.Repo(repo_path)
  643. repo.index.add(['config.json'])
  644. repo.index.commit('bumped to version {}'.format(version))
  645. repo.create_tag('v' + version, repo.head)
  646. def update_framework_requirement(version):
  647. with open('app/config.json') as f:
  648. config = json.load(f)
  649. config['requires_framework_version'] = '==' + version
  650. with open('app/config.json', 'w') as f:
  651. json.dump(config, f, indent=1, sort_keys=True)
  652. validate('lib/')
  653. validate('app/')
  654. if repo == 'app':
  655. with open('app/config.json') as f:
  656. config = json.load(f)
  657. new_version = bump_version(config['app_version'], bump_type)
  658. config['app_version'] = new_version
  659. with open('app/config.json', 'w') as f:
  660. json.dump(config, f, indent=1, sort_keys=True)
  661. add_tag('app/', new_version)
  662. elif repo == 'lib':
  663. with open('lib/config.json') as f:
  664. config = json.load(f)
  665. new_version = bump_version(config['framework_version'], bump_type)
  666. config['framework_version'] = new_version
  667. with open('lib/config.json', 'w') as f:
  668. json.dump(config, f, indent=1, sort_keys=True)
  669. add_tag('lib/', new_version)
  670. update_framework_requirement(new_version)
  671. bump('app', bump_type)
  672. if __name__=="__main__":
  673. main()