class StaticPackage(): u''' 静态编译库 ''' def __init__(self, root_path, publish_path = None, workspace = None, listener = None): self.url = None self.combines = {} self.listener = listener self.workspace = workspace self.root = root_path # 框架所在本地目录 self.publish_path = publish_path # 发布文件的目录路径 self.source_path = '' # 源文件的目录路径 self.resource_path = '' # 资源文件目录路径 self.library_path = '' # 库文件目录路径 self.library_folders = {} self.combine_cache = {self.root: self} # 存储在combine过程中生成的package self.resource_dir = None self.serverPrefix = '' self.serverUrl = '' self.serverRoot = '' self.load_config() if not self.workspace: self.load_workspace() if not self.listener: self.init_listener() def load_workspace(self): workspace_path = Workspace.get_workspace(self.root) if workspace_path: self.workspace = Workspace(workspace_path) def init_listener(self): # 不是所有的库都需要有publish_path if self.publish_path: self.listener = FileListener(os.path.join(self.publish_path, INFO_PATH)) def get_package(self, root_path): u''' 从缓存中获取package引用,如果没有则生成新的并加入缓存 ''' package = self.combine_cache.get(root_path) if not package: package = StaticPackage(root_path, self.publish_path, workspace = self.workspace, listener = self.listener) self.combine_cache[root_path] = package package.combine_cache = self.combine_cache return package def get_publish_files(self): u''' 发布目录所有的css/js文件 ''' def get_files(dir_path): paths = [] for root, dirs, files in os.walk(dir_path): if '.svn' in root: dirs[:] = [] continue for file in files: path = os.path.join(root, file) if os.path.splitext(path)[1] in ('.css', '.js'): paths.append(path) return paths files = get_files(self.publish_path) return files def get_reverse_libs(self, all = False): u''' 所有被依赖库 ''' libs = [] if self.workspace: for local_path in self.workspace.url_packages.values(): package = self.get_package(local_path) # 自己的路径在package相关library中,加入package的路径 if self.root in package.get_libs(): if package.root not in libs: libs.append(package.root) if all: sublibs = package.get_reverse_libs(all = True) libs.extend([subpath for subpath in sublibs if subpath not in libs and subpath != self.root]) return libs def get_sub_packages(self): subs = [] if self.workspace: for package_root in self.workspace.local_packages: if package_root != self.root and package_root.startswith(self.root): subs.append(package_root) else: # 遍历磁盘 pass return subs def get_libs(self, all = False): u''' 所有依赖库 ''' libs = [] def get_sub(local_path): u'获取一个库的所有依赖库并存入libs' package = self.get_package(local_path) if package: sublibs = package.get_libs(all = True) libs.extend([subpath for subpath in sublibs if subpath not in libs]) if self.workspace: if all: # 获取sub_packages的所有依赖库 sub_packages = self.get_sub_packages() for local_path in sub_packages: get_sub(local_path) for url in self.library_folders.values(): # url有可能写错了,或者url更改了 local_path = self.workspace.url_packages.get(url) if local_path: if local_path not in libs: libs.append(local_path) if all: get_sub(local_path) else: raise PackageNotFoundException(url, self.root) return libs def get_relation_files(self, source, all = False): u''' 在合并过程中相关的文件列表 ''' filetype = os.path.splitext(source)[1] if filetype == '.css' and os.path.exists(source): def pathTransformer(path): return self.get_library_path(path) imports, urls = csscompiler.getUrls(source, pathTransformer = pathTransformer, recursion = all, inAll = True) urls.extend(imports) # 需要监视的文件列表 files = [source] for aurl in urls: # 不支持 http:// 和 绝对路径 if not urlparse(aurl)[0] and not urlparse(aurl)[2].startswith('/'): file = os.path.join(os.path.split(source)[0], aurl) file = self.get_library_path(file) files.append(file) return files elif filetype == '.js' and (source in self.combines.keys() or os.path.exists(source)): if all: return [file.path for file in self.get_combine_files(source)] else: return self.combines[source] else: # 永远返回一个数组 return [] def get_combine_included(self, file): u''' 某个文件在当前库中被哪些文件引用了 ''' files = [] for combine in self.combines: for include in self.combines[combine]: if self.get_library_path(include) == file: files.append(combine) continue return files def get_included(self, source, all = False): if os.path.splitext(source)[1] == '.css': return [] else: # 到workspace中找一圈 feed_files = [] if self.workspace: for local_path in self.workspace.local_packages.keys(): package = self.get_package(local_path) feed_files.extend(package.get_combine_included(source)) else: feed_files = self.get_combine_included(source) if all: others = [] for feed_file in feed_files: others.extend(self.get_included(feed_file, all = True)) feed_files.extend(others) return feed_files def get_combine_files(self, path): u''' @param path 执行合并的文件 @return combine_files 数组用来存储此文件相关的最小颗粒度的实体文件(非合并出来的文件) ''' combine_files = [] # 存储整个合并过程中最小粒度的文件,最终会按照顺序进行合并 if path not in self.combines.keys(): return [File(path)] for include in self.combines[path]: include = self.get_library_path(include) # package变量用来存储需要执行combine方法的package # 本框架外部文件 if not include.startswith(self.source_path): root_path = StaticPackage.get_root(include) package = self.get_package(root_path) package.parse(include) # 默认为本package内部文件,为self else: package = self # 一个合并出来的文件 if include in package.combines.keys(): rFiles = package.get_combine_files(include) combine_files.extend(rFiles) elif include.find('node_modules') != -1: combine_files.append(ModuleFile(include, self.module_base)) # 一个最小粒度的文件 else: combine_files.append(File(include)) return combine_files def combine(self, output, files): u''' 将files文件列表合并成一个文件并写入到output ''' text = '' for file in files: text += file.read() target_dir = os.path.dirname(output) if not os.path.exists(target_dir): os.makedirs(target_dir) open(output, 'wb').write(text) def compile(self, filename, force = False): filename = os.path.realpath(filename) source, mode = self.parse(filename) if not source or not self.publish_path or not filename.startswith(self.publish_path): return None, None # 传进来的有可能是源文件路径 TODO if filename.startswith(self.source_path): return None, None relation_files = self.get_relation_files(source, all = True) if not relation_files: # 没有源文件的发布文件 return None, None modified, not_exists = self.listener.update(filename, relation_files) filetype = os.path.splitext(filename)[1] if filetype == '.js': if force or len(modified): combine_files = self.get_combine_files(source) self.combine(filename, combine_files) return modified, not_exists elif filetype == '.css': if DEBUG: reload(csscompiler) csscompiler.DEBUG = DEBUG def pathTransformer(path): return self.get_library_path(path) if modified or force: name = os.path.split(filename)[1] cssId = hashlib.md5(urljoin('/' + urljoin(self.serverRoot, self.serverUrl), name)).hexdigest()[:8] compiler = CSSCompiler(pathTransformer = pathTransformer) css = compiler.compile(source, mode = mode, cssId = cssId) css = self.replace_css_url(css, source, name) self.write_file(filename, css) return (modified, not_exists) return None, None def replace_css_url(self, css, source, target): u''' 将css源文件中的url路径进行转换 ''' def replaceURL(m): url = m.group(1) urltuple = urlparse(url) # 如果没有协议,则需要进行处理 if not urltuple[0]: # 如果是绝对路径,则同serverPrefix+serverRoot进行拼接 if url.startswith('/'): prefix = urljoin(self.serverPrefix, self.serverRoot) url = urljoin(prefix, url) # 如果是相对路径 else: # 拼出prefix http://xnimg.cn/n/apps/msg/ prefix = urljoin(self.serverPrefix, self.serverRoot) prefix = urljoin(prefix, self.serverUrl) # 本地路径 path = os.path.realpath(os.path.join(os.path.split(source)[0], url)) # 拼出url css/global-all-min.css relative = target[len(self.publish_path) + 1:].replace('\\', '/') url = urljoin(relative, url) # 在同一目录树的库中,判断url引用的是否是source目录中的东西 # 如果是source中的,source中的所有东西都会复制到publish中,因此不需要针对publish进行路径转换 # 如果是resource中的,resource会复制到publish中,需要进行路径转换 # 如果不是url引用的文件不是source中的,没有复制过程,需要针对publish进行路径转换 if self.resource_path and path.startswith(self.resource_path): resource_publish_path = os.path.realpath(os.path.join(self.publish_path, self.resource_dir)) url = urljoin(relativeURI(path2uri(self.publish_path), path2uri(resource_publish_path)), url) elif not path.startswith(self.source_path): url = urljoin(relativeURI(path2uri(self.publish_path), path2uri(self.source_path)), url) # 合并成最终的url http://xnimg.cn/n/core/css/global-all-min.css url = urljoin(prefix, url) return 'url(' + url + ')' return re.sub(r'url\([\'"]?(.+?)[\'"]?\)', replaceURL, css) # 绝对路径 def build_files(self): u''' 复制相关文件 ''' if not self.publish_path: return [] files = self.build_source_files() if self.resource_path: files.extend(self.build_resource_files()) return files def build_source_files(self): # package下的所有资源文件,忽略 lib 目录 allFiles = [] for root, dirs, files in os.walk(self.source_path): folders = re.split(r'[/\\]', root) if '.svn' in folders \ or root.startswith(os.path.realpath(os.path.join(self.source_path, 'lib'))): continue for file in files: if os.path.splitext(file)[1] in ('.css', '.xhtml', '.html', '.js', '.xsl', '.xml', '.un~', '.tmp', '.swp'): continue path = os.path.join(root, file) allFiles.append(path) modified, notExists = self.listener.update(self.source_path, allFiles) files = [] for file in modified: target = os.path.join(self.publish_path, file[len(self.source_path) + 1:]) target_dir = os.path.dirname(target) if not os.path.exists(target_dir): os.makedirs(target_dir) shutil.copy(file, target) files.append(target) return files def build_resource_files(self): # package下的所有资源文件,忽略 lib 目录 allFiles = [] for root, dirs, files in os.walk(self.resource_path): folders = re.split(r'[/\\]', root) if '.svn' in folders: continue for file in files: path = os.path.join(root, file) allFiles.append(path) modified, notExists = self.listener.update(self.resource_path, allFiles) files = [] for file in modified: resource_publish_path = os.path.realpath(os.path.join(self.publish_path, self.resource_dir)) target = os.path.join(resource_publish_path, file[len(self.resource_path) + 1:]) target_dir = os.path.dirname(target) if not os.path.exists(target_dir): os.makedirs(target_dir) shutil.copy(file, target) files.append(target) return files def write_file(self, path, txt): cssfile = open(path, 'wb') cssfile.write(txt) cssfile.close() def joinpath(self, path1, path2): return os.path.realpath(os.path.join(path1, path2)) def parse_config(self, xmlConfig): # 已通过source文件读取到publish_path信息 # 或者已经通过构造函数传进了publish_path信息 # 不用通过template-config.xml读取 # source引用方式下,不需要配置文件的publish配置 if self.publish_path: pass # 没有source文件,publish_path同库在同一个目录树,需要通过publish配置生成publish_path信息 else: publishNode = xmlConfig.find('publish') if publishNode != None: publishDir = xmlConfig.find('publish').get('dir') if not publishDir.endswith('/'): publishDir += '/' self.publish_path = self.joinpath(self.root, publishDir) self.url = xmlConfig.get('url') # source 是必需的 sourceDir = xmlConfig.find('source').attrib['dir'] if not sourceDir.endswith('/'): sourceDir += '/' self.source_path = self.joinpath(self.root, sourceDir) libraryNode = xmlConfig.find('library') if libraryNode != None: libraryDir = libraryNode.get('dir') self.library_path = self.joinpath(self.root, libraryDir) folderNodes = libraryNode.findall('folder') for folderNode in folderNodes: self.library_folders[folderNode.get('name')] = folderNode.get('url') resourceNode = xmlConfig.find('resource') if resourceNode != None and 'dir' in resourceNode.attrib.keys(): self.resource_dir = resourceNode.attrib['dir'] if not self.resource_dir.endswith('/'): self.resource_dir += '/' self.resource_path = self.joinpath(self.root, self.resource_dir) serverNode = xmlConfig.find('server') if serverNode != None: if 'prefix' in serverNode.attrib: self.serverPrefix = serverNode.attrib['prefix'] if 'url' in serverNode.attrib: self.serverUrl = serverNode.attrib['url'] if 'root' in serverNode.attrib: self.serverRoot = serverNode.attrib['root'] if self.serverUrl and not self.serverUrl.endswith('/'): self.serverUrl = self.serverUrl + '/' if self.serverRoot and not self.serverRoot.endswith('/'): self.serverRoot = self.serverRoot + '/' self.module_base = xmlConfig.get('module-base') combinesXML = xmlConfig.findall('source/combine') if combinesXML: for combine in combinesXML: key = self.joinpath(self.source_path, combine.get('path')) includesXML = combine.findall('include') includes = [] for include in includesXML: if include.get('module'): includePath = self.joinpath(self.source_path, 'node_modules/' + include.get('module')) else: includePath = self.joinpath(self.source_path, include.get('path')) includes.append(includePath) self.combines[key] = includes def load_config(self): ''' 解析配置文件 ''' path = os.path.join(self.root, CONFIG_FILENAME) xmlConfig = ElementTree.parse(path) self.parse_config(xmlConfig.getroot()) def get_library_path(self, includePath): includePath = os.path.realpath(includePath) # lib下的,要通过packages转换一下路径 if includePath.startswith(self.library_path): path = includePath[len(self.library_path) + 1:] if path.find(os.sep) != -1: # lib/xxx.js 直接放,没有在一个library目录中 folderName, pathInPackage = path.split(os.sep, 1) # lib下的目录名和相应package的内部路径 if folderName in self.library_folders.keys(): url = self.library_folders[folderName] if not self.workspace: raise WorkspaceNotFoundException() local_path = self.workspace.url_packages.get(url) if local_path: package = self.get_package(local_path) new_path = os.path.join(package.root, pathInPackage) return os.path.realpath(new_path) # 在lib下,也定义了folder,但是相对应的url没有在packages中配置本地磁盘的路径,或者相应的库没有配置package的url属性 else: raise PackageNotFoundException(url, self.root) return includePath def parse(self, path): u''' source 和 publish 路径一一对应 ''' path = os.path.realpath(path) if path.startswith(self.source_path): return path, None elif self.publish_path and path.startswith(self.publish_path): package_path = path[len(self.publish_path) + 1:] if os.path.splitext(path)[1] == '.css': # xxx-all-min.css --> xxx package_path = os.path.splitext(package_path)[0].split('-') if len(package_path) < 3: return (None, None) name = '-'.join(package_path[:-2]) mode = package_path[-2] source = os.path.join(self.source_path, name + '.css') else: source = os.path.join(self.source_path, package_path) mode = None return source, mode # 有可能是lib下的 else: return None, None def link(self): u''' 连接源库与发布库 ''' if self.url: package_file_path = os.path.join(self.publish_path, PACKAGE_FILENAME) open(package_file_path, 'wb').write(self.url) source_path = os.path.join(self.publish_path, SOURCE_FILENAME) source_dir = os.path.dirname(source_path) if not os.path.exists(source_dir): os.makedirs(source_dir) open(source_path, 'wb').write(self.root) @staticmethod def init(root_path): u''' 初始化一个目录为源库 ''' root_path = os.path.realpath(root_path) config_path = os.path.join(root_path, CONFIG_FILENAME) if os.path.exists(config_path): raise PackageExistsException(root_path) if not os.path.exists(root_path): os.makedirs(root_path) open(config_path, 'wb').write( '<package>\n\t<library dir="lib">\n\t</library>\n\t<source dir="src">\n\t</source>\n\t<resource dir="res">\n\t</resource>\n</package>' ) @staticmethod def is_root(path): u''' path是否是一个静态库的根路径 ''' return os.path.exists(os.path.join(path, CONFIG_FILENAME)) @staticmethod def get_root(path): u'''一个目录的源库根路径''' return StaticPackage.get_roots(path)[1] @staticmethod def get_publish(path): u''' 一个路径的发布库根路径 ''' return StaticPackage.get_roots(path, just_publish_path = True) @staticmethod def get_roots(path, just_publish_path = False, workspace = None): u''' 通过遍历父目录查找root及publish_path ''' publish_path = None root_path = None if os.path.isfile(path): path = os.path.dirname(path) while True: # 通过找 source 的方式读取到地址 publish_source_path = os.path.join(path, SOURCE_FILENAME) if os.path.exists(publish_source_path): publish_path = path root_path = os.path.realpath(open(publish_source_path, 'r').read().strip()) if just_publish_path: break # 通过.package 文件读取到地址 if workspace: package_file_path = os.path.join(path, PACKAGE_FILENAME) if os.path.exists(package_file_path): publish_path = path package_url = open(package_file_path, 'r').read().strip() package = workspace.get_package_by_url(package_url) if not package: raise PackageNotFoundException(package_url) root_path = package.root if just_publish_path: break # 直接找到配置文件 if not just_publish_path and StaticPackage.is_root(path): root_path = os.path.realpath(path) break newpath = os.path.realpath(os.path.join(path, '../')) if newpath == path: break # 已经到根目录了,停止循环 else: path = newpath if just_publish_path: return publish_path else: return publish_path, root_path
def init_listener(self): # 不是所有的库都需要有publish_path if self.publish_path: self.listener = FileListener(os.path.join(self.publish_path, INFO_PATH))
from flask import Flask, Response import os from filelistener import FileListener VERSION = "APPVERSION" HN = "HN" fl = FileListener('/app/logs/vc3.log', 60) fl.monitor() app = Flask(__name__) @app.route('/') def home(): if not fl.isFileBeingModified(): print("File is not healthy - causing an HTTP error code ") status_code = Response(status=500) return status_code ver = '(not set)' if VERSION in os.environ: ver = os.environ[VERSION] hostname = "(not set)" if HN in os.environ: hostname = os.environ[HN] return f'<h1> {hostname} is healthy, running version {ver} </h1> '