PageRenderTime 676ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/bomb/cfile.py

https://github.com/dexbol/bomb
Python | 284 lines | 276 code | 6 blank | 2 comment | 0 complexity | 92b818c24ce2dd9e2b7dc8c8ffdc3b5e MD5 | raw file
  1. # coding=utf-8
  2. import re
  3. import logging
  4. import os
  5. import tempfile
  6. from config import Config
  7. from utils import normalize_path, filename
  8. from compiler import compile_csjs
  9. logger = logging.getLogger('bomb')
  10. class CFile(object):
  11. '''config file object'''
  12. STALE_AGE = 8
  13. rdead = re.compile(r'^\s*/\*\s*@?dead\s*\*/\s*$')
  14. rmap = re.compile(r'''^\s*/\*\s*
  15. @?map\s*=\s*(.+?)
  16. \s*\*/ ''', re.VERBOSE)
  17. rbootstrap = re.compile(r'^\s*/\*\s*@?bootstrap\s*\*/\s*$', re.VERBOSE)
  18. rplaceholder = re.compile(r'^\s*/\*\s*@?placeholder\s*\*/\s*$')
  19. rversion = re.compile(r'''^\s*/\*\s*
  20. @?version\s*=\s*(\d+)
  21. \s*\*/\s*$''', re.VERBOSE)
  22. rimport = re.compile(r'''^\s*\$?import\((.+)\)|
  23. ^\s*@import\s+url\((.+)\)
  24. ''', re.VERBOSE)
  25. rdepend = re.compile(r'^\s*\$?depend\((.+)\)')
  26. rfile = re.compile(r'\.js$|\.css$')
  27. def __init__(self, path, url_base=None, url_map=dict()):
  28. self.path = path
  29. self.url_base = url_base if url_base != None\
  30. else (os.path.dirname(path) + os.sep\
  31. if os.path.dirname(path) != '' else '')
  32. self.url_map = url_map
  33. self.filename, self.basename, self.extension = filename(path)
  34. self.dead = False
  35. self.frozen = False
  36. self.bootstrap = False
  37. self.placeholder = False
  38. self.stale_age = self.STALE_AGE
  39. self._map = ''
  40. self._version = -1
  41. self._file_depend = []
  42. # scan cfile attribute
  43. with open(path) as lines:
  44. for line in lines:
  45. matchobj = re.search(self.rdead, line)
  46. if matchobj:
  47. self.dead = True
  48. return
  49. matchobj = re.search(self.rbootstrap, line)
  50. if matchobj:
  51. self.bootstrap = True
  52. matchobj = re.search(self.rplaceholder, line)
  53. if matchobj:
  54. self.placeholder = True
  55. matchobj = re.search(self.rmap, line)
  56. if matchobj:
  57. self._map = matchobj.group(1)
  58. matchobj = re.search(self.rversion, line)
  59. if matchobj:
  60. self._version = int(matchobj.group(1))
  61. matchobj = re.search(self.rdepend, line)
  62. if matchobj:
  63. dependstr = matchobj.group(1)
  64. self._file_depend.extend(dependstr.split(','))
  65. if self.version < 0:
  66. self.update_version()
  67. @property
  68. def version(self):
  69. return self._version
  70. @version.setter
  71. def version(self, version):
  72. content = []
  73. with open(self.path) as lines:
  74. for line in lines:
  75. matchobj = re.search(self.rversion, line)
  76. if not matchobj:
  77. content.append(line)
  78. self._version = version
  79. content.insert(0, '/* @version=' + str(self._version) + ' */\n')
  80. with open(self.path, 'w') as handler:
  81. handler.write(''.join(content))
  82. @property
  83. def map(self):
  84. return self._map
  85. @map.setter
  86. def map(self, newmap):
  87. new = []
  88. with open(self.path) as lines:
  89. for line in lines:
  90. matchobj = re.search(self.rmap, line)
  91. if not matchobj:
  92. new.append(line)
  93. new.insert(0, '/* @map = ' + newmap + ' */\n')
  94. with open(self.path, 'w') as handler:
  95. handler.write(''.join(new))
  96. self._map = newmap
  97. def update_version(self):
  98. self.version = self.version + 1
  99. def get_version_name(self, version=None):
  100. version = '_' + str(version if version !=None else self.version) + '.'
  101. return self.basename + version + self.extension
  102. def get_stale_name(self, stale_age=None):
  103. return self.get_version_name(self.version - \
  104. (stale_age or self.stale_age))
  105. def get_version_name_re(self):
  106. return re.compile(self.basename + '_' + r'(\d+)' + r'\.' + \
  107. self.extension)
  108. def get_placeholder_re(self):
  109. name = self.basename + '\\.' + self.extension
  110. return re.compile(r'''(\/\*\s*_PLACEHOLDER_%s\s+START\s*\*\/)
  111. ([\s\S]*?)
  112. (\/\*\s*_PLACEHOLDER_%s\s+END\s*\*\/)
  113. ''' % (name, name), re.VERBOSE)
  114. def parse_content(self):
  115. if self.dead:
  116. return
  117. with open(self.path) as lines:
  118. for line in lines:
  119. importmatch = re.search(self.rimport, line)
  120. if importmatch:
  121. path = importmatch.group(1) or importmatch.group(2)
  122. path = re.sub('["\']', '', path)
  123. path = self.parse_import_path(path)
  124. for file_generator in self.import_file(path):
  125. for l in file_generator:
  126. yield l
  127. elif re.search(self.rdepend, line):
  128. continue
  129. else:
  130. yield line
  131. def parse_import_path(self, path):
  132. url_base = self.url_base
  133. url_map = self.url_map
  134. path = normalize_path(path)
  135. if path.find('!') < 0 :
  136. return url_base + path
  137. path = path.split('!')
  138. prefix = path[0]
  139. if prefix in url_map:
  140. return normalize_path(url_base + url_map[prefix] + path[1])
  141. else:
  142. raise Exception('prefix not found ! (' + prefix + ')')
  143. def import_file(self, path):
  144. def _import(path):
  145. yield '\n'
  146. with open(path) as lines:
  147. for line in lines:
  148. yield line.decode('utf-8')
  149. if os.path.isdir(path):
  150. for (dirpath, dirnames, filenames) in os.walk(path):
  151. if '.svn' in dirnames:
  152. dirnames.remove('.svn')
  153. for fname in filenames:
  154. if fname.endswith('.js') or fname.endswith('.css'):
  155. yield _import(os.path.join(dirpath, fname))
  156. else:
  157. yield _import(path)
  158. def update_map(self):
  159. reg = re.compile(r'''^\s*([A-Z]\w*)
  160. (?:\.add|\[['"]add['"]\])
  161. \((.*?)\s*,\s*function''', re.VERBOSE)
  162. rinternal = re.compile(r'^[\'"]?!')
  163. mods = []
  164. boom = None
  165. file_depend = self._file_depend
  166. dependstr = ''
  167. for line in self.parse_content():
  168. matchobj = re.match(reg, line)
  169. if matchobj:
  170. mods.append(matchobj.group(2))
  171. boom = matchobj.group(1)
  172. if not boom:
  173. return
  174. # strip module name begined with !
  175. mods = [m for m in mods if not re.match(rinternal, m)];
  176. if len(mods) > 0:
  177. modstr = ',\'mods\':[' + ','.join(mods) + ']'
  178. else:
  179. modstr = ''
  180. if len(file_depend) > 0 :
  181. dependstr = ",'requires':[" + ",".join(file_depend) + "]"
  182. version_name = self.get_version_name()
  183. mstr = '''%s['add']('%s',{'path':'%s'%s%s}); ''' % (boom,
  184. self.filename,
  185. version_name,
  186. modstr,
  187. dependstr
  188. );
  189. self.map = mstr
  190. def dump(self, extension=[]):
  191. content = []
  192. self.update_map()
  193. for line in self.parse_content():
  194. content.append(line.encode('utf-8'))
  195. for line in extension:
  196. content.append(line.encode('utf-8'))
  197. return content
  198. def push(self, destination, extension=[]):
  199. content = self.dump(extension=extension)
  200. path = normalize_path(destination, self.get_version_name())
  201. with open(path, 'w') as handler:
  202. handler.write(''.join(content))
  203. return path
  204. def update_referrer(self, referrer):
  205. pattern = self.get_placeholder_re() if self.placeholder else \
  206. self.get_version_name_re()
  207. referrer = normalize_path(referrer)
  208. with open(referrer) as handler:
  209. content = handler.read().decode('utf-8')
  210. if self.placeholder:
  211. logger.info('replace placeholder: ' + self.filename)
  212. spawn = ''.join(self.dump())
  213. handle, abspath = tempfile.mkstemp('.' + self.extension, text=True)
  214. with open(abspath, 'w') as handler:
  215. handler.write(spawn)
  216. spawn = compile_csjs(abspath)
  217. content = re.sub(pattern, '\g<1>' + spawn + '\g<3>', content)
  218. os.close(handle)
  219. os.remove(abspath)
  220. else:
  221. content = re.sub(pattern, self.get_version_name(), content)
  222. with open(referrer, 'w') as handler:
  223. handler.write(content.encode('utf-8'))