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.
 
 
 
 
 
 

237 rivejä
6.7 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals, print_function
  4. from frappe.utils.minify import JavascriptMinify
  5. import subprocess
  6. import warnings
  7. from six import iteritems, text_type
  8. """
  9. Build the `public` folders and setup languages
  10. """
  11. import os, frappe, json, shutil, re
  12. # from cssmin import cssmin
  13. app_paths = None
  14. def setup():
  15. global app_paths
  16. pymodules = []
  17. for app in frappe.get_all_apps(True):
  18. try:
  19. pymodules.append(frappe.get_module(app))
  20. except ImportError: pass
  21. app_paths = [os.path.dirname(pymodule.__file__) for pymodule in pymodules]
  22. def bundle(no_compress, make_copy=False, restore=False, verbose=False):
  23. """concat / minify js files"""
  24. # build js files
  25. setup()
  26. make_asset_dirs(make_copy=make_copy, restore=restore)
  27. # new nodejs build system
  28. command = 'node --use_strict ../apps/frappe/frappe/build.js --build'
  29. if not no_compress:
  30. command += ' --minify'
  31. subprocess.call(command.split(' '))
  32. # build(no_compress, verbose)
  33. def watch(no_compress):
  34. """watch and rebuild if necessary"""
  35. # new nodejs file watcher
  36. command = 'node --use_strict ../apps/frappe/frappe/build.js --watch'
  37. subprocess.call(command.split(' '))
  38. # setup()
  39. # import time
  40. # compile_less()
  41. # build(no_compress=True)
  42. # while True:
  43. # compile_less()
  44. # if files_dirty():
  45. # build(no_compress=True)
  46. # time.sleep(3)
  47. def make_asset_dirs(make_copy=False, restore=False):
  48. # don't even think of making assets_path absolute - rm -rf ahead.
  49. assets_path = os.path.join(frappe.local.sites_path, "assets")
  50. for dir_path in [
  51. os.path.join(assets_path, 'js'),
  52. os.path.join(assets_path, 'css')]:
  53. if not os.path.exists(dir_path):
  54. os.makedirs(dir_path)
  55. # symlink app/public > assets/app
  56. for app_name in frappe.get_all_apps(True):
  57. pymodule = frappe.get_module(app_name)
  58. app_base_path = os.path.abspath(os.path.dirname(pymodule.__file__))
  59. symlinks = []
  60. symlinks.append([os.path.join(app_base_path, 'public'), os.path.join(assets_path, app_name)])
  61. symlinks.append([os.path.join(app_base_path, 'docs'), os.path.join(assets_path, app_name + '_docs')])
  62. for source, target in symlinks:
  63. source = os.path.abspath(source)
  64. if os.path.exists(source):
  65. if restore:
  66. if os.path.exists(target):
  67. if os.path.islink(target):
  68. os.unlink(target)
  69. else:
  70. shutil.rmtree(target)
  71. shutil.copytree(source, target)
  72. elif make_copy:
  73. if os.path.exists(target):
  74. warnings.warn('Target {target} already exists.'.format(target = target))
  75. else:
  76. shutil.copytree(source, target)
  77. else:
  78. if os.path.exists(target):
  79. if os.path.islink(target):
  80. os.unlink(target)
  81. else:
  82. shutil.rmtree(target)
  83. os.symlink(source, target)
  84. else:
  85. warnings.warn('Source {source} does not exists.'.format(source = source))
  86. def build(no_compress=False, verbose=False):
  87. assets_path = os.path.join(frappe.local.sites_path, "assets")
  88. for target, sources in iteritems(get_build_maps()):
  89. pack(os.path.join(assets_path, target), sources, no_compress, verbose)
  90. def get_build_maps():
  91. """get all build.jsons with absolute paths"""
  92. # framework js and css files
  93. build_maps = {}
  94. for app_path in app_paths:
  95. path = os.path.join(app_path, 'public', 'build.json')
  96. if os.path.exists(path):
  97. with open(path) as f:
  98. try:
  99. for target, sources in iteritems(json.loads(f.read())):
  100. # update app path
  101. source_paths = []
  102. for source in sources:
  103. if isinstance(source, list):
  104. s = frappe.get_pymodule_path(source[0], *source[1].split("/"))
  105. else:
  106. s = os.path.join(app_path, source)
  107. source_paths.append(s)
  108. build_maps[target] = source_paths
  109. except ValueError as e:
  110. print(path)
  111. print('JSON syntax error {0}'.format(str(e)))
  112. return build_maps
  113. timestamps = {}
  114. def pack(target, sources, no_compress, verbose):
  115. from six import StringIO
  116. outtype, outtxt = target.split(".")[-1], ''
  117. jsm = JavascriptMinify()
  118. for f in sources:
  119. suffix = None
  120. if ':' in f: f, suffix = f.split(':')
  121. if not os.path.exists(f) or os.path.isdir(f):
  122. print("did not find " + f)
  123. continue
  124. timestamps[f] = os.path.getmtime(f)
  125. try:
  126. with open(f, 'r') as sourcefile:
  127. data = text_type(sourcefile.read(), 'utf-8', errors='ignore')
  128. extn = f.rsplit(".", 1)[1]
  129. if outtype=="js" and extn=="js" and (not no_compress) and suffix!="concat" and (".min." not in f):
  130. tmpin, tmpout = StringIO(data.encode('utf-8')), StringIO()
  131. jsm.minify(tmpin, tmpout)
  132. minified = tmpout.getvalue()
  133. if minified:
  134. outtxt += text_type(minified or '', 'utf-8').strip('\n') + ';'
  135. if verbose:
  136. print("{0}: {1}k".format(f, int(len(minified) / 1024)))
  137. elif outtype=="js" and extn=="html":
  138. # add to frappe.templates
  139. outtxt += html_to_js_template(f, data)
  140. else:
  141. outtxt += ('\n/*\n *\t%s\n */' % f)
  142. outtxt += '\n' + data + '\n'
  143. except Exception:
  144. print("--Error in:" + f + "--")
  145. print(frappe.get_traceback())
  146. if not no_compress and outtype == 'css':
  147. pass
  148. #outtxt = cssmin(outtxt)
  149. with open(target, 'w') as f:
  150. f.write(outtxt.encode("utf-8"))
  151. print("Wrote %s - %sk" % (target, str(int(os.path.getsize(target)/1024))))
  152. def html_to_js_template(path, content):
  153. '''returns HTML template content as Javascript code, adding it to `frappe.templates`'''
  154. return """frappe.templates["{key}"] = '{content}';\n""".format(\
  155. key=path.rsplit("/", 1)[-1][:-5], content=scrub_html_template(content))
  156. def scrub_html_template(content):
  157. '''Returns HTML content with removed whitespace and comments'''
  158. # remove whitespace to a single space
  159. content = re.sub("\s+", " ", content)
  160. # strip comments
  161. content = re.sub("(<!--.*?-->)", "", content)
  162. return content.replace("'", "\'")
  163. def files_dirty():
  164. for target, sources in iteritems(get_build_maps()):
  165. for f in sources:
  166. if ':' in f: f, suffix = f.split(':')
  167. if not os.path.exists(f) or os.path.isdir(f): continue
  168. if os.path.getmtime(f) != timestamps.get(f):
  169. print(f + ' dirty')
  170. return True
  171. else:
  172. return False
  173. def compile_less():
  174. from distutils.spawn import find_executable
  175. if not find_executable("lessc"):
  176. return
  177. for path in app_paths:
  178. less_path = os.path.join(path, "public", "less")
  179. if os.path.exists(less_path):
  180. for fname in os.listdir(less_path):
  181. if fname.endswith(".less") and fname != "variables.less":
  182. fpath = os.path.join(less_path, fname)
  183. mtime = os.path.getmtime(fpath)
  184. if fpath in timestamps and mtime == timestamps[fpath]:
  185. continue
  186. timestamps[fpath] = mtime
  187. print("compiling {0}".format(fpath))
  188. css_path = os.path.join(path, "public", "css", fname.rsplit(".", 1)[0] + ".css")
  189. os.system("lessc {0} > {1}".format(fpath, css_path))