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.
 
 
 
 
 
 

331 lines
8.6 KiB

  1. # Copyright (c) 2012 Web Notes Technologies Pvt Ltd (http://erpnext.com)
  2. #
  3. # MIT License (MIT)
  4. #
  5. # Permission is hereby granted, free of charge, to any person obtaining a
  6. # copy of this software and associated documentation files (the "Software"),
  7. # to deal in the Software without restriction, including without limitation
  8. # the rights to use, copy, modify, merge, publish, distribute, sublicense,
  9. # and/or sell copies of the Software, and to permit persons to whom the
  10. # Software is furnished to do so, subject to the following conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be included in
  13. # all copies or substantial portions of the Software.
  14. #
  15. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  16. # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
  17. # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  18. # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
  19. # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
  20. # OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  21. #
  22. """
  23. Utilities for using modules
  24. """
  25. import webnotes
  26. transfer_types = ['Role', 'Print Format','DocType','Page','DocType Mapper','GL Mapper','Search Criteria', 'Patch']
  27. def scrub(txt):
  28. return txt.replace(' ','_').replace('-', '_').replace('/', '_').lower()
  29. def scrub_dt_dn(dt, dn):
  30. """
  31. Returns in lowercase and code friendly names of doctype and name for certain types
  32. """
  33. ndt, ndn = dt, dn
  34. if dt.lower() in ('doctype', 'search criteria', 'page'):
  35. ndt, ndn = scrub(dt), scrub(dn)
  36. return ndt, ndn
  37. def get_item_file(module, dt, dn):
  38. """
  39. Returns the path of the item file
  40. """
  41. import os
  42. ndt, ndn = scrub_dt_dn(dt, dn)
  43. return os.path.join(get_module_path(module), ndt, ndn, ndn + '.txt')
  44. def get_item_timestamp(module, dt, dn):
  45. """
  46. Return ths timestamp of the given item (if exists)
  47. """
  48. from webnotes.utils import get_file_timestamp
  49. return get_file_timestamp(get_item_file(module, dt, dn))
  50. def get_module_path(module):
  51. """
  52. Returns path of the given module (imports it and reads it from __file__)
  53. """
  54. return Module(module).get_path()
  55. def get_doc_path(dt, dn, module=None):
  56. """
  57. Return the path to a particular doc folder
  58. """
  59. import os
  60. if not module:
  61. if dt=='Module Def':
  62. module=dn
  63. else:
  64. module = webnotes.conn.get_value(dt, dn, 'module')
  65. ndt, ndn = scrub_dt_dn(dt, dn)
  66. return os.path.join(get_module_path(module), ndt, ndn)
  67. def reload_doc(module, dt, dn):
  68. """
  69. Sync a file from txt to module
  70. Alias for::
  71. Module(module).reload(dt, dn)
  72. """
  73. Module(module).reload(dt, dn)
  74. def export_doc(doctype, name):
  75. """write out a doc"""
  76. from webnotes.modules.export_module import write_document_file
  77. import webnotes.model.doc
  78. module = webnotes.conn.get_value(doctype, name, 'module')
  79. doclist = [d.fields for d in webnotes.model.doc.get(doctype, name)]
  80. write_document_file(doclist, module)
  81. class ModuleManager:
  82. """
  83. Module manager class, used to run functions on all modules
  84. """
  85. def get_all_modules(self):
  86. """
  87. Return list of all modules
  88. """
  89. import webnotes.defs
  90. from webnotes.modules.utils import listfolders
  91. if hasattr(webnotes.defs, 'modules_path'):
  92. return listfolders(webnotes.defs.modules_path, 1)
  93. class Module:
  94. """
  95. Represents a module in the framework, has classes for syncing files
  96. """
  97. def __init__(self, name):
  98. self.name = name
  99. self.path = None
  100. self.sync_types = ['txt','sql']
  101. self.code_types = ['js','css','py','html','sql']
  102. def get_path(self):
  103. """
  104. Returns path of the module (imports it and reads it from __file__)
  105. """
  106. if not self.path:
  107. import webnotes.defs, os
  108. try:
  109. # by import
  110. exec ('import ' + scrub(self.name)) in locals()
  111. self.path = eval(scrub(self.name) + '.__file__')
  112. self.path = os.path.sep.join(self.path.split(os.path.sep)[:-1])
  113. except ImportError, e:
  114. # force
  115. self.path = os.path.join(webnotes.defs.modules_path, scrub(self.name))
  116. return self.path
  117. def get_doc_file(self, dt, dn, extn='.txt'):
  118. """
  119. Return file of a doc
  120. """
  121. dt, dn = scrub_dt_dn(dt, dn)
  122. return self.get_file(dt, dn, dn + extn)
  123. def get_file(self, *path):
  124. """
  125. Returns ModuleFile object, in path specifiy the package name and file name
  126. For example::
  127. Module('accounts').get_file('doctype','account','account.txt')
  128. """
  129. import os
  130. path = os.path.join(self.get_path(), os.path.join(*path))
  131. if path.endswith('.txt'):
  132. return TxtModuleFile(path)
  133. if path.endswith('.sql'):
  134. return SqlModuleFile(path)
  135. if path.endswith('.js'):
  136. return JsModuleFile(path)
  137. else:
  138. return ModuleFile(path)
  139. def reload(self, dt, dn):
  140. """
  141. Sync the file to the db
  142. """
  143. import os
  144. dt, dn = scrub_dt_dn(dt, dn)
  145. path = os.path.exists(os.path.join(self.get_path(), os.path.join(dt, dn, dn + '.txt')))
  146. if not path:
  147. webnotes.msgprint("%s not found" % path)
  148. else:
  149. self.get_file(dt, dn, dn + '.txt').sync(force=1)
  150. def sync_all_of_type(self, extn, verbose=0):
  151. """
  152. Walk through all the files in the modules and sync all files of
  153. a particular type
  154. """
  155. import os
  156. ret = []
  157. for walk_tuple in os.walk(self.get_path()):
  158. for f in walk_tuple[2]:
  159. if f.split('.')[-1] == extn:
  160. path = os.path.relpath(os.path.join(walk_tuple[0], f), self.get_path())
  161. self.get_file(path).sync()
  162. if verbose:
  163. print 'complete: ' + path
  164. def sync_all(self, verbose=0):
  165. """
  166. Walk through all the files in the modules and sync all files
  167. """
  168. import os
  169. self.sync_all_of_type('txt', verbose)
  170. self.sync_all_of_type('sql', verbose)
  171. class ModuleFile:
  172. """
  173. Module file class.
  174. Module files can be dynamically generated by specifying first line is "#!python"
  175. the output
  176. """
  177. def __init__(self, path):
  178. self.path = path
  179. def load_content(self):
  180. """
  181. returns file contents
  182. """
  183. import os
  184. if os.path.exists(self.path):
  185. f = open(self.path,'r')
  186. self.content = f.read()
  187. f.close()
  188. else:
  189. self.content = ''
  190. return self.content
  191. def read(self, do_execute = None):
  192. """
  193. Return the file content, if dynamic, execute it
  194. """
  195. self.load_content()
  196. if do_execute and self.content.startswith('#!python'):
  197. from webnotes.model.code import execute
  198. self.content = execute(self.content)
  199. return self.content
  200. class TxtModuleFile(ModuleFile):
  201. """
  202. Class for .txt files, sync the doclist in the txt file into the database
  203. """
  204. def __init__(self, path):
  205. ModuleFile.__init__(self, path)
  206. def sync(self, force=1):
  207. """
  208. import the doclist if new
  209. """
  210. from webnotes.model.utils import peval_doclist
  211. doclist = peval_doclist(self.read())
  212. if doclist:
  213. from webnotes.utils.transfer import set_doc
  214. set_doc(doclist, 1, 1, 1)
  215. # since there is a new timestamp on the file, update timestamp in
  216. # the record
  217. webnotes.conn.sql("update `tab%s` set modified=now() where name=%s" \
  218. % (doclist[0]['doctype'], '%s'), doclist[0]['name'])
  219. class SqlModuleFile(ModuleFile):
  220. def __init__(self, path):
  221. ModuleFile.__init__(self, path)
  222. def sync(self):
  223. """
  224. execute the sql if new
  225. The caller must either commit or rollback an open transaction
  226. """
  227. content = self.read()
  228. # execute everything but selects
  229. # theses are ddl statements, should either earlier
  230. # changes must be committed or rollbacked
  231. # by the caller
  232. if content.strip().split()[0].lower() in ('insert','update','delete','create','alter','drop'):
  233. webnotes.conn.sql(self.read())
  234. # start a new transaction, as we have to update
  235. # the timestamp table
  236. webnotes.conn.begin()
  237. class JsModuleFile(ModuleFile):
  238. """
  239. JS File. read method will read file and replace all $import() with relevant code
  240. Example::
  241. $import(accounts/common.js)
  242. """
  243. def __init__(self, path):
  244. ModuleFile.__init__(self, path)
  245. def get_js(self, match):
  246. """
  247. New style will expect file path or doctype
  248. """
  249. name = match.group('name')
  250. custom = ''
  251. import webnotes.defs, os
  252. if os.path.sep in name:
  253. module = name.split(os.path.sep)[0]
  254. path = os.path.join(Module(module).get_path(), os.path.sep.join(name.split(os.path.sep)[1:]))
  255. else:
  256. # its a doctype
  257. path = os.path.join(get_doc_path('DocType', name), scrub(name) + '.js')
  258. # add custom script if present
  259. from webnotes.model.code import get_custom_script
  260. custom = get_custom_script(name, 'Client') or ''
  261. return JsModuleFile(path).read() + '\n' + custom
  262. def read(self):
  263. """
  264. return js content (replace $imports if needed)
  265. """
  266. self.load_content()
  267. code = self.content
  268. if code and code.strip():
  269. import re
  270. p = re.compile('\$import\( (?P<name> [^)]*) \)', re.VERBOSE)
  271. code = p.sub(self.get_js, code)
  272. return code