diff --git a/.gitignore b/.gitignore index e9cf5d1..77dd81e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ test.py server_config.yml venv .idea -Log \ No newline at end of file +Log +build +*.egg-info/ \ No newline at end of file diff --git a/Core/page.py b/Core/page.py deleted file mode 100644 index 1383244..0000000 --- a/Core/page.py +++ /dev/null @@ -1,115 +0,0 @@ -from .IO import File -from .styles import get_style_code -from .utils import PropertySetter -from .utils.event import triggers -from .utils.formatter import format_code -from .library import Library -from .logger import Logger -from .i18n import locale as t -from .config import config - -logger = Logger('Page') - -class PageBase(): - "页面基类" - def __init__(self, project) -> None: - self.project = project - - def generate(self, *args, **kwargs): - "获取页面 XAML 代码" - - @property - def display_name(self): - raise NotImplementedError() - - def get_content_type(self, setter): - return 'application/xml' - -class FileBasedPage(PageBase): - "基于文件的页面,仅应用于继承" - def __init__(self, file:File, project) -> None: - super().__init__(project) - self.file = file - - def generate(self, *args, **kwargs): - raise NotImplementedError() - -class RawXamlPage(FileBasedPage): - - @property - def display_name(self): - return self.file.name - - def generate(self, *args, **kwargs): - return self.file.data - -class CardStackPage(FileBasedPage): - def __init__(self, file:File, project) -> None: - super().__init__(file, project) - data = file.data - self.setter = PropertySetter(data.get('fill'), data.get('override')) - self.name = data.get('name', file.name) - self.display_name_str = data.get('display_name', self.name) - self.cardrefs = data.get('cards',{}) - self.alias = data.get('alias', []) - - @property - def display_name(self): - return self.display_name_str - - @triggers('page.generate') - def generate(self, *args, **kwargs): - xaml = self.getframe() - xaml = xaml.replace('${animations}', '') # TODO - xaml = xaml.replace('${styles}', get_style_code(self.project.resources.styles)) - xaml = xaml.replace('${content}', self.generate_content(*args, **kwargs)) - return xaml - - def generate_content(self, *args, **kwargs): - "生成页面主要内容" - runtime_setter = self.setter.clone() - runtime_setter.attach(kwargs.get('setter')) - content = '' - for card_ref in self.cardrefs: - content += self.__getcardscontent(card_ref, setter = runtime_setter) - return content - - def __getcardscontent(self, ref, setter:PropertySetter): - "一行可能有多个卡片,本方法处理整行" - ref = format_code(code=ref, card=setter.toProperties(), project=self.project) - code = '' - for each_card_ref in ref.split(';'): - code += self.__getonecardcontent(each_card_ref, setter.clone()) - return code - - def __getonecardcontent(self, ref, setter:PropertySetter): - "一行可能有多个卡片,本方法处理单个卡片" - ref = ref.replace(' ', '').split('|') - real_ref = ref[0] - args = ref[1:] if len(ref) > 1 else [] - if real_ref == '': - logger.info(t('project.get_card.null')) - return '' - setter.attach(PropertySetter.fromargs(args)) - logger.info(t('project.get_card', card_ref=real_ref)) - card = self.__getcard(real_ref,setter) - if not card: - return '' - return self.project.template_manager.build(card) - - def __getcard(self,ref,setter): - if config('Debug.Enable'): - return self.__getcardunsafe(ref, setter) - try: - return self.__getcardunsafe(ref, setter) - except Exception as ex: - logger.warning(t('project.get_card.failed', ex=ex)) - return None - - def __getcardunsafe(self,ref,setter): - card = self.project.base_library.get_card(ref, False) - card = setter.decorate(card) - return card - - def getframe(self): - return self.project.resources.page_templates['Default'] \ No newline at end of file diff --git a/Core/resource.py b/Core/resource.py deleted file mode 100644 index eeba539..0000000 --- a/Core/resource.py +++ /dev/null @@ -1,64 +0,0 @@ -''' -资源结构模块 -''' -import re -from os.path import sep -from .IO import Dire,File -from .logger import Logger -from .i18n import locale as t -from .i18n import append_locale -from Debug.timer import count_time - -logger = Logger('Resource') -YML_PATTERN = re.compile(r'.*\.yml$') -XAML_PATTERN = re.compile(r'.*\.xaml$') -PY_PATTERN = re.compile(r'.*\.py$') - -class Resource: - '''资源类''' - def load_resources(self,path:str): - '''加载资源''' - logger.info(t('resource.load',path=path)) - self.animations.update(create_res_mapping(f'{path}{sep}Animations',XAML_PATTERN)) - self.components.update(create_res_mapping(f'{path}{sep}Components',XAML_PATTERN)) - self.data.update(create_res_mapping(f'{path}{sep}Data')) - self.templates.update(create_res_mapping(f'{path}{sep}Templates',YML_PATTERN)) - self.page_templates.update(create_res_mapping(f'{path}{sep}Page_Templates',XAML_PATTERN)) - #self.load_scripts(path=path) - self.styles.update(create_res_mapping(f'{path}{sep}Styles',XAML_PATTERN)) - self.styles.update(create_res_mapping(f'{path}{sep}Styles',YML_PATTERN)) - append_locale(f'{path}{sep}i18n') - self.components.update({'':''}) # IF Failed return a null component - - def __init__(self): - self.animations = {} - self.components = {} - self.styles = {} - self.data = {'global':{}} - self.scripts = {} - self.templates = {} - self.page_templates = {} - -def create_res_mapping(path:str,patten = None): - output = {} - try: - dire = Dire(path) - mapping_file(dire,output,'',patten) - except FileNotFoundError: - return output - except Exception as ex: - logger.error(ex) - return - return output - -def mapping_file(file_or_dire,output,prefix = '',patten = None,is_toplevel:bool = True): - if isinstance(file_or_dire,File): - file = file_or_dire - output[prefix + file.name] = file.data - elif isinstance(file_or_dire,Dire): - dire = file_or_dire - for node in dire.scan(patten,include_dires=True): - mapping_file(node,output,patten=patten,is_toplevel = False, - prefix = '' if is_toplevel else f'{prefix}{dire.name}/') - else: - raise TypeError() diff --git a/Core/utils/__init__.py b/Core/utils/__init__.py deleted file mode 100644 index 7c86c1c..0000000 --- a/Core/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .property import PropertySetter -from .decos import enable_by \ No newline at end of file diff --git a/Core/utils/event.py b/Core/utils/event.py deleted file mode 100644 index d91949b..0000000 --- a/Core/utils/event.py +++ /dev/null @@ -1,72 +0,0 @@ -import functools -from typing import Dict, List -from ..logger import Logger -from ..i18n import locale - -events:Dict[str,List[callable]] = {} -logger = Logger('Event') - -def trigger_invoke(event_name:str): - '''在函数开始时触发事件''' - def wrapper(func): - @functools.wraps(func) - def inner_wrapper(*args,**kwagrs): - trigger_event(event_name,*args,**kwagrs) - return func(*args,**kwagrs) - return inner_wrapper - return wrapper - -def trigger_return(event_name:str,return_name='result'): - '''在函数返回时触发事件''' - def wrapper(func): - @functools.wraps(func) - def inner_wrapper(*args,**kwagrs): - result = func(*args,**kwagrs) - kwagrs[return_name] = result - trigger_event(event_name,*args,**kwagrs) - return result - return inner_wrapper - return wrapper - -def trigger_failed(event_name:str, re_arise = True): - '''在函数出错时触发事件''' - def wrapper(func): - @functools.wraps(func) - def inner_wrapper(*args,**kwagrs): - try: - return func(*args,**kwagrs) - except Exception as ex: - kwagrs['exception'] = ex - trigger_event(event_name, *args,**kwagrs) - if re_arise: - raise ex - return inner_wrapper - return wrapper - -def triggers(event_name:str): - '''在函数开始时、返回、和出错时触发事件''' - def wrapper(func): - @functools.wraps(func) - @trigger_invoke(event_name + '.start') - @trigger_return(event_name + '.return') - @trigger_failed(event_name + '.failed') - def inner_wrapper(*args,**kwagrs): - return func(*args,**kwagrs) - return inner_wrapper - return wrapper - -def listen_event(event_name:str): - '''监听事件''' - def wrapper(func): - if event_name not in events: - events[event_name] = [] - events[event_name].append(func) - return wrapper - -def trigger_event(event_name:str,*args,**kwargs): - logger.event(locale('event.triggered',event_name=event_name)) - actions_list = events.get(event_name) - if not actions_list: - return - for action in actions_list: - action(*args,**kwargs) \ No newline at end of file diff --git a/Debug/__init__.py b/Debug/__init__.py deleted file mode 100644 index d9fd512..0000000 --- a/Debug/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .timer import count_time \ No newline at end of file diff --git a/Interfaces/Events/__init__.py b/Interfaces/Events/__init__.py deleted file mode 100644 index 30f9526..0000000 --- a/Interfaces/Events/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -from Core.utils.event import trigger_invoke, trigger_failed, trigger_return, listen_event as on - -def on_card_creating(): - return on(event_name='card.creating') - -def on_card_created(): - return on(event_name='card.created') - -def on_card_building(): - return on(event_name='card.building') - -def on_card_builded(): - return on(event_name='card.builded') - -def on_project_loaded(): - return on(event_name='project.loaded') \ No newline at end of file diff --git a/Interfaces/__init__.py b/Interfaces/__init__.py deleted file mode 100644 index 3d44d9b..0000000 --- a/Interfaces/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -from Core.ModuleManager import script,invoke_module as invoke, require -from Core.ModuleManager.page import page_class_handles -from Core.IO import file_reader, file_writer, read_string, write_string -from Core.logger import Logger -from Core.encode import encode_escape, decode_escape -from Core.utils.formatter import format_code, get_card_prop -from Core.i18n import locale -from Core.utils import enable_by -from Core.page import PageBase, FileBasedPage -from Core.config import enable_by_config,DisabledByConfig, config \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..3385288 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,3 @@ +graft src/homepagebuilder/modules +graft src/homepagebuilder/plugins +graft src/homepagebuilder/resources \ No newline at end of file diff --git a/Modules/ExistCard.py b/Modules/ExistCard.py deleted file mode 100644 index 6de51eb..0000000 --- a/Modules/ExistCard.py +++ /dev/null @@ -1,8 +0,0 @@ -from Core.project import Project -from Interfaces import script,format_code - -@script('ExistCard') -def exist_card(card_name,proj:Project,card,**kwargs): - card_name = format_code(card_name,card=card,project=proj,children_code='',err_output='') - return (card_name in proj.base_library.card_mapping.keys()) \ - or (card_name in proj.base_library.cards.keys()) diff --git a/Modules/Global.py b/Modules/Global.py deleted file mode 100644 index 01e6003..0000000 --- a/Modules/Global.py +++ /dev/null @@ -1,5 +0,0 @@ -from Interfaces import script - -@script('Global') -def global_vers(key,res,**kwarg): - return res.data.get('global').get(key) \ No newline at end of file diff --git a/Modules/IF.py b/Modules/IF.py deleted file mode 100644 index 245f0c5..0000000 --- a/Modules/IF.py +++ /dev/null @@ -1,27 +0,0 @@ -from Interfaces import script,format_code - -@script('IF') -def if_script(eq_expression:str,true_return,false_return='',**kwargs): - project = kwargs['proj'] - card = kwargs['card'] - eq_expression.replace(' ','') - return true_return if iseq(eq_expression,card,project) else false_return - -def iseq(eq_expression,card,project) -> bool: - if '=' in eq_expression: - eqexp_left, eqexp_right = eq_expression.split('=',1) - eqexp_left = format_eq(eqexp_left,card,project) - eqexp_right = format_eq(eqexp_right,card,project) - if eqexp_left == eqexp_right: - return True - else: - if eq_expression.startswith('!'): - if format_eq(eq_expression[1:],card,project).lower() in ['false','null','none']: - return True - else: - if format_eq(eq_expression,card,project).lower() not in ['false','null','none']: - return True - return False - -def format_eq(expression:str,card,project): - return format_code(expression,card=card,project=project,children_code='',err_output='false') diff --git a/Modules/Presenters.py b/Modules/Presenters.py deleted file mode 100644 index 3929379..0000000 --- a/Modules/Presenters.py +++ /dev/null @@ -1,14 +0,0 @@ -from Interfaces import script - -@script('RawPresenter') -def raw_presenter(card,**_): - '''将卡片的 `data` 属性直接放入代码中''' - if 'data' in card: - return card['data'] - else: - return '' - -@script('ChildrenPresenter') -def children_presenter(children_code,**_): - '''将子代码放入代码中''' - return children_code diff --git a/Plugin/ProjectInfo/pack.yml b/Plugin/ProjectInfo/pack.yml deleted file mode 100644 index e4071c6..0000000 --- a/Plugin/ProjectInfo/pack.yml +++ /dev/null @@ -1 +0,0 @@ -pack_namespace: ProjectInfo \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8276777 --- /dev/null +++ b/setup.py @@ -0,0 +1,32 @@ +from setuptools import setup, find_packages + +setup( + name = "homepagebuilder", + version = "0.14.0", + author = "Nattiden", + author_email = "lightbeacon@bugjump.net", + url = "https://github.com/Light-Beacon/HomepageBuilder", + description = "A tool to generate homepage code for PCL2", + keywords = ['PCL'], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Environment :: Console', + 'Topic :: Scientific/Engineering :: Artificial Intelligence', + 'License :: OSI Approved :: GNU Affero General Public License v3', + 'Natural Language :: Chinese (Simplified)', + 'Natural Language :: English', + 'Programming Language :: Python :: 3 :: Only', + 'Programming Language :: Python :: 3.9', + 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', + ], + + packages = find_packages(where="src"), + package_dir = {"":"src"}, + include_package_data=True, + license='AGPL-3.0', + entry_points = { + 'console_scripts': [ + 'builder = homepagebuilder.main:main' + ] + } +) \ No newline at end of file diff --git a/Core/__init__.py b/src/homepagebuilder/__init__.py similarity index 100% rename from Core/__init__.py rename to src/homepagebuilder/__init__.py diff --git a/src/homepagebuilder/__main__.py b/src/homepagebuilder/__main__.py new file mode 100644 index 0000000..f0f4b2d --- /dev/null +++ b/src/homepagebuilder/__main__.py @@ -0,0 +1,3 @@ +if __name__ == '__main__': + from .main import main + main() \ No newline at end of file diff --git a/Server/__init__.py b/src/homepagebuilder/core/__init__.py similarity index 100% rename from Server/__init__.py rename to src/homepagebuilder/core/__init__.py diff --git a/src/homepagebuilder/core/builder.py b/src/homepagebuilder/core/builder.py new file mode 100644 index 0000000..1edc94d --- /dev/null +++ b/src/homepagebuilder/core/builder.py @@ -0,0 +1,94 @@ +import os +from .config import enable_by_config +from .io import Dire +from .project import Project +from .i18n import locale as t, append_locale +from .logger import Logger +from .module_manager import load_module_dire,get_check_list +from .templates_manager import TemplateManager +from .types import Builder as BuilderBase +from .loader import Loader +from .utils.paths import getbuilderpath +from ..debug import global_anlyzer as anl + +PATH_SEP = os.path.sep +logger = Logger('Builder') + +class Builder(BuilderBase): + """构建器核心""" + def __init__(self): + super().__init__() + self.envpath = os.path.dirname(os.path.dirname(__file__)) + anl.phase('初始化构建器环境') + self.__init_env() + anl.phase('初始化构建器资源') + anl.switch_in() + self.load_structure(getbuilderpath('resources/structures/')) + self.load_resources(getbuilderpath('resources/resources/')) + self.template_manager = TemplateManager() + self.load_modules(getbuilderpath('modules')) + self.load_plugins(getbuilderpath('plugins')) + self.current_project = None + anl.switch_out() + + def __init_env(self): + self.__env['components'] = {} + self.__env['builder'] = self + self.__env['data'] = {} + self.__env['page_templates'] = {} + self.__env['templates'] = {} + self.__env['project'] = None + self.__env['resources'] = {} + self.__env['setter'] = None + + def load_structure(self,dire_path): + anl.phase('加载结构文件') + self.__env['components'].update( + Loader.load_compoents(dire_path + 'components')) + self.__env['templates'].update( + Loader.load_tempaltes(dire_path + 'templates')) + self.__env['page_templates'].update( + Loader.load_page_tempaltes(dire_path + 'pagetemplates')) + + def load_resources(self,dire_path): + """加载构建器资源""" + anl.phase('加载资源文件') + logger.info(t('builder.load.resources')) + self.__env['resources'].update( + Loader.load_resources(dire_path)) + + def load_modules(self,dire_path): + """加载构建器模块""" + anl.phase('加载模块') + logger.info(t('project.load.modules')) + load_module_dire(dire_path) + + @enable_by_config('System.EnablePlugins') + def load_plugins(self, plugin_path): + """加载构建器插件""" + anl.phase('加载插件') + logger.info(t('project.load.plugins')) + anl.switch_in() + for file in Dire(plugin_path).scan_subdir(r'pack\.yml'): + data = file.data + anl.phase(file.data['pack_namespace']) + anl.switch_in() + dire = os.path.dirname(data['file_path']) + anl.phase('加载插件结构') + self.load_structure(f'{dire}{PATH_SEP}structures/') + anl.phase('加载插件资源') + self.load_resources(f'{dire}{PATH_SEP}resources') + anl.phase('加载插件模块') + load_module_dire(f'{dire}{PATH_SEP}modules') + anl.phase('加载插件本地化文件') + append_locale(f'{dire}{PATH_SEP}i18n') + anl.switch_out() + self.__check_module_wait_list() + anl.switch_out() + + def __check_module_wait_list(self): + if len(wait_list := get_check_list()) > 0: + logger.error(t('builder.check_module_list.error', wait_list=wait_list)) + + def load_proejct(self,project_path): + self.current_project = Project(self,project_path) \ No newline at end of file diff --git a/Core/config.py b/src/homepagebuilder/core/config.py similarity index 50% rename from Core/config.py rename to src/homepagebuilder/core/config.py index be1171a..b3e4d75 100644 --- a/Core/config.py +++ b/src/homepagebuilder/core/config.py @@ -1,21 +1,22 @@ import os import yaml -config_dict = {} +CONFIG_DICT = {} - -def config(key): +def config(key:str,default = None) -> object: """获取配置""" - return config_dict.get(key) - + return CONFIG_DICT.get(key,default) class DisabledByConfig(Exception): """被配置禁用""" - +def is_debugging() -> bool: + """在调试模式状态下""" + return config('Debug.Enable') def enable_by_config(key:str,default_output=None, raise_error=False): + """当配置为 True 时启用的修饰器""" def enable_by_config_deco(func:callable): def wrapper(*args,**kwagrs): if config(key): @@ -29,20 +30,27 @@ def wrapper(*args,**kwagrs): return enable_by_config_deco -def partly_init(): - global config_dict - envpath = os.path.dirname(os.path.dirname(__file__)) - filepath = f"{envpath}{os.path.sep}Config{os.path.sep}basic.yml" +def __init_default() -> None: + """加载默认配置""" + from .utils.paths import getbuilderpath + filepath = getbuilderpath('resources/configs/default.yml') if not os.path.exists(filepath): raise FileNotFoundError(f'Config file {filepath} not exist! Please re-install the program') with open(filepath,encoding='utf-8') as f: data:dict = yaml.load(f,Loader=yaml.FullLoader) - config_dict = data + CONFIG_DICT.update(data) + +def init_full() -> None: + """加载所有配置""" + from .utils.paths import getbuilderpath + import_config_dire(getbuilderpath("resources/configs")) -def fully_init(): - from .IO import Dire,ENVPATH - files = Dire(f"{ENVPATH}{os.path.sep}Config",).scan(recur=True,patten=r'.*\.yml') +def import_config_dire(direpath) -> None: + """从文件夹路径加载配置""" + from .io.structure import Dire + files = Dire(direpath).scan(recur=True,patten=r'.*\.yml') for file in files: - config_dict.update(file.data) + CONFIG_DICT.update(file.data) -partly_init() +##ON IMPORTED +__init_default() diff --git a/src/homepagebuilder/core/elements/__init__.py b/src/homepagebuilder/core/elements/__init__.py new file mode 100644 index 0000000..2285529 --- /dev/null +++ b/src/homepagebuilder/core/elements/__init__.py @@ -0,0 +1 @@ +from .compoent import Component \ No newline at end of file diff --git a/src/homepagebuilder/core/elements/compoent.py b/src/homepagebuilder/core/elements/compoent.py new file mode 100644 index 0000000..f6f715a --- /dev/null +++ b/src/homepagebuilder/core/elements/compoent.py @@ -0,0 +1,27 @@ +from typing import List, TYPE_CHECKING +from ..utils.finder import find_using_resources +from ..formatter import format_code + +if TYPE_CHECKING: + from core.io import File + from core.types import BuildingEnvironment + +class Component: + def __init__(self, file: 'File') -> None: + self.file:File = file + self.used_resources:List[str] = self.__findusedresources() + + def toxaml(self, card, env, children_code = '') -> str: + self.mark_used_resources(card,env) + return format_code(code = self.file.data,data = card,env=env,children_code=children_code) + + def __findusedresources(self): + return find_using_resources(self.file) + + def mark_used_resources(self,card,env:'BuildingEnvironment'): + for res_ref in self.used_resources: + res_ref = format_code(code = res_ref,data=card,env=env) + env.get('used_resources').add(res_ref) + + def __str__(self) -> str: + return self.file.data \ No newline at end of file diff --git a/src/homepagebuilder/core/elements/page.py b/src/homepagebuilder/core/elements/page.py new file mode 100644 index 0000000..ac15c57 --- /dev/null +++ b/src/homepagebuilder/core/elements/page.py @@ -0,0 +1,99 @@ +from abc import abstractmethod +from ..IO import File +from ..styles import get_style_code +from ..utils import PropertySetter +from ..utils.event import set_triggers +from ..formatter import format_code +from ..library import Library +from ..logger import Logger +from ..i18n import locale as t +from ..config import config +from ..types import BuildingEnvironment, PageBase + +logger = Logger('Page') +class FileBasedPage(PageBase): + "基于文件的页面,仅应用于继承" + def __init__(self, file:File) -> None: + super().__init__() + self.file = file + +class RawXamlPage(FileBasedPage): + """纯XAML页面""" + @property + def display_name(self): + return self.file.name + + def generate(self, env): + return self.file.data + +class CardStackPage(FileBasedPage): + """卡片堆叠页面""" + def __init__(self, file:File,) -> None: + super().__init__(file) + data = file.data + self.setter = PropertySetter(data.get('fill'), data.get('override')) + self.name = data.get('name', file.name) + self.display_name_str = data.get('display_name', self.name) + self.cardrefs = data.get('cards',{}) + self.alias = data.get('alias', []) + + @property + def display_name(self): + return self.display_name_str + + @set_triggers('page.generate') + def generate(self, env): + xaml = self.getframe(env) + xaml = xaml.replace('${animations}', '') # TODO + xaml = xaml.replace('${content}', self.generate_content(env)) + xaml = xaml.replace('${styles}', get_style_code(env)) + return xaml + + def generate_content(self, env:BuildingEnvironment): + """生成页面主要内容""" + runtime_setter = self.setter.clone() + runtime_setter.attach(env.get('setter')) + content = '' + for card_ref in self.cardrefs: + content += self.__getcardscontent(card_ref, env, setter = runtime_setter) + return content + + def __getcardscontent(self, ref:str, env:BuildingEnvironment, setter:PropertySetter): + """一行可能有多个卡片,本方法处理整行""" + ref = format_code(code=ref, data=setter.toProperties(), env=env) + code = '' + for each_card_ref in ref.split(';'): + code += self.__getonecardcontent(each_card_ref, env, setter.clone()) + return code + + def __getonecardcontent(self, ref, env:BuildingEnvironment, setter:PropertySetter): + """一行可能有多个卡片,本方法处理单个卡片""" + ref = ref.replace(' ', '').split('|') + real_ref = ref[0] + args = ref[1:] if len(ref) > 1 else [] + if real_ref == '': + logger.info(t('project.get_card.null')) + return '' + setter.attach(PropertySetter.fromargs(args)) + logger.info(t('project.get_card', card_ref=real_ref)) + card = self.__getcard(real_ref,env,setter) + if not card: + return '' + return env.get('builder').template_manager.build(card,env) + + def __getcard(self,ref,env:BuildingEnvironment,setter): + if config('Debug.Enable'): + return self.__getcardunsafe(ref, env, setter) + try: + return self.__getcardunsafe(ref, env, setter) + except Exception as ex: + logger.warning(t('project.get_card.failed', ex=ex)) + return None + + def __getcardunsafe(self,ref,env:BuildingEnvironment,setter): + card = env.get('project').base_library.get_card(ref, False) + card = setter.decorate(card) + return card + + def getframe(self,env:BuildingEnvironment): + return env.get('project').resources.page_templates['Default'] \ No newline at end of file diff --git a/Core/utils/formatter.py b/src/homepagebuilder/core/formatter.py similarity index 77% rename from Core/utils/formatter.py rename to src/homepagebuilder/core/formatter.py index 996dc73..cd8c009 100644 --- a/Core/utils/formatter.py +++ b/src/homepagebuilder/core/formatter.py @@ -1,18 +1,27 @@ """ 该模块用于格式化代码 """ -from typing import Dict -from ..logger import Logger -from ..ModuleManager import invoke_script +from typing import Dict, TYPE_CHECKING +from .logger import Logger +from .module_manager import invoke_script + +if TYPE_CHECKING: + from .types import BuildingEnvironment + logger = Logger('Formatter') -def format_code(code:str,card:Dict[str,object], - project,children_code:str='',stack:list = None,err_output = None): +def format_code(code: str, + data: Dict[str,object], + env: 'BuildingEnvironment', + children_code: str = '', + stack:list = None, + err_output = None): '''格式化代码''' if not isinstance(code,str): return code if not stack: stack = [] + project = env.get('project') code = str(code) matches = findall_placeholders(code) for match in matches: @@ -29,11 +38,11 @@ def format_code(code:str,card:Dict[str,object], if attr_name.startswith('$') or attr_name.startswith('@'): script_name=qurey_tuple[0][1:] replacement = invoke_script(script_name=script_name, - project=project,card=card,args=qurey_tuple[1:], + project=project,env=env,card=data,args=qurey_tuple[1:], children_code=children_code) else: try: - replacement = get_card_prop(card,attr_name) + replacement = get_card_prop(data,attr_name) except Exception: if err_output: return err_output @@ -44,7 +53,7 @@ def format_code(code:str,card:Dict[str,object], continue stack.append(code) try: - replacement = format_code(replacement,card,project,children_code,stack) + replacement = format_code(replacement,data,env,children_code,stack) finally: stack.pop() code = code.replace(f'${{{match}}}',str(replacement),1) @@ -60,8 +69,18 @@ def dfs_get_prop(current_tree,prop_path:str): if next_tree := current_tree.get(this_name): return dfs_get_prop(next_tree,next_path) else: - raise KeyError() - + raise PropNotFoundError(prop_path) + +class PropNotFoundError(Exception): + def __init__(self, key, *args,): + super().__init__(*args) + self.key = key + +class PropNotFormatedError(Exception): + def __init__(self, key, *args,): + super().__init__(*args) + self.key = key + def split_args(string:str): '''分离参数''' args = [] diff --git a/Core/i18n.py b/src/homepagebuilder/core/i18n.py similarity index 90% rename from Core/i18n.py rename to src/homepagebuilder/core/i18n.py index 42feda5..6f8fde1 100644 --- a/Core/i18n.py +++ b/src/homepagebuilder/core/i18n.py @@ -1,9 +1,9 @@ -import os from locale import getdefaultlocale from string import Template from .config import config from .logger import Logger -from .IO import Dire +from .io import Dire +from .utils.paths import getbuilderpath CONFIG_LANG = config('System.Language') DEFAULTLANG,_ = getdefaultlocale() if str(CONFIG_LANG).lower() == 'auto' else (CONFIG_LANG,None) locales = {} @@ -11,8 +11,7 @@ def init(locales_tree): '''初始化''' - envpath = os.path.dirname(os.path.dirname(__file__)) - i18n_dire = Dire(f"{envpath}{os.path.sep}i18n",) + i18n_dire = Dire(getbuilderpath("resources/i18n",)) files = i18n_dire.scan() for file in files: locales_tree[file.name] = file.data diff --git a/Core/IO/__init__.py b/src/homepagebuilder/core/io/__init__.py similarity index 100% rename from Core/IO/__init__.py rename to src/homepagebuilder/core/io/__init__.py diff --git a/Core/IO/accessor.py b/src/homepagebuilder/core/io/accessor.py similarity index 89% rename from Core/IO/accessor.py rename to src/homepagebuilder/core/io/accessor.py index 8e1033e..cc5a724 100644 --- a/Core/IO/accessor.py +++ b/src/homepagebuilder/core/io/accessor.py @@ -1,5 +1,6 @@ import os -from Core.logger import Logger +from ..logger import Logger +from typing import Iterable logger = Logger('IO') read_func_mapping = {} @@ -22,24 +23,24 @@ def reg_filetype(func,file_exten:str,action:str): read_func_mapping[file_exten] = func elif action == 'w': write_func_mapping[file_exten] = func - if isinstance(file_extentions,list): - for exten in file_extentions: - reg_filetype(func,exten,action) - elif isinstance(file_extentions,str): + if isinstance(file_extentions,str): if file_extentions[0] == '.': file_extentions = file_extentions[1:] reg_filetype(func,file_extentions,action) + elif isinstance(file_extentions,Iterable): + for exten in file_extentions: + regist_file_function(func,action,exten) else: raise TypeError() -def file_reader(file_extentions): +def file_reader(*file_extentions): '''(修饰器)注册该函数为文件读取函数''' def decorator(func): regist_file_function(func,'r',file_extentions) return func return decorator -def file_writer(file_extentions): +def file_writer(*file_extentions): '''(修饰器)注册该函数为文件写入函数''' def decorator(func): regist_file_function(func,'w',file_extentions) diff --git a/Core/IO/formats.py b/src/homepagebuilder/core/io/formats.py similarity index 85% rename from Core/IO/formats.py rename to src/homepagebuilder/core/io/formats.py index 858dd0d..b4320a5 100644 --- a/Core/IO/formats.py +++ b/src/homepagebuilder/core/io/formats.py @@ -1,7 +1,7 @@ import json import yaml -import os -from Core.IO import file_reader,file_writer +from pathlib import Path +from .accessor import file_reader,file_writer @file_reader('json') def read_json(filepath) -> dict: @@ -29,5 +29,8 @@ def read_string(filepath:str): @file_writer(['txt','xaml']) def write_string(filepath:str,data:str): '''写入字符串文件''' + file = Path(filepath) + if not file.is_file(): + file.touch() with open(filepath, "w",encoding="utf-8") as file: return file.write(data) diff --git a/Core/IO/structure.py b/src/homepagebuilder/core/io/structure.py similarity index 73% rename from Core/IO/structure.py rename to src/homepagebuilder/core/io/structure.py index 2429a43..5a2fe1a 100644 --- a/Core/IO/structure.py +++ b/src/homepagebuilder/core/io/structure.py @@ -1,26 +1,44 @@ import re import os import sys -from typing import List,Dict,Union -from Core.config import config -from Core.logger import Logger -from .accessor import read,write +from typing import List,Union +from ..logger import Logger +from ..config import config +from .accessor import read +SEP = os.sep logger = Logger('IO') -ALL = re.compile('.*') -SEP = os.path.sep +ANYPATTERN = re.compile('.*') -ENABLE_SYMLINK = config('IO.EnableSymbolink') -ENABLE_SYMLINK_ERROR = config('IO.EnableSymbolinkError') -ENABLE_INGORE = config('IO.EnableIngore') -INGORE_PREFIX = config('IO.IngorePrefix') -INGORE_SUFFIX = config('IO.IngoreSuffix') +#region [CONFIG FUNCTIONS]配置文件函数 +def IS_ENABLE_SYMLINK() -> bool: + """是否忽略符号链接""" + return config('IO.EnableSymbolink', False) +def IS_ENABLE_SYMLINK_ERROR() -> bool: + """当遇到符号链接时是否报错""" + return config('IO.EnableSymbolinkError', True) + +def IS_ENABLE_INGORE() -> bool: + """是否启用文件首尾为特定格式时忽略""" + return config('IO.EnableIngore') + +def GET_INGORE_PREFIX() -> List[str]: + """需要忽略的文件的前缀名""" + return config('IO.IngorePrefix',['#','.']) + +def GET_INGORE_SUFFIX() -> List[str]: + """需要忽略的文件的前缀名""" + return config('IO.IngoreSuffix',['.disabled']) +#endregion + +#region [EXCEPTIONS]异常 class SymbolLinkError(Exception): '''Target is a symbol link''' class IsAFileError(Exception): '''Operation dosen't work on files''' +#endregion class File(): def __init__(self,abs_path,read_init = False): @@ -45,6 +63,8 @@ def data(self,func = None,): def read(self,func = None,usecache:bool = True): '''读取文件''' #logger.debug(f'读取{self.abs_path}') + if config('Debug.Enable'): + return read(self,func,usecache) try: return read(self,func,usecache) except Exception as ex: @@ -74,19 +94,19 @@ def __add_node(self,path) -> None: elif os.path.isdir(path): self.dires[basename] = Dire(path) elif os.path.islink(path): - if ENABLE_SYMLINK_ERROR: + if IS_ENABLE_SYMLINK(): self.__add_node(os.readlink(path)) - elif ENABLE_SYMLINK_ERROR: + elif IS_ENABLE_SYMLINK_ERROR(): raise SymbolLinkError(f'{path} is a symbol link') else: raise NotImplementedError(f'Cannot judge type of {path}, it is not a file, directory, or a symbol link.') - def __should_be_ingore(self,name): + def __should_be_ingored(self,name): basename = os.path.basename(name) - for prefix in INGORE_PREFIX: + for prefix in GET_INGORE_PREFIX(): if basename.startswith(prefix): return True - for suffix in INGORE_SUFFIX: + for suffix in GET_INGORE_SUFFIX(): if basename.endswith(suffix): return True return False @@ -95,19 +115,23 @@ def __self_scan(self): self.files = {} self.dires = {} for rel_path in os.listdir(self.abs_path): - if ENABLE_INGORE: - if self.__should_be_ingore(rel_path): + if IS_ENABLE_INGORE(): + if self.__should_be_ingored(rel_path): continue self.__add_node(f'{self.abs_path}{SEP}{rel_path}') - def scan_subdir(self,patten:Union[str|re.Pattern] = ALL): + def scan_subdir(self,patten:Union[str|re.Pattern] = ANYPATTERN): '''和 `scan` 效果相似,函数会返回该文件夹所有下边的一层文件夹的指定文件列表''' return self.scan(patten=patten,recur=True,min_recur_deepth=1,max_recur_deepth=1) - def scan(self,patten:Union[str|re.Pattern] = ALL, - recur:bool=False,dire_patten:Union[str|re.Pattern] = ALL, - min_recur_deepth:int = 0,max_recur_deepth:Union[int|None] = sys.maxsize, - include_dires:bool = False,include_files:bool = True, + def scan(self, + patten:Union[str|re.Pattern] = ANYPATTERN, + recur:bool=False, + dire_patten:Union[str|re.Pattern] = ANYPATTERN, + min_recur_deepth:int = 0, + max_recur_deepth:int = sys.maxsize, + include_dires:bool = False, + include_files:bool = True, ) -> List[File]: '''遍历文件夹下所有文件夹所有文件, 读取其中符合正则表达式的文件, 最后以列表形式输出 ## 参数 @@ -124,7 +148,7 @@ def scan(self,patten:Union[str|re.Pattern] = ALL, if not (self.files or self.dires): self.__self_scan() if not patten: - patten = ALL + patten = ANYPATTERN output = [] if min_recur_deepth <= 0 : if include_files: diff --git a/Core/library.py b/src/homepagebuilder/core/library.py similarity index 88% rename from Core/library.py rename to src/homepagebuilder/core/library.py index c3e3dfd..3216a1d 100644 --- a/Core/library.py +++ b/src/homepagebuilder/core/library.py @@ -2,16 +2,17 @@ 该模块内存放了卡片库类 ''' import os -from .IO import Dire,File +from .io import Dire,File from .logger import Logger from .i18n import locale as t -from Core.utils.event import trigger_invoke,trigger_return,triggers -from Core.utils import PropertySetter +from .utils.event import set_triggers +from .utils.property import PropertySetter logger = Logger('Library') + class Library: '''卡片库类''' - @triggers('library.init') + @set_triggers('library.init') def __init__(self,data:dict): self.name= data['name'] logger.info(t('library.load',name=self.name)) @@ -34,7 +35,8 @@ def __get_decoless_card(self,card_ref:str,is_original:bool): if ':' in card_ref: libname, card_ref = card_ref.split(':',2) return self.get_card_from_mapping(card_ref,libname,is_original) - + + @set_triggers('library.getcard') def get_card(self,card_ref:str,is_original:bool): '''获取卡片''' target = self.__get_decoless_card(card_ref,is_original) @@ -52,19 +54,21 @@ def get_card_from_mapping(self,card_ref,lib_name,is_original): return {'templates':[card_ref]} targetlib = self.libs_mapping.get(lib_name) if not targetlib: - raise logger.exception(KeyError(f'[Library] Cannot find library "{lib_name}"')) + exp = KeyError(f'[Library] Cannot find library "{lib_name}"') + logger.exception(exp) + raise exp return targetlib.get_card(lib_name + ':' + card_ref,is_original) else: if card_ref in self.cards: return self.cards[card_ref].copy() targetlib = self.card_mapping.get(card_ref) if not targetlib: - raise logger.exception(KeyError(f'[Library] Cannot find card "{card_ref} among sub-libraries"')) + exp = KeyError(f'[Library] Cannot find card "{card_ref} among sub-libraries"') + logger.exception(exp) + raise exp return targetlib.get_card(card_ref,is_original) - - @trigger_invoke('card.creating') - @trigger_return('card.created',return_name='card') + @set_triggers('library.creatcard.fromfile') def add_card_from_file(self,file:File): '''通过文件添加卡片''' filename = file.name @@ -82,6 +86,7 @@ def add_card_from_file(self,file:File): 'card_name':name,'file': file}) return self.cards[name] + @set_triggers('library.subs.add') def add_sub_libraries(self,files): '''增加子库''' def add_sub_library(self,yamldata): @@ -107,6 +112,7 @@ def add_sub_library(self,yamldata): add_sub_library(self,files) # DEV NOTICE 如果映射的内存占用太大了就将每一个卡片和每一个子库的路径压成栈,交给根库来管理 + @set_triggers('library.getallcard') def get_all_cards(self): '''获取该库的所有卡片''' result = [self.setter.decorate(card) for card in self.cards.values()] diff --git a/src/homepagebuilder/core/loader.py b/src/homepagebuilder/core/loader.py new file mode 100644 index 0000000..87ef55c --- /dev/null +++ b/src/homepagebuilder/core/loader.py @@ -0,0 +1,85 @@ +import re +from typing import Dict +from .io import Dire, File +from .logger import Logger +from .elements import Component +from .resource import ResourceLoader +from .config import is_debugging +from .i18n import locale + +YAML_PATTERN = re.compile(r'.*\.ya?ml$') +XAML_PATTERN = re.compile(r'.*\.xaml$') +PY_PATTERN = re.compile(r'.*\.py$') + +logger = Logger('Loader') + +class Loader(): + + @classmethod + def load_compoents(cls,direpath): + return cls.create_structure_mapping(direpath,XAML_PATTERN,Component) + + @classmethod + def load_tempaltes(cls,direpath): + return cls.create_structure_mapping(direpath,YAML_PATTERN) + + @classmethod + def load_page_tempaltes(cls,direpath): + return cls.create_structure_mapping(direpath,XAML_PATTERN) + + @classmethod + def load_resources(cls,direpath): + output = {} + try: + dire = Dire(direpath) + except FileNotFoundError: + return output + if is_debugging(): + return cls.__load_resources_unsafe(dire) + try: + output = cls.__load_resources_unsafe(dire) + except Exception as ex: + logger.error(ex) + return {} + return output + + @classmethod + def __load_resources_unsafe(cls,dire): + output = {} + files = dire.scan(patten=YAML_PATTERN,recur=True) + files.extend(dire.scan(patten=XAML_PATTERN,recur=True)) + output.update(ResourceLoader.loadfiles(files)) + return output + + @classmethod + def create_structure_mapping(cls,path:str,patten = None,item_type=None): + output = {} + try: + dire = Dire(path) + cls.mapping_file(file_or_dire = dire, item_type = item_type, + output = output,patten = patten) + except FileNotFoundError: + return output + except Exception as ex: + logger.error(ex) + return + return output + + @classmethod + def mapping_file(cls,file_or_dire,output:Dict[str,object],item_type:type=None,prefix = '',patten = None,is_toplevel:bool = True): + if isinstance(file_or_dire,File): + file = file_or_dire + name = prefix + file.name + if item_type: + output[name] = item_type(file=file) + logger.debug(locale('loader.regist.structure.withtype',type_name=item_type.__name__,name=name)) + else: + output[name] = file.data + logger.debug(locale('loader.regist.structure',name=name)) + elif isinstance(file_or_dire,Dire): + dire = file_or_dire + for node in dire.scan(patten,include_dires=True): + cls.mapping_file(node,output,patten=patten,is_toplevel = False, item_type=item_type, + prefix = '' if is_toplevel else f'{prefix}{dire.name}/') + else: + raise TypeError() \ No newline at end of file diff --git a/Core/logger.py b/src/homepagebuilder/core/logger.py similarity index 99% rename from Core/logger.py rename to src/homepagebuilder/core/logger.py index ed71bc8..82e7ac9 100644 --- a/Core/logger.py +++ b/src/homepagebuilder/core/logger.py @@ -5,7 +5,7 @@ import os.path import sys import time -from Core.config import config +from .config import config TAB_TEXT = ' ' CONSOLE_CLEAR = '\033[0m' diff --git a/Core/ModuleManager/__init__.py b/src/homepagebuilder/core/module_manager/__init__.py similarity index 100% rename from Core/ModuleManager/__init__.py rename to src/homepagebuilder/core/module_manager/__init__.py diff --git a/Core/ModuleManager/loader.py b/src/homepagebuilder/core/module_manager/loader.py similarity index 92% rename from Core/ModuleManager/loader.py rename to src/homepagebuilder/core/module_manager/loader.py index 8f05221..406db40 100644 --- a/Core/ModuleManager/loader.py +++ b/src/homepagebuilder/core/module_manager/loader.py @@ -3,10 +3,10 @@ import sys import re from typing import List -from Core.logger import Logger -from Core.i18n import locale as t -from Core.IO import file_reader, Dire -from Core.ModuleManager.manager import modules +from ..logger import Logger +from ..i18n import locale as t +from ..io import file_reader, Dire +from .manager import modules PY_PATTERN = re.compile(r'.*\.py$') @@ -69,11 +69,11 @@ def load_module(module_path:str,queue_load:bool=False): name,_ = os.path.splitext(file_name) if name in modules : # Module already exist - logger.info(t('module.reload',name=name)) + logger.debug(t('module.reload',name=name)) module = importlib.reload(modules[name]) else: if not queue_load: - logger.info(t('module.load',name=name)) + logger.debug(t('module.load',name=name)) # Add module sys.path.append(path_to) try: @@ -82,7 +82,7 @@ def load_module(module_path:str,queue_load:bool=False): # 如果请求加载某些模块 dependency_manager.require(rd,module_path) return UnLoadedFunction(module_path) - logger.info(t('module.load.success',name=name)) + logger.debug(t('module.load.success',name=name)) modules[name] = module for module in dependency_manager.satisfied(name): # 依赖已经成功加载需要重新加载的模块 diff --git a/Core/ModuleManager/manager.py b/src/homepagebuilder/core/module_manager/manager.py similarity index 100% rename from Core/ModuleManager/manager.py rename to src/homepagebuilder/core/module_manager/manager.py diff --git a/Core/ModuleManager/page.py b/src/homepagebuilder/core/module_manager/page.py similarity index 88% rename from Core/ModuleManager/page.py rename to src/homepagebuilder/core/module_manager/page.py index d09bcd4..8680ee8 100644 --- a/Core/ModuleManager/page.py +++ b/src/homepagebuilder/core/module_manager/page.py @@ -1,6 +1,6 @@ -from Core.utils.event import listen_event -from Core.logger import Logger -from Core.i18n import locale as t +from ..utils.event import listen_event +from ..logger import Logger +from ..i18n import locale as t logger = Logger('Module') diff --git a/Core/ModuleManager/script.py b/src/homepagebuilder/core/module_manager/script.py similarity index 58% rename from Core/ModuleManager/script.py rename to src/homepagebuilder/core/module_manager/script.py index 147d9c0..32cc1a1 100644 --- a/Core/ModuleManager/script.py +++ b/src/homepagebuilder/core/module_manager/script.py @@ -1,23 +1,30 @@ -from typing import Dict -from Core.config import config -from Core.logger import Logger -from Core.i18n import locale as t +from typing import Dict, TYPE_CHECKING +from ..config import config +from ..logger import Logger +from ..i18n import locale as t + +if TYPE_CHECKING: + from ...core.types import BuildingEnvironment logger = Logger('ScriptsManager') NO_ERR_OUTPUT_WHILE_SCRIPT_NOTFOUND = config('System.Script.IgnoreError') scripts = {} def script(script_name): - '''(修饰器)注册该函数为脚本''' + '''(修饰器)注册该函数为脚本 + ## 传入参数 + *args 脚本参数 + card 卡片 + env 环境 + ''' def decorator(func): scripts[script_name] = func return func return decorator -def invoke_script(script_name:str,project,card:Dict[str,object], +def invoke_script(script_name:str,env:'BuildingEnvironment',card:Dict[str,object], args:list,**kwargs): '''获取脚本输出结果''' - resources = project.resources script_code = scripts.get(script_name) if script_code is None: if NO_ERR_OUTPUT_WHILE_SCRIPT_NOTFOUND: @@ -25,5 +32,5 @@ def invoke_script(script_name:str,project,card:Dict[str,object], else: logger.error(t('script.invoke.failed.notfound',name = script_name)) return '' - result = scripts[script_name](*args,card=card,res=resources,proj=project,**kwargs) + result = scripts[script_name](*args,card=card,env=env,**kwargs) return result diff --git a/src/homepagebuilder/core/page.py b/src/homepagebuilder/core/page.py new file mode 100644 index 0000000..bd83485 --- /dev/null +++ b/src/homepagebuilder/core/page.py @@ -0,0 +1,129 @@ +from abc import abstractmethod +from typing import TYPE_CHECKING +from .types import BuildingEnvironment +from .utils.property import PropertySetter +from .utils.event import set_triggers +from .formatter import format_code +from .logger import Logger +from .i18n import locale as t +from .config import config +from .resource import get_resources_code +from ..debug import global_anlyzer as anl + +if TYPE_CHECKING: + from .types import BuildingEnvironment + from .io import File + +logger = Logger('Page') + +class PageBase(): + "页面基类" + @abstractmethod + def generate(self, env:'BuildingEnvironment'): + "获取页面 XAML 代码" + + @property + def display_name(self): + raise NotImplementedError() + + def get_content_type(self, setter:PropertySetter): + return 'application/xml' + +class FileBasedPage(PageBase): + "基于文件的页面,仅应用于继承" + def __init__(self, file: 'File') -> None: + super().__init__() + self.file = file + +class CodeBasedPage(): + "基于代码的页面,仅应用于继承" + def __init__(self, project) -> None: + super().__init__() + self.project = project +class RawXamlPage(FileBasedPage): + """纯XAML页面""" + @property + def display_name(self): + return self.file.name + + def generate(self, env): + return self.file.data + +class CardStackPage(FileBasedPage): + """卡片堆叠页面""" + def __init__(self, file: 'File') -> None: + super().__init__(file) + data = file.data + self.setter = PropertySetter(data.get('fill'), data.get('override')) + self.name = data.get('name', file.name) + self.display_name_str = data.get('display_name', self.name) + self.cardrefs = data.get('cards',{}) + self.alias = data.get('alias', []) + + @property + def display_name(self): + return self.display_name_str + + @set_triggers('page.generate') + def generate(self, env): + anl.phase(self.name) + anl.switch_in() + xaml = self.getframe(env) + #xaml = xaml.replace('${animations}', '') # TODO + anl.phase("内容") + anl.switch_in() + xaml = xaml.replace('${content}', self.generate_content(env)) + anl.switch_out() + xaml = xaml.replace('${styles}', get_resources_code(env)) + anl.switch_out() + return xaml + + def generate_content(self, env:'BuildingEnvironment'): + """生成页面主要内容""" + runtime_setter = self.setter.clone() + runtime_setter.attach(env.get('setter')) + content = '' + for card_ref in self.cardrefs: + anl.phase(card_ref) + content += self.__getcardscontent(card_ref, env, setter = runtime_setter) + return content + + def __getcardscontent(self, ref:str, env:'BuildingEnvironment', setter:PropertySetter): + """一行可能有多个卡片,本方法处理整行""" + ref = format_code(code=ref, data=setter.toProperties(), env=env) + code = '' + for each_card_ref in ref.split(';'): + code += self.__getonecardcontent(each_card_ref, env, setter.clone()) + return code + + def __getonecardcontent(self, ref, env:'BuildingEnvironment', setter:PropertySetter): + """一行可能有多个卡片,本方法处理单个卡片""" + ref = ref.replace(' ', '').split('|') + real_ref = ref[0] + args = ref[1:] if len(ref) > 1 else [] + if real_ref == '': + logger.info(t('project.get_card.null')) + return '' + setter.attach(PropertySetter.fromargs(args)) + logger.info(t('project.get_card', card_ref=real_ref)) + card = self.__getcard(real_ref,env,setter) + if not card: + return '' + return env.get('builder').template_manager.build(card,env) + + def __getcard(self,ref,env:'BuildingEnvironment',setter): + if config('Debug.Enable'): + return self.__getcardunsafe(ref, env, setter) + try: + return self.__getcardunsafe(ref, env, setter) + except Exception as ex: + logger.warning(t('project.get_card.failed', ex=ex)) + return None + + def __getcardunsafe(self,ref:str,env:'BuildingEnvironment',setter): + card = env.get('project').base_library.get_card(ref, False) + card = setter.decorate(card) + return card + + def getframe(self,env:'BuildingEnvironment'): + return env.get('page_templates')['Default'] \ No newline at end of file diff --git a/Core/project.py b/src/homepagebuilder/core/project.py similarity index 51% rename from Core/project.py rename to src/homepagebuilder/core/project.py index 858239d..1078a7d 100644 --- a/Core/project.py +++ b/src/homepagebuilder/core/project.py @@ -2,123 +2,128 @@ 工程文件模块,构建器核心 """ import os -from typing import Dict -from .IO import Dire, File +from .io import Dire, File from .library import Library -from .resource import Resource -from .styles import get_style_code -from .templates_manager import TemplateManager -from .utils.formatter import format_code from .logger import Logger from .i18n import locale as t -from .config import enable_by_config -from .ModuleManager import load_module_dire,get_check_list -from .utils.event import trigger_invoke,trigger_return,triggers -from Debug import count_time -from .page import CardStackPage, RawXamlPage, PageBase - +from .module_manager import load_module_dire,get_check_list +from .utils.event import set_triggers +from .utils.paths import fmtpath +from ..debug import global_anlyzer as anl +from .page import CardStackPage, RawXamlPage +from .loader import Loader +from .types import Project as ProjectBase +from .config import import_config_dire PATH_SEP = os.path.sep logger = Logger('Project') -class Project: +class Project(ProjectBase): """工程类""" - - @enable_by_config('System.EnablePlugins') - def load_plugins(self, plugin_path): - """加载插件""" - for file in Dire(plugin_path).scan_subdir(r'pack\.yml'): - data = file.data - dire = os.path.dirname(data['file_path']) - self.resources.load_resources(f'{dire}{PATH_SEP}Resources') - load_module_dire(f'{dire}{PATH_SEP}Modules', self) - - def checkModuleWaitList(self): + def __checkModuleWaitList(self): if len(wait_list := get_check_list()) > 0: logger.error(t('project.check_module_list.error', wait_list=wait_list)) - @triggers('project.import') + @set_triggers('project.import') def import_pack(self, path): """导入工程包""" + anl.phase('加载工程包') + anl.switch_in() logger.info(t('project.import.start', path=path)) - pack_info = File(path).read() - self.version = pack_info['version'] - self.default_page = pack_info.get('default_page') - logger.info(t('project.import.pack.version', version=self.version)) - self.base_path = os.path.dirname(path) + self.__init_load_projectfile(path) + self.__init_import_configs() self.__init_import_modules() - self.__init_import_cards() + self.__init_import_structures() self.__init_import_resources() + self.__init_import_cards() self.__init_import_pages() + self.__init_import_data() + anl.switch_out() logger.info(t('project.import.success')) - @triggers('project.import.modules') + @set_triggers('project.import.projectfile') + def __init_load_projectfile(self,path): + anl.phase('读取工程文件') + pack_info = File(path).read() + self.base_path = os.path.dirname(path) + self.version = pack_info['version'] + self.default_page = pack_info.get('default_page') + logger.info(t('project.import.pack.version', version=self.version)) + + def __init_import_configs(self): + anl.phase('导入配置') + try: + import_config_dire(fmtpath(self.base_path,'/configs')) + except FileNotFoundError: + pass + + @set_triggers('project.impoort.structures') + def __init_import_structures(self): + anl.phase('导入构件') + self.__env['components'].update(Loader.load_compoents( + fmtpath(self.base_path,'/structures/components'))) + anl.phase('导入卡片模版') + self.__env['templates'].update(Loader.load_tempaltes( + fmtpath(self.base_path,'/structures/templates'))) + anl.phase('导入页面模版') + self.__env['page_templates'].update(Loader.load_page_tempaltes( + fmtpath(self.base_path,'/structures/pagetemplates'))) + + def __init_import_data(self): + anl.phase('读取数据文件') + self.__env['data'].update(Loader.create_structure_mapping( + fmtpath(self.base_path,'/data'))) + + @set_triggers('project.import.modules') def __init_import_modules(self): + anl.phase('导入模块') logger.info(t('project.import.modules')) - load_module_dire(f'{self.base_path}{PATH_SEP}Modules', self) + load_module_dire(fmtpath(self.base_path,'/modules'), self) + self.__checkModuleWaitList() - @triggers('project.import.cards') + @set_triggers('project.import.cards') def __init_import_cards(self): + anl.phase('导入卡片') logger.info(t('project.import.cards')) self.base_library = Library(File( - f"{self.base_path}{PATH_SEP}Libraries{PATH_SEP}__LIBRARY__.yml").data) + fmtpath(self.base_path,"/libraries/__LIBRARY__.yml")).data) - @triggers('project.import.resources') + @set_triggers('project.import.resources') def __init_import_resources(self): + anl.phase('导入资源') logger.info(t('project.import.resources')) - self.resources.load_resources(f'{self.base_path}{PATH_SEP}Resources') + self.__env['resources'].update(Loader.load_resources( + fmtpath(self.base_path,'/resources'))) - @triggers('project.import.pages') + @set_triggers('project.import.pages') def __init_import_pages(self): + anl.phase('导入页面') logger.info(t('project.import.pages')) - for pagefile in Dire(f'{self.base_path}{PATH_SEP}Pages').scan(recur=True): + for pagefile in Dire(fmtpath(self.base_path,'/pages')).scan(recur=True): self.import_page_from_file(pagefile) - - def get_all_card(self) -> list: - """获取工程里的全部卡片""" - return self.base_library.get_all_cards() - - def get_all_pagename(self) -> list: - """获取工程里的全部页面名""" - return self.pagelist - @trigger_invoke('project.loading') - @trigger_return('project.loaded') - def __init__(self, path): + @set_triggers('project.load') + def __init__(self,builder, path): + anl.phase('初始化仓库类') logger.info(t('project.init')) - self.base_library = None - self.base_path = None - self.default_page = None - self.version = None - envpath = os.path.dirname(os.path.dirname(__file__)) - self.resources = Resource() - logger.info(t('project.load.basic_res')) - self.resources.load_resources(f'{envpath}{PATH_SEP}Resources') - logger.info(t('project.load.plugins')) - self.load_plugins(f'{envpath}{PATH_SEP}Plugin') - logger.info(t('project.load.modules')) - load_module_dire(f'{envpath}{PATH_SEP}Modules') - self.pages:Dict[PageBase] = {} - self.pagelist = [] - self.import_pack(path) - self.checkModuleWaitList() - self.template_manager = TemplateManager(self) + super().__init__(builder=builder,path=path) logger.info(t('project.load.success')) + anl.pause() def import_page_from_file(self, page_file: File): """导入页面""" file_name = page_file.name file_exten = page_file.extention if file_exten == 'yml': - page = CardStackPage(page_file,self) + page = CardStackPage(page_file) self.__import_card_stack_page(page) elif file_exten == 'xaml': - page = RawXamlPage(page_file,self) + page = RawXamlPage(page_file) else: logger.warning(f'Page file not supported: {file_name}.{file_exten}') return self.pages[file_name] = page self.pagelist.append(file_name) - + def __import_card_stack_page(self,page:CardStackPage): if page.name: self.pages[page.name] = page @@ -126,6 +131,7 @@ def __import_card_stack_page(self,page:CardStackPage): for alias in page.alias: self.pages[alias] = page + @set_triggers('project.genxaml') def get_page_xaml(self, page_alias, no_not_found_err_logging = False, setter = None): """获取页面 xaml 代码""" logger.info(t('project.gen_page.start', page=page_alias, args=setter)) @@ -133,7 +139,11 @@ def get_page_xaml(self, page_alias, no_not_found_err_logging = False, setter = N if not no_not_found_err_logging: logger.error(t('project.gen_page.failed.notfound', page=page_alias)) raise PageNotFoundError(page_alias) - return self.pages[page_alias].generate(setter = setter) + env = self.get_environment_copy() + env.update(setter=setter) + env.update(used_resources=set()) + xaml = self.pages[page_alias].generate(env = env) + return xaml def get_page_content_type(self, page_alias, no_not_found_err_logging = False, setter = None): if page_alias not in self.pages: @@ -149,6 +159,10 @@ def get_page_displayname(self, page_alias): raise PageNotFoundError(t('page.not_found',page = page_alias)) return page.display_name - + def set_env_data(self,key,value): + self.__env['data'][key] = value + + def get_env_data(self,key): + return self.__env['data'].get(key) class PageNotFoundError(Exception): """页面未找到错误""" diff --git a/src/homepagebuilder/core/resource.py b/src/homepagebuilder/core/resource.py new file mode 100644 index 0000000..8c77a18 --- /dev/null +++ b/src/homepagebuilder/core/resource.py @@ -0,0 +1,176 @@ +''' +资源结构模块 +''' +import re +from abc import ABC, abstractmethod +from typing import Dict, Annotated, List, Union, Set, TYPE_CHECKING +from .io import File +from .logger import Logger +from .utils.checking import is_xaml, is_yaml +from .i18n import locale +from .utils.funcs import transform +import xml.etree.ElementTree as ET + +if TYPE_CHECKING: + from .types import BuildingEnvironment + +logger = Logger('Resource') +YML_PATTERN = re.compile(r'.*\.yml$') +XAML_PATTERN = re.compile(r'.*\.xaml$') +PY_PATTERN = re.compile(r'.*\.py$') + +XML_ROOT = ''' + +{chindren} + +''' + +class ResourceLoader: + + @classmethod + def loadfiles(cls,files): + output = {} + for file in files: + resources = cls.loadfile(file) + for _key,resource in resources.items(): + output[resource.key] = resource + return output + + @classmethod + def loadfile(cls,file:File) -> List['Resource']: + logger.debug(locale('resourceloader.load.resourcefile',name=file.name)) + if is_xaml(file): + return cls.__loadxamlfile(file) + elif is_yaml(file): + return cls.__loadyamlfile(file) + else: + raise ValueError() + + @classmethod + def __loadyamlfile(cls,file:Annotated[File,is_yaml]): + """加载yaml资源文件""" + output = {} + data:Dict = file.data + if styles := data.get('Styles'): + for style in styles: + res = StyleResource(style) + output[res.key] = res + logger.debug(locale('resourceloader.load.regist',name=res.key,type_name=res.type)) + return output + + @classmethod + def __loadxamlfile(cls,file:Annotated[File,is_xaml]): + """加载xaml资源文件""" + output = {} + data = XML_ROOT.replace('{chindren}', file.data) + root = ET.fromstring(data) + for element in root: + res = XamlResource(element) + output[res.key] = res + logger.debug(locale('resourceloader.load.regist',name=res.key,type_name=res.type)) + return output + +class Resource(ABC): + """资源基类""" + key:str + basedon: Union[str|None] + is_default: bool + + @abstractmethod + def getxaml(self): + "获取xaml" + + @property + @abstractmethod + def type(self): + """资源类型""" + +class StyleResource(Resource): + setters: Dict[str,object] + is_default: bool + + def __init__(self,style_dict): + super().__init__() + self.setters = {} + self.target = style_dict.get('Target') + self.basedon = style_dict.get('BasedOn') + if key := style_dict.get('Key'): + self.is_default = False + self.key = key + else: + self.is_default = True + self.key = f'Default/{self.target}' + if setters := style_dict.get('Setters'): + for property_name,value in setters.items(): + self.setters[property_name] = value + + @property + def type(self): + return 'Style' + + def getxaml(self): + xaml = '\n' + return xaml + + +NAMESPACES = { + 'sys':'clr-namespace:System;assembly=mscorlib', + 'x':'http://schemas.microsoft.com/winfx/2006/xaml', + 'local': 'clr-namespace:PCL;assembly=Plain Craft Launcher 2', +} +NAMESPACES_T = transform(NAMESPACES) + +class XamlResource(Resource): + is_default: bool + + def __init__(self,element:ET.Element): + self.target = element.get('TargetType') + self.basedon = element.get('BasedOn') + self.__type = element.tag + if key := element.get(f"{{{NAMESPACES['x']}}}Key"): + self.is_default = False + self.key = key + else: + self.is_default = True + if self.target: + self.key = f'Default/{self.target}' + else: + raise ValueError() + self.xaml = ET.tostring(element, encoding='unicode',xml_declaration = False) + self.xaml = XamlResource.shorten(string=self.xaml) + + @classmethod + def shorten(cls,string): + if ms := re.findall(r'xmlns:ns(\d)=\"([^\"]*)\"',string): + for ns_index,ns_def in ms: + ns_name = NAMESPACES_T[ns_def] + string = string.replace(f'ns{ns_index}:',ns_name +':') + string = string.replace(f'xmlns:ns{ns_index}="{ns_def}"','') + return string + + @property + def type(self): + return self.__type + + def getxaml(self): + return self.xaml + +def get_resources_code(env:'BuildingEnvironment'): + resset:Set[Resource] = set(env['resources'][res] for res in env['used_resources']) + for usedres in env['used_resources']: + if baseon := env['resources'][usedres].basedon: + resset.add(env['resources'][baseon]) + for _k,res in env['resources'].items(): + if res.is_default: + resset.add(res) + return ''.join([res.getxaml() for res in resset]) diff --git a/Core/styles.py b/src/homepagebuilder/core/styles.py similarity index 90% rename from Core/styles.py rename to src/homepagebuilder/core/styles.py index ac069aa..5f9e16b 100644 --- a/Core/styles.py +++ b/src/homepagebuilder/core/styles.py @@ -4,8 +4,9 @@ from typing import Dict -def get_style_code(styles:Dict[str,object]) -> str: +def get_style_code(env) -> str: '''获取样式代码''' + styles:Dict[str,object] = env.get('styles') xaml = '' for item in styles.values(): if isinstance(item,str): diff --git a/Core/templates_manager.py b/src/homepagebuilder/core/templates_manager.py similarity index 59% rename from Core/templates_manager.py rename to src/homepagebuilder/core/templates_manager.py index df3cac1..938b1d2 100644 --- a/Core/templates_manager.py +++ b/src/homepagebuilder/core/templates_manager.py @@ -3,13 +3,17 @@ """ import traceback from queue import Queue -from typing import List, Union -from .utils.formatter import format_code -from .ModuleManager import invoke_script -from .library import Library +from typing import TYPE_CHECKING +from .formatter import format_code, PropNotFormatedError +from .module_manager import invoke_script from .logger import Logger -from .utils.event import trigger_invoke, trigger_return -from .utils import PropertySetter +from .utils.event import set_triggers +from .utils.property import PropertySetter +from .config import is_debugging + +if TYPE_CHECKING: + from typing import List, Union + from .types import BuildingEnvironment logger = Logger('Template') @@ -30,6 +34,8 @@ def __is_filter_value_match(rule:str,value:str): def filter_match(template,card): '''检测卡片是否符合模版筛选规则''' + if not template: + return False if 'filter' not in template: return True if template['filter'] == 'never': @@ -51,14 +57,9 @@ def filter_match(template,card): raise TypeError() return True -class TemplateManager: +class TemplateManager(): '''模版管理器类''' - def __init__(self,project): - self.project = project - self.resources = project.resources - self.templates = self.resources.templates - - def expend_card_placeholders(self,card:dict,children_code): + def expend_card_placeholders(self,card:dict,children_code,env): '''展开卡片属性内所有占位符''' q = Queue() tries = 0 @@ -66,42 +67,44 @@ def expend_card_placeholders(self,card:dict,children_code): q.put(key) while not q.empty(): if tries > q.qsize(): - logger.warning(f"检测到卡片中 {'、'.join(q.queue)} 属性无法被展开,跳过") + if is_debugging(): + raise ValueError(f"检测到卡片中 {'、'.join(q.queue)} 属性无法被展开") + else: + logger.warning(f"检测到卡片中 {'、'.join(q.queue)} 属性无法被展开,跳过") break key = q.get() try: - card[key] = format_code(card[key],card,self.project,children_code) + card[key] = format_code(card[key],card,env=env,children_code=children_code) tries = 0 - except KeyError: + except PropNotFormatedError: q.put(key) tries += 1 continue return card - def build_with_template(self,card,template_name,children_code) -> str: + def build_with_template(self,card,template_name,children_code,env:'BuildingEnvironment') -> str: '''使用指定模版构建卡片''' if (not template_name) or template_name == 'void': return children_code - target_template = self.templates[template_name] + target_template = env.get('templates')[template_name] code = '' card = PropertySetter(target_template.get('fill'),target_template.get('cover')).decorate(card) - card = self.expend_card_placeholders(card,children_code) + card = self.expend_card_placeholders(card,children_code,env) for cpn in target_template['components']: - cpn = format_code(cpn,card,self.project,'') - if cpn in self.resources.components: - code += format_code(code = self.resources.components[cpn], card = card, - project=self.project, children_code = children_code) + cpn = format_code(cpn,card,env,'') + if cpnobj := env.get('components').get(cpn): + code += cpnobj.toxaml(card,env,children_code) elif cpn.startswith('$') or cpn.startswith('@'): args = cpn[1:].split('|') - code += invoke_script(args[0],self.project,card,args[1:],children_code=children_code) - else: + code += invoke_script(args[0],env=env,card=card,args=args[1:],children_code=children_code) + elif not cpn == '': logger.warning(f'{template_name}模版中调用了未载入的构件{cpn},跳过') if 'containers' in target_template: tree_path = target_template['containers'] - code = self.packin_containers(tree_path,card,code) - return self.build_with_template(card,target_template.get('base'),code) + code = self.packin_containers(tree_path,card,code,env) + return self.build_with_template(card,target_template.get('base'),code,env) - def packin_containers(self,tree_path:Union[str,List[str]],card,code:str): + def packin_containers(self,tree_path:'Union[str,List[str]]',card,code:str,env:'BuildingEnvironment'): '''按照容器组件路径包装''' containers:list = [] if isinstance(tree_path,str): @@ -119,39 +122,43 @@ def packin_containers(self,tree_path:Union[str,List[str]],card,code:str): case 'base': break case _: - if container in self.resources.components: - current_code = format_code(self.resources.components[container], - card,self.project,current_code) + if container in env.get('components'): + current_code = format_code(env.get('components')[container], + card,env.get('project'),current_code) else: raise ValueError('容器路径中存在不存在的组件') return current_code - @trigger_invoke('card.building') - @trigger_return('card.builded') - def build(self,card): + @set_triggers('tm.buildcard') + def build(self,card,env:'BuildingEnvironment'): '''构建卡片''' - def try_build(self,card,template): + def try_build(self,card,template,env:'BuildingEnvironment'): try: - return self.build_with_template(card,template,'') - except Exception: - logger.warning(f'构建卡片时出现错误:\n{traceback.format_exc()}Skipped.') - return '' + return self.build_with_template(card,template,'',env) + except Exception as ex: + if is_debugging(): + raise ex + else: + logger.warning(f'构建卡片时出现错误:\n{traceback.format_exc()}Skipped.') + return '' attr = card['templates'] + templates = env.get('templates') if isinstance(attr,str): - template = self.resources.templates[attr] + template = templates[attr] if filter_match(template,card): - return try_build(self,card,attr) + return try_build(self,card,attr,env) else: logger.warning('卡片与其配置的模版要求不符,跳过') return '' elif isinstance(attr,list): for template_name in card['templates']: - if template_name not in self.resources.templates: - continue - if filter_match(self.resources.templates[template_name],card): - return try_build(self,card,template_name) - logger.warning('卡片没有匹配的配置模版,跳过') + if filter_match(templates.get(template_name),card): + return try_build(self,card,template_name,env) + if is_debugging(): + raise ValueError('卡片没有匹配的配置模版') + else: + logger.warning('卡片没有匹配的配置模版,跳过') return '' else: logger.warning('[TemplateManager] 模版列表类型无效,跳过') diff --git a/src/homepagebuilder/core/types/__init__.py b/src/homepagebuilder/core/types/__init__.py new file mode 100644 index 0000000..efb3eb7 --- /dev/null +++ b/src/homepagebuilder/core/types/__init__.py @@ -0,0 +1,3 @@ +from .builder import Builder +from .environment import BuildingEnvironment +from .project import Project \ No newline at end of file diff --git a/src/homepagebuilder/core/types/builder.py b/src/homepagebuilder/core/types/builder.py new file mode 100644 index 0000000..a004faf --- /dev/null +++ b/src/homepagebuilder/core/types/builder.py @@ -0,0 +1,39 @@ +from abc import ABC,abstractmethod +from typing import TYPE_CHECKING +from .environment import BuildingEnvironment + +if TYPE_CHECKING: + from . import Project + from ...core.resource import Resource + from ...core.templates_manager import TemplateManager + +class Builder(ABC): + """构建器核心""" + def __init__(self) -> None: + self.envpath: str + self.resources: Resource + self.template_manager: TemplateManager + self.current_project: Project + self.__env:BuildingEnvironment = BuildingEnvironment( + builder=self + ) + + @abstractmethod + def load_resources(self,dire_path) -> None: + """加载构建器资源""" + + @abstractmethod + def load_modules(self,dire_path) -> None: + """加载构建器模块""" + + @abstractmethod + def load_plugins(self, plugin_path) -> None: + """加载构建器插件""" + + @abstractmethod + def load_proejct(self,project_path) -> None: + """加载工程""" + + def get_environment_copy(self) -> 'BuildingEnvironment': + """获取环境拷贝""" + return self.__env.copy() \ No newline at end of file diff --git a/src/homepagebuilder/core/types/building_environment.py b/src/homepagebuilder/core/types/building_environment.py new file mode 100644 index 0000000..c65254d --- /dev/null +++ b/src/homepagebuilder/core/types/building_environment.py @@ -0,0 +1,8 @@ +from typing import TypedDict +from .builder import Builder +from .builder import Project + +class BuildingEnvironment(TypedDict): + """构建时环境""" + builder: Builder + project: Project diff --git a/src/homepagebuilder/core/types/environment.py b/src/homepagebuilder/core/types/environment.py new file mode 100644 index 0000000..39e5e38 --- /dev/null +++ b/src/homepagebuilder/core/types/environment.py @@ -0,0 +1,21 @@ +from typing import Dict, Set, TypedDict, TYPE_CHECKING +from ..utils.property import PropertySetter +from ..resource import Resource + +if TYPE_CHECKING: + from . import Builder, Project + from ...core.elements import Component + +class BuildingEnvironment(TypedDict): + """构建时环境""" + builder: 'Builder' + """环境构建器""" + project: 'Project' + """所在工程""" + resources: Dict[str,Resource] + """工程资源""" + components: Dict[str,'Component'] + data: Dict[str,object] + page_templates: Dict[str,str] + setter: PropertySetter + used_resources: Set[str] = set() diff --git a/src/homepagebuilder/core/types/project.py b/src/homepagebuilder/core/types/project.py new file mode 100644 index 0000000..09c3f4a --- /dev/null +++ b/src/homepagebuilder/core/types/project.py @@ -0,0 +1,56 @@ +from abc import ABC,abstractmethod +from typing import Dict, List, TYPE_CHECKING + + +if TYPE_CHECKING: + from . import Builder, BuildingEnvironment + from ..resource import Resource + from ..library import Library + from ..io import File + from ..page import PageBase + +class Project(ABC): + """工程类接口""" + @abstractmethod + def import_pack(self, path:str) -> None: + """导入工程包""" + + def get_all_card(self) -> list: + """获取工程里的全部卡片""" + return self.base_library.get_all_cards() + + def get_all_pagename(self) -> list: + """获取工程里的全部页面名""" + return self.pagelist + + def __init__(self,builder, path:str): + self.builder:Builder = builder + self.__env:'BuildingEnvironment' = builder.get_environment_copy() + self.__env['project'] = self + self.base_library:Library = None + self.base_path:str = None + self.default_page:PageBase = None + self.version:str = None + self.pages:Dict[str,PageBase] = {} + self.pagelist:List[PageBase] = [] + self.import_pack(path) + + @abstractmethod + def import_page_from_file(self, page_file: 'File') -> None: + """导入页面""" + + @abstractmethod + def get_page_xaml(self, page_alias, no_not_found_err_logging = False, setter = None) -> str: + """获取页面 xaml 代码""" + + @abstractmethod + def get_page_content_type(self, page_alias, no_not_found_err_logging = False, setter = None) -> str: + """获取页面返回类型""" + + @abstractmethod + def get_page_displayname(self, page_alias) -> str: + """获取页面显示名""" + + def get_environment_copy(self) -> 'BuildingEnvironment': + """获取环境拷贝""" + return self.__env.copy() \ No newline at end of file diff --git a/src/homepagebuilder/core/utils/__init__.py b/src/homepagebuilder/core/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/homepagebuilder/core/utils/checking.py b/src/homepagebuilder/core/utils/checking.py new file mode 100644 index 0000000..19fb984 --- /dev/null +++ b/src/homepagebuilder/core/utils/checking.py @@ -0,0 +1,11 @@ +"""类型检查限制模块""" + +from ..io import File + +def is_xaml(file:File) -> bool: + "判断文件是否为Xaml" + return file.extention == 'xaml' + +def is_yaml(file:File) -> bool: + "判断文件是否为Yaml" + return file.extention in ['yml','yaml'] diff --git a/Core/utils/decos.py b/src/homepagebuilder/core/utils/decos.py similarity index 100% rename from Core/utils/decos.py rename to src/homepagebuilder/core/utils/decos.py diff --git a/Core/encode.py b/src/homepagebuilder/core/utils/encode.py similarity index 100% rename from Core/encode.py rename to src/homepagebuilder/core/utils/encode.py diff --git a/src/homepagebuilder/core/utils/event.py b/src/homepagebuilder/core/utils/event.py new file mode 100644 index 0000000..e8c84a5 --- /dev/null +++ b/src/homepagebuilder/core/utils/event.py @@ -0,0 +1,50 @@ +import functools +from typing import Dict, List +from ..logger import Logger +# from ..i18n import locale + +events:Dict[str,List[callable]] = {} +logger = Logger('Event') + +def set_triggers(event_name:str): + '''在函数开始时、返回、和出错时触发事件''' + def wrapper(func): + @functools.wraps(func) + def function_triggers(*args,**kwagrs): + try: + trigger_event(event_name + '.start',*args,**kwagrs) + result = func(*args,**kwagrs) + trigger_event(event_name + '.return',*args ,result = result, **kwagrs) + return result + except ResultOverride as ro: + return ro.result + except Exception as ex: + try: + trigger_event(event_name + '.failed', *args,exception = ex, **kwagrs) + except ResultOverride as e: + return e.result + finally: + raise ex + return function_triggers + return wrapper + +def listen_event(event_name:str): + '''监听事件''' + def wrapper(func): + if event_name not in events: + events[event_name] = [] + events[event_name].append(func) + return wrapper + +def trigger_event(event_name:str,*args,**kwargs): + #logger.event(locale('event.triggered',event_name=event_name)) + actions_list = events.get(event_name) + if not actions_list: + return + for action in actions_list: + action(*args,**kwargs) + +class ResultOverride(Exception): + """在事件中抛出该异常以停止原函数以及尚未执行的触发器运行并输出结果""" + def __init__(self, result): + self.result = result \ No newline at end of file diff --git a/src/homepagebuilder/core/utils/finder.py b/src/homepagebuilder/core/utils/finder.py new file mode 100644 index 0000000..8292f5f --- /dev/null +++ b/src/homepagebuilder/core/utils/finder.py @@ -0,0 +1,14 @@ +import re +from typing import List +from ..io import File + +RESOURCE_PATTERN = re.compile(r'\"\s*{\s*StaticResource\s+([^\s]*)\s*}\s*\"') +#RESOURCE_PATTERN = re.compile(r'Style\s*=\s*\"\s*{\s*StaticResource\s+([^\s]*)\s*}\s*\"') + +def find_using_resources(target) -> List[str]: + if isinstance(target,File): + return RESOURCE_PATTERN.findall(target.data) + elif isinstance(target,str): + return RESOURCE_PATTERN.findall(target) + else: + raise TypeError() diff --git a/src/homepagebuilder/core/utils/funcs.py b/src/homepagebuilder/core/utils/funcs.py new file mode 100644 index 0000000..cd5f052 --- /dev/null +++ b/src/homepagebuilder/core/utils/funcs.py @@ -0,0 +1,6 @@ +from typing import Dict +def transform(original_dict:Dict): + newdict = {} + for k,v in original_dict.items(): + newdict[v] = k + return newdict \ No newline at end of file diff --git a/src/homepagebuilder/core/utils/paths.py b/src/homepagebuilder/core/utils/paths.py new file mode 100644 index 0000000..b7f1c99 --- /dev/null +++ b/src/homepagebuilder/core/utils/paths.py @@ -0,0 +1,15 @@ +import os + +dn = os.path.dirname + +ENV_PATH = dn(dn(dn(__file__))) + +def fmtpath(*paths) -> str: + result = '' + for path in paths: + result += path.replace('/',os.sep) + return result + +def getbuilderpath(path:str): + path = fmtpath(path) + return ENV_PATH + os.sep + path \ No newline at end of file diff --git a/Core/utils/property.py b/src/homepagebuilder/core/utils/property.py similarity index 80% rename from Core/utils/property.py rename to src/homepagebuilder/core/utils/property.py index d185067..21e413f 100644 --- a/Core/utils/property.py +++ b/src/homepagebuilder/core/utils/property.py @@ -3,14 +3,14 @@ class ReadOnlySetterException(Exception): pass class PropertySetter(): - def __init__(self,fill = None,override = None): + def __init__(self,fill = None,override = None, frozen = True): self.fill:Dict = dict(fill) if fill else {} self.override:Dict = dict(override) if override else {} - self.frozen = True + self.__frozen = frozen def attach(self,sticker_setter:Union['PropertySetter', None]): """向 Setter 上附上另一层 Setter""" - if self.frozen: + if self.__frozen: raise ReadOnlySetterException() if not sticker_setter: return @@ -21,7 +21,7 @@ def attach(self,sticker_setter:Union['PropertySetter', None]): def clone(self): new_setter = PropertySetter(self.fill, self.override) - new_setter.frozen = False + new_setter.__frozen = False return new_setter def toProperties(self): @@ -35,6 +35,16 @@ def decorate(self,property:Dict): new_property.update(self.override) return new_property + def froze(self): + self.__frozen = True + + @property + def isfrozen(self): + return self.__frozen + + def __len__(self): + return len(self.fill) + len(self.override) + @classmethod def fromargs(cls,args:List[str]): override = {} diff --git a/src/homepagebuilder/debug/__init__.py b/src/homepagebuilder/debug/__init__.py new file mode 100644 index 0000000..ca948ca --- /dev/null +++ b/src/homepagebuilder/debug/__init__.py @@ -0,0 +1,3 @@ +from .timer import count_time +from .analyzer import global_anlyzer +from .eventbreakpoints import addbp \ No newline at end of file diff --git a/src/homepagebuilder/debug/analyzer.py b/src/homepagebuilder/debug/analyzer.py new file mode 100644 index 0000000..64d0c50 --- /dev/null +++ b/src/homepagebuilder/debug/analyzer.py @@ -0,0 +1,178 @@ + +import time +from typing import List + +ANLDEBUGGING = False + +def repeat(string,times:int): + s = '' + for _ in range(0,times): + s += string + return s + + +class Phase(): + def __init__(self,name): + self.name = name + self.subphases:List['Phase'] = [] + self.__timespan = 0 + self.__start_time = None + self.__end_time = None + self.__pasued = False + self.__ancestor = None + self.__current_subphase = None + + @property + def ancesotr(self): + return self.__ancestor + + @property + def timespan(self): + if not self.__start_time: + raise PhaseNotStartedError(phase = self) + return self.__timespan + + @property + def timespan_ms(self): + return round(self.__timespan*1000) + + def start_new_subphase(self,name:str): + if self.__current_subphase: + self.__current_subphase.stop() + subphase = Phase(name) + subphase.__set_ancestor(self) + self.__current_subphase = subphase + self.subphases.append(subphase) + subphase.start() + return subphase + + def __set_ancestor(self,ancesotr:'Phase'): + self.__ancestor = ancesotr + + @property + def last_subphase(self): + if len(self.subphases) <= 0: + return None + return self.subphases[-1] + + @property + def start_time(self): + return self.__start_time + + @property + def end_time(self): + return self.__end_time + + def start(self): + self.__checklock() + if self.__start_time: + raise PhaseStartedError(phase = self) + self.__start_time = time.time() + + def pasue(self): + if self.__pasued: + return + self.__timespan += self.getspan() + self.__pasued = True + + def resume(self): + if not self.__pasued: + return + self.__start_time = time.time() + + def stop(self): + if self.__current_subphase: + self.__current_subphase.stop() + if self.is_ended(): + return + self.__end_time = time.time() + if not self.__pasued: + self.__timespan += self.getspan(self.__end_time) + + def is_ended(self): + return bool(self.__end_time) + + def __checklock(self): + if self.__end_time: + raise PhaseEndedError(phase = self) + + def getspan(self, curtime = None): + if not curtime: + curtime = time.time() + return curtime - self.__start_time + +class PhaseNotStartedError(Exception): + pass + +class PhaseStartedError(Exception): + pass + +class PhaseEndedError(Exception): + pass + +class Analyzer(): + def __init__(self, disabled = False) -> None: + self.__start_time = time.time() + self.mainphase = Phase('Main') + self.ancesotrphase = self.mainphase + self.currentphase = None + self.mainphase.start() + self.__disabled = disabled + + def switch_in(self): + if self.__disabled: + return + if not self.currentphase: + return + self.ancesotrphase = self.currentphase + self.currentphase = None + + def switch_out(self): + if self.__disabled: + return + if not self.currentphase.ancesotr: + raise ReferenceError() + if not self.currentphase.is_ended(): + self.currentphase.stop() + self.currentphase = self.ancesotrphase + self.ancesotrphase = self.currentphase.ancesotr + + def phase(self,name) -> Phase: + '''阶段''' + if self.__disabled: + return None + self.currentphase = self.ancesotrphase.start_new_subphase(name) + + def stop(self): + if self.mainphase and not self.mainphase.is_ended(): + self.mainphase.stop() + + def pause(self): + if self.currentphase and not self.currentphase.is_ended(): + self.currentphase.pasue() + + def get_total_time(self): + return self.mainphase.getspan() + + def __print_phase(self,phase,total_time,deepth:int): + print(f'{repeat(" ",deepth)}{phase.name}: {phase.timespan_ms}ms {round(phase.timespan * 100 / total_time,2)}%') + self.__print_subphases(phase,total_time,deepth) + + def __print_subphases(self,phase,total_time,deepth:int): + for subphase in phase.subphases: + self.__print_phase(subphase,total_time,deepth+1) + + def summarize(self): + total_time = self.get_total_time() + print(repeat('=',26)) + print('TIME COUSUMING SUMMERY:') + print(repeat('-',26)) + self.__print_subphases(self.mainphase,total_time,-1) + print(repeat('-',26)) + print(f'TOTAL: {round(self.mainphase.timespan,4)}s {round(self.mainphase.timespan * 100 / total_time,4)}%') + print(repeat('=',26)) + + def disable(self): + self.__disabled = True + +global_anlyzer = Analyzer(True) \ No newline at end of file diff --git a/src/homepagebuilder/debug/eventbreakpoints.py b/src/homepagebuilder/debug/eventbreakpoints.py new file mode 100644 index 0000000..8376542 --- /dev/null +++ b/src/homepagebuilder/debug/eventbreakpoints.py @@ -0,0 +1,11 @@ +from ..core.utils.event import listen_event +from ..core.config import is_debugging + +breakpoints = [] + +if is_debugging(): + for bp in breakpoints: + listen_event(bp)(lambda *_args,**_kwargs: breakpoint()) + +def addbp(eventname): + breakpoints.append(eventname) \ No newline at end of file diff --git a/Debug/timer.py b/src/homepagebuilder/debug/timer.py similarity index 89% rename from Debug/timer.py rename to src/homepagebuilder/debug/timer.py index 3e32c63..4e1d880 100644 --- a/Debug/timer.py +++ b/src/homepagebuilder/debug/timer.py @@ -1,7 +1,7 @@ import time -from Core.i18n import locale -from Core.logger import Logger -from Core.config import config +from ..core.i18n import locale +from ..core.logger import Logger +from ..core.config import config logger = Logger('Timer') count = 0 diff --git a/src/homepagebuilder/interfaces/Events/__init__.py b/src/homepagebuilder/interfaces/Events/__init__.py new file mode 100644 index 0000000..a0c6406 --- /dev/null +++ b/src/homepagebuilder/interfaces/Events/__init__.py @@ -0,0 +1 @@ +from ...core.utils.event import set_triggers, listen_event as on , ResultOverride \ No newline at end of file diff --git a/src/homepagebuilder/interfaces/__init__.py b/src/homepagebuilder/interfaces/__init__.py new file mode 100644 index 0000000..82a7107 --- /dev/null +++ b/src/homepagebuilder/interfaces/__init__.py @@ -0,0 +1,10 @@ +from ..core.module_manager import script,invoke_module as invoke, require +from ..core.module_manager.page import page_class_handles +from ..core.io import file_reader, file_writer, read_string, write_string +from ..core.logger import Logger +from ..core.utils.encode import encode_escape, decode_escape +from ..core.formatter import format_code, get_card_prop +from ..core.i18n import locale +from ..core.utils.decos import enable_by +from ..core.page import PageBase, FileBasedPage +from ..core.config import enable_by_config, DisabledByConfig, config diff --git a/main.py b/src/homepagebuilder/main.py similarity index 59% rename from main.py rename to src/homepagebuilder/main.py index 645e77c..68f6732 100644 --- a/main.py +++ b/src/homepagebuilder/main.py @@ -1,10 +1,11 @@ """构建器主入口""" import argparse +import os from os import makedirs from os.path import sep, exists -from Core.project import Project -from Core import config - +from .core.builder import Builder +from .core.config import init_full +from .debug import global_anlyzer as anl def build_and_output(project, page, output_path): xaml = project.get_page_xaml(page_alias=page) @@ -14,33 +15,37 @@ def build_and_output(project, page, output_path): def command_build(args): - project = Project(args.project_file_path) + builder = Builder() + builder.load_proejct(args.project) output_path = args.output_path + anl.phase('构建页面') + anl.switch_in() if args.all_page: if not exists(args.output_path): makedirs(args.output_path, exist_ok=True) - for page in project.get_all_pagename(): + for page in builder.current_project.get_all_pagename(): if not args.output_path.endswith(sep): output_path = output_path + sep if args.dry_run: page_output_path = None else: page_output_path = f"{output_path}{page}.xaml" - build_and_output(project, page, page_output_path) + build_and_output(builder.current_project, page, page_output_path) else: page = args.page if not page: - page = project.default_page + page = builder.current_project.default_page if args.dry_run: page_output_path = None else: page_output_path = f"{output_path}" - build_and_output(project, page, page_output_path) - + build_and_output(builder.current_project, page, page_output_path) + anl.stop() + anl.summarize() def command_server(args): - from Server.main import Server - server = Server(args.project_path) + from .server.main import Server + server = Server(args.project) server.run(args.port if args.port else 6608) @@ -51,18 +56,24 @@ def command_server(args): def main(): """构建器主入口""" + anl.phase('初始化构建器配置') + init_full() parser = argparse.ArgumentParser() subparsers = parser.add_subparsers(help='Command', dest='command') parser_build = subparsers.add_parser('build', help='Build homepage') parser_server = subparsers.add_parser('server', help='Start server') - - parser_build.add_argument('project_file_path', type=str, help='project file path') - parser_build.add_argument('output_path', type=str, help='generated file dest') + parser_build.add_argument('--project', type=str, + default=os.getcwd() + os.path.sep + 'Project.yml', + help='project file path') + parser_build.add_argument('--output-path', type=str, + default= os.getcwd() + os.path.sep + 'output.xaml', + help='generated file dest') parser_build.add_argument('-p', '--page', type=str, help='page name') parser_build.add_argument('-a', '--all-page', action='store_true', help='generate all page') parser_build.add_argument('--dry-run', action='store_true', help='dry run') - - parser_server.add_argument('project_path', type=str, help='project path') + parser_server.add_argument('--project', type=str, + default=os.getcwd() + os.path.sep + 'Project.yml', + help='project file path') parser_server.add_argument('-p', '--port', type=str, help='project path') args = parser.parse_args() @@ -70,5 +81,4 @@ def main(): if __name__ == '__main__': - config.fully_init() main() diff --git a/Modules/Category.py b/src/homepagebuilder/modules/Category.py similarity index 75% rename from Modules/Category.py rename to src/homepagebuilder/modules/Category.py index c8a55a3..0be25c9 100644 --- a/Modules/Category.py +++ b/src/homepagebuilder/modules/Category.py @@ -1,5 +1,5 @@ -from Core.project import Project -from Interfaces import script +from homepagebuilder.core.types.project import Project +from homepagebuilder.interfaces import script @script('Category') def cats(cat_name,proj:Project,**kwargs): diff --git a/src/homepagebuilder/modules/ExistCard.py b/src/homepagebuilder/modules/ExistCard.py new file mode 100644 index 0000000..8dad665 --- /dev/null +++ b/src/homepagebuilder/modules/ExistCard.py @@ -0,0 +1,8 @@ +from homepagebuilder.core.types.project import Project +from homepagebuilder.interfaces import script, format_code + +@script('ExistCard') +def exist_card(card_name,env,card,**kwargs): + card_name = format_code(card_name,data=card,env=env,children_code='',err_output='') + return (card_name in env.get('project').base_library.card_mapping.keys()) \ + or (card_name in env.get('project').base_library.cards.keys()) diff --git a/Modules/ForEach.py b/src/homepagebuilder/modules/ForEach.py similarity index 60% rename from Modules/ForEach.py rename to src/homepagebuilder/modules/ForEach.py index 445d489..0c98f10 100644 --- a/Modules/ForEach.py +++ b/src/homepagebuilder/modules/ForEach.py @@ -1,13 +1,13 @@ -from Interfaces import script,format_code, get_card_prop +from homepagebuilder.interfaces import script,format_code, get_card_prop @script('ForEach') def for_each_script(store_name,iter_item_name,itemoutput,**kwargs): - project = kwargs['proj'] + env = kwargs['env'] card = kwargs['card'] iter_item = get_card_prop(card=card,attr_name=iter_item_name) code = '' for item in iter_item: card_copy = card.copy() card_copy[store_name] = item - code += format_code(itemoutput,card=card_copy,project=project,children_code='',err_output='false') + code += format_code(itemoutput,data=card_copy,env=env,children_code='',err_output='false') return code \ No newline at end of file diff --git a/src/homepagebuilder/modules/Global.py b/src/homepagebuilder/modules/Global.py new file mode 100644 index 0000000..0032cee --- /dev/null +++ b/src/homepagebuilder/modules/Global.py @@ -0,0 +1,5 @@ +from homepagebuilder.interfaces import script + +@script('Global') +def global_vers(key,env,**kwarg): + return env['data'].get(key) \ No newline at end of file diff --git a/src/homepagebuilder/modules/IF.py b/src/homepagebuilder/modules/IF.py new file mode 100644 index 0000000..1bf9204 --- /dev/null +++ b/src/homepagebuilder/modules/IF.py @@ -0,0 +1,27 @@ +from homepagebuilder.interfaces import script, format_code + +@script('IF') +def if_script(eq_expression:str,true_return,false_return='',**kwargs): + env = kwargs['env'] + card = kwargs['card'] + eq_expression.replace(' ','') + return true_return if iseq(eq_expression,card,env) else false_return + +def iseq(eq_expression,card,env) -> bool: + if '=' in eq_expression: + eqexp_left, eqexp_right = eq_expression.split('=',1) + eqexp_left = format_eq(eqexp_left,card,env) + eqexp_right = format_eq(eqexp_right,card,env) + if eqexp_left == eqexp_right: + return True + else: + if eq_expression.startswith('!'): + if format_eq(eq_expression[1:],card,env).lower() in ['false','null','none']: + return True + else: + if format_eq(eq_expression,card,env).lower() not in ['false','null','none']: + return True + return False + +def format_eq(expression:str,card,env): + return format_code(expression,data=card,env=env,children_code='',err_output='false') diff --git a/src/homepagebuilder/modules/Presenters.py b/src/homepagebuilder/modules/Presenters.py new file mode 100644 index 0000000..e36be79 --- /dev/null +++ b/src/homepagebuilder/modules/Presenters.py @@ -0,0 +1,23 @@ +from homepagebuilder.core.utils.finder import find_using_resources +from homepagebuilder.core.formatter import format_code +from homepagebuilder.core.types import BuildingEnvironment +from homepagebuilder.interfaces import script + +@script('RawPresenter') +def raw_presenter(card,env:BuildingEnvironment,**_): + '''将卡片的 `data` 属性直接放入代码中''' + data = card.get('data','') + if 'usedresources' not in card: + card['usedresources'] = find_using_resources(data) + mark_used_resources(card['usedresources'],card,env) + return data + +@script('ChildrenPresenter') +def children_presenter(children_code,**_): + '''将子代码放入代码中''' + return children_code + +def mark_used_resources(used_resources,card,env:'BuildingEnvironment'): + for res_ref in used_resources: + res_ref = format_code(code = res_ref,data=card,env=env) + env.get('used_resources').add(res_ref) \ No newline at end of file diff --git a/src/homepagebuilder/modules/config_script.py b/src/homepagebuilder/modules/config_script.py new file mode 100644 index 0000000..d377794 --- /dev/null +++ b/src/homepagebuilder/modules/config_script.py @@ -0,0 +1,5 @@ +from homepagebuilder.interfaces import config, script + +@script('public_conf') +def conf_script(key:str,default = None,**_kwargs): + return config('public.'+key,default) \ No newline at end of file diff --git a/src/homepagebuilder/modules/pcl_versions.py b/src/homepagebuilder/modules/pcl_versions.py new file mode 100644 index 0000000..c0fffb7 --- /dev/null +++ b/src/homepagebuilder/modules/pcl_versions.py @@ -0,0 +1,29 @@ +from homepagebuilder.interfaces import script, format_code, Logger + +logger = Logger('PCLVersionScript') + +@script('IF_PCLNewerThan') +def newer_script(versionid,content,card,env,**_kwargs): + vid = format_code('${client.versionid}',card,env=env) + if not vid: + return + gtid = int(versionid) + if not vid or not str.isalnum(vid): + logger.warning('Cannot get version of pcl') + return '' + if int(vid) >= gtid: + return content + return '' + +@script('IF_PCLOlderThan') +def lower_script(versionid,content,card,env,**_kwargs): + vid = format_code('${client.versionid}',card,env=env) + if not vid: + return + ltid = int(versionid) + if not vid or not str.isalnum(vid): + logger.warning('Cannot get version of pcl') + return '' + if int(vid) < ltid: + return content + return '' diff --git a/Plugin/Markdown/Modules/GetMdH1.py b/src/homepagebuilder/plugins/Markdown/Modules/GetMdH1.py similarity index 88% rename from Plugin/Markdown/Modules/GetMdH1.py rename to src/homepagebuilder/plugins/Markdown/Modules/GetMdH1.py index e57e065..8b13792 100644 --- a/Plugin/Markdown/Modules/GetMdH1.py +++ b/src/homepagebuilder/plugins/Markdown/Modules/GetMdH1.py @@ -1,6 +1,6 @@ import markdown import re -from Interfaces import script +from homepagebuilder.interfaces import script @script('GetMdH1') def get_md_h1(card,**kwargs): diff --git a/Plugin/Markdown/Modules/MarkdownPresenter.py b/src/homepagebuilder/plugins/Markdown/Modules/MarkdownPresenter.py similarity index 63% rename from Plugin/Markdown/Modules/MarkdownPresenter.py rename to src/homepagebuilder/plugins/Markdown/Modules/MarkdownPresenter.py index 7acf0b3..8e990e3 100644 --- a/Plugin/Markdown/Modules/MarkdownPresenter.py +++ b/src/homepagebuilder/plugins/Markdown/Modules/MarkdownPresenter.py @@ -1,15 +1,17 @@ import re import markdown from bs4 import BeautifulSoup -from Interfaces import script,encode_escape,Logger -from Parsers import create_node +from homepagebuilder.interfaces import script, require -def html2xaml(html,res): +parsers_module = require('markdown_parsers') +create_node= parsers_module.create_node + +def html2xaml(html,env): '''html转为xaml代码''' soup = BeautifulSoup(html,'html.parser') xaml = '' for tag in soup.find_all(recursive=False): - xaml += create_node(tag,res,[]).convert() + xaml += create_node(tag,env,[]).convert() return xaml del_pattern = re.compile(r'~~(.*)~~') @@ -18,15 +20,15 @@ def md_del_replace(md:str): '''转译删除线''' return re.sub(del_pattern,r'\1',md) -def convert(card,res): +def convert(card,env): '''生成xaml代码''' md = card['markdown'] md = md_del_replace(md) html = markdown.markdown(md) - xaml = html2xaml(html,res) + xaml = html2xaml(html,env) return xaml @script('MarkdownPresenter') -def script(card,res,**_): +def script(card,env,**_): '''从markdown生成xaml代码脚本''' - return convert(card,res) + return convert(card,env) diff --git a/Plugin/Markdown/Modules/MarkdownReader.py b/src/homepagebuilder/plugins/Markdown/Modules/MarkdownReader.py similarity index 93% rename from Plugin/Markdown/Modules/MarkdownReader.py rename to src/homepagebuilder/plugins/Markdown/Modules/MarkdownReader.py index 5b7b3e4..6edd55e 100644 --- a/Plugin/Markdown/Modules/MarkdownReader.py +++ b/src/homepagebuilder/plugins/Markdown/Modules/MarkdownReader.py @@ -3,7 +3,7 @@ ''' import re import yaml -from Interfaces import file_reader, read_string +from homepagebuilder.interfaces import file_reader, read_string # 提取列表项:(?:\[?\s*)(\".*?\"|\'.*?\'|[^,]*?)(?:\s*[,|\]]) diff --git a/Plugin/Markdown/Resources/Styles/headings.yml b/src/homepagebuilder/plugins/Markdown/Resources/headings.yml similarity index 100% rename from Plugin/Markdown/Resources/Styles/headings.yml rename to src/homepagebuilder/plugins/Markdown/Resources/headings.yml diff --git a/Plugin/Markdown/Resources/Styles/inline_code_style.xaml b/src/homepagebuilder/plugins/Markdown/Resources/inline_code_style.xaml similarity index 100% rename from Plugin/Markdown/Resources/Styles/inline_code_style.xaml rename to src/homepagebuilder/plugins/Markdown/Resources/inline_code_style.xaml diff --git a/Plugin/Markdown/Resources/Styles/markdown_styles.yml b/src/homepagebuilder/plugins/Markdown/Resources/markdown_styles.yml similarity index 100% rename from Plugin/Markdown/Resources/Styles/markdown_styles.yml rename to src/homepagebuilder/plugins/Markdown/Resources/markdown_styles.yml diff --git a/Plugin/Markdown/Resources/Styles/quotes.yml b/src/homepagebuilder/plugins/Markdown/Resources/quotes.yml similarity index 100% rename from Plugin/Markdown/Resources/Styles/quotes.yml rename to src/homepagebuilder/plugins/Markdown/Resources/quotes.yml diff --git a/Plugin/Markdown/Resources/Components/a.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/a.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/a.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/a.xaml diff --git a/Plugin/Markdown/Resources/Components/blockquote-typed.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/blockquote-typed.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/blockquote-typed.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/blockquote-typed.xaml diff --git a/Plugin/Markdown/Resources/Components/blockquote.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/blockquote.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/blockquote.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/blockquote.xaml diff --git a/Plugin/Markdown/Resources/Components/br.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/br.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/br.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/br.xaml diff --git a/Plugin/Markdown/Resources/Components/code.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/code.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/code.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/code.xaml diff --git a/Plugin/Markdown/Resources/Components/del.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/del.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/del.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/del.xaml diff --git a/Plugin/Markdown/Resources/Components/em.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/em.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/em.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/em.xaml diff --git a/Plugin/Markdown/Resources/Components/heading.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/heading.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/heading.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/heading.xaml diff --git a/Plugin/Markdown/Resources/Components/hr.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/hr.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/hr.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/hr.xaml diff --git a/Plugin/Markdown/Resources/Components/img.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/img.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/img.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/img.xaml diff --git a/Plugin/Markdown/Resources/Components/li.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/li.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/li.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/li.xaml diff --git a/Plugin/Markdown/Resources/Components/ol.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/ol.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/ol.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/ol.xaml diff --git a/Plugin/Markdown/Resources/Components/p.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/p.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/p.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/p.xaml diff --git a/Plugin/Markdown/Resources/Components/pclhint.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/pclhint.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/pclhint.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/pclhint.xaml diff --git a/Plugin/Markdown/Resources/Components/strong.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/strong.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/strong.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/strong.xaml diff --git a/Plugin/Markdown/Resources/Components/titled-img.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/titled-img.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/titled-img.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/titled-img.xaml diff --git a/Plugin/Markdown/Resources/Components/ul.xaml b/src/homepagebuilder/plugins/Markdown/Structures/Components/ul.xaml similarity index 100% rename from Plugin/Markdown/Resources/Components/ul.xaml rename to src/homepagebuilder/plugins/Markdown/Structures/Components/ul.xaml diff --git a/Plugin/Markdown/Resources/Templates/MarkdownCard.yml b/src/homepagebuilder/plugins/Markdown/Structures/Templates/MarkdownCard.yml similarity index 100% rename from Plugin/Markdown/Resources/Templates/MarkdownCard.yml rename to src/homepagebuilder/plugins/Markdown/Structures/Templates/MarkdownCard.yml diff --git a/src/homepagebuilder/plugins/Markdown/modules/GetMdH1.py b/src/homepagebuilder/plugins/Markdown/modules/GetMdH1.py new file mode 100644 index 0000000..8b13792 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/modules/GetMdH1.py @@ -0,0 +1,14 @@ +import markdown +import re +from homepagebuilder.interfaces import script + +@script('GetMdH1') +def get_md_h1(card,**kwargs): + exten = card['file_exten'] + if exten != 'md' and exten != 'markdown': + return 'NOT A MARKDOWN DOCUMENT!' + html = markdown.markdown(card['markdown']) + titles = re.findall('

(.*)

',html) + if len(titles) == 0: + return '' + return titles[0] diff --git a/src/homepagebuilder/plugins/Markdown/modules/MarkdownPresenter.py b/src/homepagebuilder/plugins/Markdown/modules/MarkdownPresenter.py new file mode 100644 index 0000000..8e990e3 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/modules/MarkdownPresenter.py @@ -0,0 +1,34 @@ +import re +import markdown +from bs4 import BeautifulSoup +from homepagebuilder.interfaces import script, require + +parsers_module = require('markdown_parsers') +create_node= parsers_module.create_node + +def html2xaml(html,env): + '''html转为xaml代码''' + soup = BeautifulSoup(html,'html.parser') + xaml = '' + for tag in soup.find_all(recursive=False): + xaml += create_node(tag,env,[]).convert() + return xaml + +del_pattern = re.compile(r'~~(.*)~~') + +def md_del_replace(md:str): + '''转译删除线''' + return re.sub(del_pattern,r'\1',md) + +def convert(card,env): + '''生成xaml代码''' + md = card['markdown'] + md = md_del_replace(md) + html = markdown.markdown(md) + xaml = html2xaml(html,env) + return xaml + +@script('MarkdownPresenter') +def script(card,env,**_): + '''从markdown生成xaml代码脚本''' + return convert(card,env) diff --git a/src/homepagebuilder/plugins/Markdown/modules/MarkdownReader.py b/src/homepagebuilder/plugins/Markdown/modules/MarkdownReader.py new file mode 100644 index 0000000..6edd55e --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/modules/MarkdownReader.py @@ -0,0 +1,32 @@ +''' +Markdown 读取模块 +''' +import re +import yaml +from homepagebuilder.interfaces import file_reader, read_string + +# 提取列表项:(?:\[?\s*)(\".*?\"|\'.*?\'|[^,]*?)(?:\s*[,|\]]) + +@file_reader(['md','markdown']) +def read_markdown(filepath:str): + '''读取markdown文件方法''' + string = read_string(filepath=filepath) + string, card = sep_attr(string) + card['markdown'] = replace_with_wiki_link(string) + return card + +WIKILINK_APTTERN = re.compile(r'\[\[(.*?)\]\]') +def replace_with_wiki_link(text): + '''替换 MCW 链接''' + replaced_text = re.sub(WIKILINK_APTTERN , r'[\1](https://zh.minecraft.wiki/w/\1)', text) + return replaced_text + +ATTR_PATTERN = re.compile(r'^\-{3,}\n((?:.*\n)+)\-{3,}(?:\n|$)') +def sep_attr(md): + '''分离markdwon正文与文档属性''' + attr = {} + matchs = re.match(ATTR_PATTERN, md) + if matchs: + attr = yaml.safe_load(matchs.groups()[0]) + md = re.sub(ATTR_PATTERN, '', md) + return md,attr diff --git a/src/homepagebuilder/plugins/Markdown/modules/__init__.py b/src/homepagebuilder/plugins/Markdown/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Plugin/Markdown/Modules/Parsers.py b/src/homepagebuilder/plugins/Markdown/modules/markdown_parsers.py similarity index 88% rename from Plugin/Markdown/Modules/Parsers.py rename to src/homepagebuilder/plugins/Markdown/modules/markdown_parsers.py index 3b872bf..0492ccf 100644 --- a/Plugin/Markdown/Modules/Parsers.py +++ b/src/homepagebuilder/plugins/Markdown/modules/markdown_parsers.py @@ -1,7 +1,7 @@ from typing import Union,List,Literal from abc import abstractmethod,ABC import re -from Interfaces import encode_escape,Logger +from homepagebuilder.interfaces import encode_escape,Logger logger = Logger('Markdown') FIRSTLINE_SPACES = ' ' @@ -47,8 +47,9 @@ def to_plain_str(node:'Node') -> str: return content class Node(): - def __init__(self,tag,res,parent_stack): - self.res = res + def __init__(self,tag,env,parent_stack): + self.env = env + self.components = env.get('components') self.name = None self.attrs = None self.tag = tag @@ -132,11 +133,12 @@ def get_replacement(self) -> Union[List]: return [] def get_element_frame(self): - components:dict[str,str] = self.res.components - replace_list = self.get_replacement() - replace_str = components.get(self.component_name) - if len(replace_list) > 0: - for k,v in replace_list: + replacement = self.get_replacement() + component_obj = self.components.get(self.component_name) + component_obj.mark_used_resources(replacement,self.env) + replace_str = str(component_obj) + if len(replacement) > 0: + for k,v in replacement.items(): replace_str = replace_str.replace(f'${{{k}}}',encode_escape(str(v))) return replace_str @@ -144,7 +146,7 @@ def parse_children(self): if self.tag.contents: self.children = [] for child in self.tag.contents: - self.add_child_node(create_node(child,self.res,self.parent_stack + [self])) + self.add_child_node(create_node(child,self.env,self.parent_stack + [self])) def add_child_node(self,child_node:Node): if child_node.expose: @@ -211,6 +213,7 @@ def convert_children(self): continue if not in_paragraph: content += '' + self.env.get('used_resources').add('Lp') in_paragraph = True else: if in_paragraph: @@ -253,10 +256,10 @@ def component_name(self) -> str: def get_replacement(self) -> Union[List]: if self.is_pcl_hint: - return [('iswarn', self.is_warn)] + return {'iswarn': self.is_warn} else: - return [('type', self.quote_type), - ('typename',self.quote_type_name)] + return {'type': self.quote_type, + 'typename': self.quote_type_name} def convert_children(self): content = '' @@ -302,17 +305,17 @@ def component_name(self) -> str: return 'heading' def get_replacement(self) -> Union[List|None]: - return[('level',self.name[1:])] + return {'level': self.name[1:]} @handles('a') class Link(InlineNode): def get_replacement(self) -> Union[List|None]: - reps = [('link',self.attrs['href'])] + reps = {'link': self.attrs['href']} ancestor = self.ancestor if ancestor.name == 'li': - reps.append(('pos_down',3)) + reps['pos_down'] = 3 else: - reps.append(('pos_down',2)) + reps['pos_down'] = 2 return reps @handles('img') @@ -326,14 +329,13 @@ def component_name(self) -> str: return 'titled-img' if self.title else 'img' def get_replacement(self) -> Union[List|None]: - replace_list = [] - replace_list.append(('source',self.attrs['src'])) + replacements = {'source': self.attrs['src']} if self.title: - replace_list.append(('title',self.title)) - return replace_list + replacements['title'] = self.title + return replacements -def create_node(tag,res,parent_stack): +def create_node(tag,env,parent_stack): if isinstance(tag,str): - return Text(tag,res,parent_stack) + return Text(tag,env=env,parent_stack=parent_stack) else: - return TAG_PARSER_MAPPING[tag.name](tag=tag,res=res,parent_stack=parent_stack) \ No newline at end of file + return TAG_PARSER_MAPPING[tag.name](tag=tag,env=env,parent_stack=parent_stack) \ No newline at end of file diff --git a/Plugin/Markdown/pack.yml b/src/homepagebuilder/plugins/Markdown/pack.yml similarity index 100% rename from Plugin/Markdown/pack.yml rename to src/homepagebuilder/plugins/Markdown/pack.yml diff --git a/src/homepagebuilder/plugins/Markdown/resources/headings.yml b/src/homepagebuilder/plugins/Markdown/resources/headings.yml new file mode 100644 index 0000000..3e2607d --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/resources/headings.yml @@ -0,0 +1,46 @@ +Anchors: + - &Color1 "{DynamicResource ColorBrush1}" + - &Color2 "{DynamicResource ColorBrush2}" + - &Color3 "{DynamicResource ColorBrush3}" + - &Color4 "{DynamicResource ColorBrush4}" + - &Color5 "{DynamicResource ColorBrush5}" + +Styles: + - Key: H1 + Target: Paragraph + Setters: + FontSize: 24 + Margin: 0,10,0,10 + FontWeight: Bold + Foreground: *Color3 + + - Key: H2 + Target: Paragraph + Setters: + FontSize: 22 + Margin: 0,10,0,5 + FontWeight: Bold + Foreground: *Color3 + + - Key: H3 + Target: Paragraph + Setters: + FontSize: 18 + Margin: 0,10,0,3 + FontWeight: Bold + Foreground: *Color4 + + - Key: H4 + Target: Paragraph + Setters: + FontSize: 16 + Margin: 0,10,0,1 + FontWeight: Bold + Foreground: *Color4 + + - Key: H5 + Target: Paragraph + Setters: + FontSize: 14 + Margin: 0,8,0,1 + Foreground: *Color4 \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/resources/inline_code_style.xaml b/src/homepagebuilder/plugins/Markdown/resources/inline_code_style.xaml new file mode 100644 index 0000000..7790044 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/resources/inline_code_style.xaml @@ -0,0 +1,21 @@ + \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/resources/markdown_styles.yml b/src/homepagebuilder/plugins/Markdown/resources/markdown_styles.yml new file mode 100644 index 0000000..4d65d91 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/resources/markdown_styles.yml @@ -0,0 +1,52 @@ +Anchors: + - &Color1 "{DynamicResource ColorBrush1}" + - &Color2 "{DynamicResource ColorBrush2}" + - &Color3 "{DynamicResource ColorBrush3}" + - &Color4 "{DynamicResource ColorBrush4}" + - &Color5 "{DynamicResource ColorBrush5}" + +Styles: + - Target: Image + Key: InnerImage + Setters: + MaxHeight: 500 + HorizontalAlignment: Center + + - Target: TextBlock + Setters: + TextWrapping: Wrap + HorizontalAlignment: Left + FontSize: 14 + Foreground: Black + + - Target: Paragraph + Setters: + LineHeight: 12 + TextIndent: 0 + Margin: 0,8 + + - Key: Lp + Target: Paragraph + Setters: + LineHeight: 16 + TextIndent: 0 + + - Target: List + Setters: + Foreground: *Color3 + Margin: 20,6,0,6 + MarkerStyle: 1 + Padding: 0 + + - Target: ListItem + Setters: + Foreground: Black + Margin: 0,4 + + - Key: Hr + Target: Section + Setters: + Margin: "0,4,0,0" + BorderThickness: "0,1.5,0,0" + BorderBrush: "{StaticResource ColorBrush4}" + FontSize: 4 \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/resources/quotes.yml b/src/homepagebuilder/plugins/Markdown/resources/quotes.yml new file mode 100644 index 0000000..102204e --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/resources/quotes.yml @@ -0,0 +1,43 @@ +Styles: + - Key: Quote + Target: Section + Setters: + BorderThickness: 5,0,0,0 + BorderBrush: "{DynamicResource ColorBrush4}" + Padding: 10,5 + Margin: 0,5 + + - Key: cautionQuote + Target: Section + BasedOn: Quote + Setters: + BorderBrush: "#CC4455" + Background: "#33CC4455" + + - Key: noteQuote + Target: Section + BasedOn: Quote + Setters: + BorderBrush: "#4455AA" + Background: "#334455AA" + + - Key: tipQuote + Target: Section + BasedOn: Quote + Setters: + BorderBrush: "#44AA55" + Background: "#3344AA55" + + - Key: importantQuote + Target: Section + BasedOn: Quote + Setters: + BorderBrush: "#AA55BB" + Background: "#33AA55BB" + + - Key: warningQuote + Target: Section + BasedOn: Quote + Setters: + BorderBrush: "#DDBB44" + Background: "#33DDBB44" \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/a.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/a.xaml new file mode 100644 index 0000000..d3c8a6d --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/a.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/blockquote-typed.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/blockquote-typed.xaml new file mode 100644 index 0000000..73eff08 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/blockquote-typed.xaml @@ -0,0 +1,9 @@ +
+ + ${typename} + + + + + ${content} +
\ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/blockquote.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/blockquote.xaml new file mode 100644 index 0000000..53eb5f5 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/blockquote.xaml @@ -0,0 +1,3 @@ +
+ ${content} +
\ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/br.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/br.xaml new file mode 100644 index 0000000..6c9bc03 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/br.xaml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/code.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/code.xaml new file mode 100644 index 0000000..d8e3381 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/code.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/del.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/del.xaml new file mode 100644 index 0000000..a5102f2 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/del.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/em.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/em.xaml new file mode 100644 index 0000000..fce1ca3 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/em.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/heading.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/heading.xaml new file mode 100644 index 0000000..c3855f3 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/heading.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/hr.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/hr.xaml new file mode 100644 index 0000000..4b4c207 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/hr.xaml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/img.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/img.xaml new file mode 100644 index 0000000..ec96c1b --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/img.xaml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/li.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/li.xaml new file mode 100644 index 0000000..e42b0b0 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/li.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/ol.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/ol.xaml new file mode 100644 index 0000000..6f830e1 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/ol.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/p.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/p.xaml new file mode 100644 index 0000000..1602b46 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/p.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/pclhint.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/pclhint.xaml new file mode 100644 index 0000000..76d32c2 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/pclhint.xaml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/strong.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/strong.xaml new file mode 100644 index 0000000..3507946 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/strong.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/titled-img.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/titled-img.xaml new file mode 100644 index 0000000..049c831 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/titled-img.xaml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/components/ul.xaml b/src/homepagebuilder/plugins/Markdown/structures/components/ul.xaml new file mode 100644 index 0000000..5baa2e7 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/components/ul.xaml @@ -0,0 +1 @@ +${content} \ No newline at end of file diff --git a/src/homepagebuilder/plugins/Markdown/structures/templates/MarkdownCard.yml b/src/homepagebuilder/plugins/Markdown/structures/templates/MarkdownCard.yml new file mode 100644 index 0000000..a0ac268 --- /dev/null +++ b/src/homepagebuilder/plugins/Markdown/structures/templates/MarkdownCard.yml @@ -0,0 +1,10 @@ +# 使用 Markdown 文本的卡片 +components: + - $MarkdownPresenter +base: FlowDocCard +fill: + title: ${@GetMdH1} +filter: + file_exten: + - md + - markdown \ No newline at end of file diff --git a/Plugin/ProjectInfo/Modules/GitInfo.py b/src/homepagebuilder/plugins/ProjectInfo/Modules/GitInfo.py similarity index 68% rename from Plugin/ProjectInfo/Modules/GitInfo.py rename to src/homepagebuilder/plugins/ProjectInfo/Modules/GitInfo.py index 89e5ae9..fc7d6ec 100644 --- a/Plugin/ProjectInfo/Modules/GitInfo.py +++ b/src/homepagebuilder/plugins/ProjectInfo/Modules/GitInfo.py @@ -1,10 +1,9 @@ import subprocess import datetime -import os -from Core.logger import Logger -from Core.i18n import locale -from Interfaces.Events import on_card_building, on_project_loaded -from Interfaces import enable_by_config, config as sys_config, enable_by +from homepagebuilder.core.logger import Logger +from homepagebuilder.core.i18n import locale +from homepagebuilder.interfaces.Events import on +from homepagebuilder.interfaces import enable_by_config, config as sys_config, enable_by def gitinfo_config(key): return sys_config('ProjectInfo.GitInfo.' + key) @@ -31,41 +30,44 @@ def check_git_installtion() -> bool: IS_GIT_INSTALLED = check_git_installtion() def is_git_repo(directory): - return os.path.exists(os.path.join(directory, '.git')) + try: + subprocess.run(["git", "rev-parse"],cwd=directory, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except Exception: + return False -@on_project_loaded() +@on('project.load.return') @enable_by_config('ProjectInfo.GitInfo.Enable') @enable_by(IS_GIT_INSTALLED) def set_githash(proj,*_,**__): - res = proj.resources - res.data['global']['git.isrepo'] = is_git_repo(proj.base_path) - if not res.data['global']['git.isrepo']: + proj.set_env_data('git.isrepo', is_git_repo(proj.base_path)) + if not proj.get_env_data('git.isrepo'): if not gitinfo_config('NoProduceNotRepoWarning'): logger.warning(locale('projectinfo.git.isnotrepo')) logger.warning(locale('projectinfo.git.disablehint', hide_config_key = 'NoProduceNotRepoWarning')) return githash = get_githash(proj.base_path).removesuffix('\n') logger.info(locale('projectinfo.git.version',version=githash)) - res.data['global']['git.commit.hash'] = githash - res.data['global']['git.commit.id'] = githash[:7] + proj.set_env_data('git.commit.hash',githash) + proj.set_env_data('git.commit.id',githash[:7]) def get_githash(path): githash = subprocess.check_output('git rev-parse HEAD',cwd = path, shell=True) return githash.decode("utf-8") -@on_card_building() +@on('tm.buildcard.start') @enable_by_config('ProjectInfo.GitInfo.Enable') @enable_by(IS_GIT_INSTALLED) -def get_card_last_update_time(proj,card): - res = proj.resources - if not res.data['global']['git.isrepo']: +def get_card_last_update_time(_tm,card,env,*args,**kwargs): + data = env.get('data') + if not data['git.isrepo']: return if 'last_update' in card: return file = card.get('file') if not file: return - timestamp = subprocess.check_output(r"git log -1 --format=%ct --follow -- " + file.abs_path ,cwd=file.direname, shell=True).decode("utf-8") + timestamp = subprocess.check_output(r"git log -1 --format=%ct -- " + file.abs_path ,cwd=file.direname, shell=True).decode("utf-8") if len(timestamp) == 0: return dt = datetime.datetime.fromtimestamp(int(timestamp[:-1])) diff --git a/Plugin/ProjectInfo/Resources/i18n/en_US.yml b/src/homepagebuilder/plugins/ProjectInfo/i18n/en_US.yml similarity index 100% rename from Plugin/ProjectInfo/Resources/i18n/en_US.yml rename to src/homepagebuilder/plugins/ProjectInfo/i18n/en_US.yml diff --git a/Plugin/ProjectInfo/Resources/i18n/zh_CN.yml b/src/homepagebuilder/plugins/ProjectInfo/i18n/zh_CN.yml similarity index 100% rename from Plugin/ProjectInfo/Resources/i18n/zh_CN.yml rename to src/homepagebuilder/plugins/ProjectInfo/i18n/zh_CN.yml diff --git a/src/homepagebuilder/plugins/ProjectInfo/modules/GitInfo.py b/src/homepagebuilder/plugins/ProjectInfo/modules/GitInfo.py new file mode 100644 index 0000000..fc7d6ec --- /dev/null +++ b/src/homepagebuilder/plugins/ProjectInfo/modules/GitInfo.py @@ -0,0 +1,74 @@ +import subprocess +import datetime +from homepagebuilder.core.logger import Logger +from homepagebuilder.core.i18n import locale +from homepagebuilder.interfaces.Events import on +from homepagebuilder.interfaces import enable_by_config, config as sys_config, enable_by + +def gitinfo_config(key): + return sys_config('ProjectInfo.GitInfo.' + key) + +logger = Logger('ProjectInfo') + + +def is_git_installed() -> bool: + try: + subprocess.run(["git", "--version"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except Exception: + return False + +def check_git_installtion() -> bool: + if not gitinfo_config('Enable'): + return False + is_installed = is_git_installed() + if not is_installed and not gitinfo_config('NoProduceNotInstalledWarning'): + logger.warning(locale('projectinfo.git.notinstalled')) + logger.warning(locale('projectinfo.git.disablehint', hide_config_key = 'NoProduceNotInstalledWarning')) + return is_installed + +IS_GIT_INSTALLED = check_git_installtion() + +def is_git_repo(directory): + try: + subprocess.run(["git", "rev-parse"],cwd=directory, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return True + except Exception: + return False + +@on('project.load.return') +@enable_by_config('ProjectInfo.GitInfo.Enable') +@enable_by(IS_GIT_INSTALLED) +def set_githash(proj,*_,**__): + proj.set_env_data('git.isrepo', is_git_repo(proj.base_path)) + if not proj.get_env_data('git.isrepo'): + if not gitinfo_config('NoProduceNotRepoWarning'): + logger.warning(locale('projectinfo.git.isnotrepo')) + logger.warning(locale('projectinfo.git.disablehint', hide_config_key = 'NoProduceNotRepoWarning')) + return + githash = get_githash(proj.base_path).removesuffix('\n') + logger.info(locale('projectinfo.git.version',version=githash)) + proj.set_env_data('git.commit.hash',githash) + proj.set_env_data('git.commit.id',githash[:7]) + +def get_githash(path): + githash = subprocess.check_output('git rev-parse HEAD',cwd = path, shell=True) + return githash.decode("utf-8") + +@on('tm.buildcard.start') +@enable_by_config('ProjectInfo.GitInfo.Enable') +@enable_by(IS_GIT_INSTALLED) +def get_card_last_update_time(_tm,card,env,*args,**kwargs): + data = env.get('data') + if not data['git.isrepo']: + return + if 'last_update' in card: + return + file = card.get('file') + if not file: + return + timestamp = subprocess.check_output(r"git log -1 --format=%ct -- " + file.abs_path ,cwd=file.direname, shell=True).decode("utf-8") + if len(timestamp) == 0: + return + dt = datetime.datetime.fromtimestamp(int(timestamp[:-1])) + card['last_update'] = dt.date() diff --git a/src/homepagebuilder/plugins/ProjectInfo/pack.yml b/src/homepagebuilder/plugins/ProjectInfo/pack.yml new file mode 100644 index 0000000..ecbabd2 --- /dev/null +++ b/src/homepagebuilder/plugins/ProjectInfo/pack.yml @@ -0,0 +1 @@ +pack_namespace: projectinfo \ No newline at end of file diff --git a/Config/ProjectInfo.yml b/src/homepagebuilder/resources/configs/ProjectInfo.yml similarity index 100% rename from Config/ProjectInfo.yml rename to src/homepagebuilder/resources/configs/ProjectInfo.yml diff --git a/Config/basic.yml b/src/homepagebuilder/resources/configs/default.yml similarity index 94% rename from Config/basic.yml rename to src/homepagebuilder/resources/configs/default.yml index fa0a7c1..1bd2e28 100644 --- a/Config/basic.yml +++ b/src/homepagebuilder/resources/configs/default.yml @@ -5,9 +5,9 @@ System.EnablePlugins: True # 是否忽略由脚本引起的错误 System.Script.IgnoreError: False -Logging.Level: 30 +Logging.Level: 20 # 是否启用 Debug -Debug.Enable: True +Debug.Enable: False Debug.Logging.Level: 10 Debug.Logging.FileOutput.Enable: False diff --git a/i18n/en_US.yml b/src/homepagebuilder/resources/i18n/en_US.yml similarity index 69% rename from i18n/en_US.yml rename to src/homepagebuilder/resources/i18n/en_US.yml index e42b44b..c067b02 100644 --- a/i18n/en_US.yml +++ b/src/homepagebuilder/resources/i18n/en_US.yml @@ -1,3 +1,21 @@ +builder.load.resources: 'Loading builder resources' +debug.timer.start: '${func}(${count}) Timer Started' +debug.timer.stop: '${func}(${count}) Timer Stopped, Time used: ${time}s' +event.triggered: 'Triggered event ${event_name}' +io.format.unsupported: 'File format (${exten}) of ${path} is unsupported.' +library.load: 'Loading library: ${name}' +resourceloader.load.resourcefile: 'Loading resources file: ${name}' +resourceloader.load.regist: 'Regist resource key: (${type_name}) ${name} ' +loader.regist.structure: 'Regist structure: ${name}' +loader.regist.structure.withtype: 'Regist structure: (${type_name}) ${name}' +module.load: 'Loading module: ${name}' +module.load.success: 'Loading module successfully: ${name}' +module.reload: 'Reload module: ${name}' +module.page.import.start: 'Start to import code-driven page' +module.page.import.one.init: 'Load code-driven page: ${name}' +module.page.import.one.add: 'Registed code-driven page:${name}' +module.page.import.success: 'Load code-driven page successfully' +page.not_found: 'Cannot find page named ${page}' project.init: "Initializing.." project.load.basic_res: "Loading basic resources" project.load.modules: "Loading modules" @@ -16,21 +34,12 @@ project.get_card.failed: 'Failed to get card due to: ${ex}' project.gen_page.start: 'Getting codes of the page ${page}' project.gen_page.failed.notfound: 'Cannot find page named ${page}' project.check_module_list.error: 'Cannot find module(s): ${wait_list}' -module.load: 'Loading script: ${name}' -module.load.success: 'Successfully loaded script: ${name}' -module.reload: 'Reloading script: ${name}' script.invoke.failed.notfound: 'Try to invoke not existed script ${name}' resource.load: 'Loading resource pack: ${path}' -library.load: 'Loading library: ${name}' server.init: 'Server Initializing...' server.start: 'Server Started on port ${port}' server.request.received: 'Received request get ${page} with ${args}' server.request.response.json: 'Responed request of ${page} with json' server.request.response.page: 'Responed request of ${page} with page content' server.request.response.not_found: 'Responed request of ${page} with not found' -server.request.response.error: 'An error occured during responding the request of ${page} with args ${args}' -page.not_found: 'Cannot find page named ${page}' -debug.timer.start: '${func}(${count}) Timer Started' -debug.timer.stop: '${func}(${count}) Timer Stopped, Time used: ${time}s' -event.triggered: 'Triggered event ${event_name}' -io.format.unsupported: 'File format (${exten}) of ${path} is unsupported.' \ No newline at end of file +server.request.response.error: 'An error occured during responding the request of ${page} with args ${args}' \ No newline at end of file diff --git a/i18n/zh_CN.yml b/src/homepagebuilder/resources/i18n/zh_CN.yml similarity index 88% rename from i18n/zh_CN.yml rename to src/homepagebuilder/resources/i18n/zh_CN.yml index 8563111..0817134 100644 --- a/i18n/zh_CN.yml +++ b/src/homepagebuilder/resources/i18n/zh_CN.yml @@ -1,8 +1,13 @@ +builder.load.resources: '加载构建器资源' debug.timer.start: '${func}(${count}) 计时器启动' debug.timer.stop: '${func}(${count}) 计时器停止,该函数运行时间: ${time}s' event.triggered: '事件 ${event_name} 被触发' io.format.unsupported: '${path} 文件为不支持的文件类型 ${exten}' library.load: '正在加载库: ${name}' +resourceloader.load.resourcefile: '正在加载资源文件: ${name}' +resourceloader.load.regist: '注册资源:(${type_name}) ${name} ' +loader.regist.structure: '注册结构:${name}' +loader.regist.structure.withtype: '注册结构:(${type_name}) ${name}' module.load: '加载脚本: ${name}' module.load.success: '成功加载脚本: ${name}' module.reload: '重载脚本: ${name}' diff --git a/Resources/Styles/Basics.yml b/src/homepagebuilder/resources/resources/Basics.yml similarity index 99% rename from Resources/Styles/Basics.yml rename to src/homepagebuilder/resources/resources/Basics.yml index f1f4872..e080217 100644 --- a/Resources/Styles/Basics.yml +++ b/src/homepagebuilder/resources/resources/Basics.yml @@ -3,6 +3,7 @@ Styles: Target: StackPanel Setters: Margin: 20,40,20,20 + - Key: Card Target: local:MyCard Setters: diff --git a/Resources/Styles/FlowDoc.yml b/src/homepagebuilder/resources/resources/FlowDoc.yml similarity index 100% rename from Resources/Styles/FlowDoc.yml rename to src/homepagebuilder/resources/resources/FlowDoc.yml diff --git a/Resources/Components/Button.xaml b/src/homepagebuilder/resources/structures/Components/Button.xaml similarity index 100% rename from Resources/Components/Button.xaml rename to src/homepagebuilder/resources/structures/Components/Button.xaml diff --git a/Resources/Components/Card.xaml b/src/homepagebuilder/resources/structures/Components/Card.xaml similarity index 100% rename from Resources/Components/Card.xaml rename to src/homepagebuilder/resources/structures/Components/Card.xaml diff --git a/Resources/Components/CardContentStack.xaml b/src/homepagebuilder/resources/structures/Components/CardContentStack.xaml similarity index 100% rename from Resources/Components/CardContentStack.xaml rename to src/homepagebuilder/resources/structures/Components/CardContentStack.xaml diff --git a/Resources/Components/FlowDocumentViewer.xaml b/src/homepagebuilder/resources/structures/Components/FlowDocumentViewer.xaml similarity index 100% rename from Resources/Components/FlowDocumentViewer.xaml rename to src/homepagebuilder/resources/structures/Components/FlowDocumentViewer.xaml diff --git a/Resources/Components/Hint.xaml b/src/homepagebuilder/resources/structures/Components/Hint.xaml similarity index 100% rename from Resources/Components/Hint.xaml rename to src/homepagebuilder/resources/structures/Components/Hint.xaml diff --git a/Resources/Page_Templates/Default.xaml b/src/homepagebuilder/resources/structures/PageTemplates/Default.xaml similarity index 84% rename from Resources/Page_Templates/Default.xaml rename to src/homepagebuilder/resources/structures/PageTemplates/Default.xaml index 9e794e4..40dd47e 100644 --- a/Resources/Page_Templates/Default.xaml +++ b/src/homepagebuilder/resources/structures/PageTemplates/Default.xaml @@ -2,12 +2,9 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:PCL;assembly=Plain Craft Launcher 2"> - -${animations} - ${styles} ${content} diff --git a/Resources/Templates/Card.yml b/src/homepagebuilder/resources/structures/Templates/Card.yml similarity index 100% rename from Resources/Templates/Card.yml rename to src/homepagebuilder/resources/structures/Templates/Card.yml diff --git a/Resources/Templates/CardFrame.yml b/src/homepagebuilder/resources/structures/Templates/CardFrame.yml similarity index 100% rename from Resources/Templates/CardFrame.yml rename to src/homepagebuilder/resources/structures/Templates/CardFrame.yml diff --git a/Resources/Templates/FlowDocCard.yml b/src/homepagebuilder/resources/structures/Templates/FlowDocCard.yml similarity index 100% rename from Resources/Templates/FlowDocCard.yml rename to src/homepagebuilder/resources/structures/Templates/FlowDocCard.yml diff --git a/Resources/Templates/Raw.yml b/src/homepagebuilder/resources/structures/Templates/Raw.yml similarity index 100% rename from Resources/Templates/Raw.yml rename to src/homepagebuilder/resources/structures/Templates/Raw.yml diff --git a/Resources/Templates/RawCard.yml b/src/homepagebuilder/resources/structures/Templates/RawCard.yml similarity index 100% rename from Resources/Templates/RawCard.yml rename to src/homepagebuilder/resources/structures/Templates/RawCard.yml diff --git a/src/homepagebuilder/resources/structures/components/Button.xaml b/src/homepagebuilder/resources/structures/components/Button.xaml new file mode 100644 index 0000000..3861296 --- /dev/null +++ b/src/homepagebuilder/resources/structures/components/Button.xaml @@ -0,0 +1,2 @@ + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/components/Card.xaml b/src/homepagebuilder/resources/structures/components/Card.xaml new file mode 100644 index 0000000..2f61183 --- /dev/null +++ b/src/homepagebuilder/resources/structures/components/Card.xaml @@ -0,0 +1,3 @@ + +${$ChildrenPresenter} + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/components/CardContentStack.xaml b/src/homepagebuilder/resources/structures/components/CardContentStack.xaml new file mode 100644 index 0000000..5917da4 --- /dev/null +++ b/src/homepagebuilder/resources/structures/components/CardContentStack.xaml @@ -0,0 +1,3 @@ + +${$ChildrenPresenter} + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/components/FlowDocumentViewer.xaml b/src/homepagebuilder/resources/structures/components/FlowDocumentViewer.xaml new file mode 100644 index 0000000..3ff3689 --- /dev/null +++ b/src/homepagebuilder/resources/structures/components/FlowDocumentViewer.xaml @@ -0,0 +1,5 @@ + + +${$ChildrenPresenter} + + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/components/Hint.xaml b/src/homepagebuilder/resources/structures/components/Hint.xaml new file mode 100644 index 0000000..13dd0b9 --- /dev/null +++ b/src/homepagebuilder/resources/structures/components/Hint.xaml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/pagetemplates/Default.xaml b/src/homepagebuilder/resources/structures/pagetemplates/Default.xaml new file mode 100644 index 0000000..40dd47e --- /dev/null +++ b/src/homepagebuilder/resources/structures/pagetemplates/Default.xaml @@ -0,0 +1,11 @@ + + + + +${styles} + +${content} + \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/templates/Card.yml b/src/homepagebuilder/resources/structures/templates/Card.yml new file mode 100644 index 0000000..d381fbc --- /dev/null +++ b/src/homepagebuilder/resources/structures/templates/Card.yml @@ -0,0 +1,4 @@ +# 具有一个 StackPanel 的卡片 +components: + - CardContentStack +base: CardFrame \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/templates/CardFrame.yml b/src/homepagebuilder/resources/structures/templates/CardFrame.yml new file mode 100644 index 0000000..a5d978c --- /dev/null +++ b/src/homepagebuilder/resources/structures/templates/CardFrame.yml @@ -0,0 +1,4 @@ +# 卡片框架 +components: + - Card +base: void \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/templates/FlowDocCard.yml b/src/homepagebuilder/resources/structures/templates/FlowDocCard.yml new file mode 100644 index 0000000..0d6feec --- /dev/null +++ b/src/homepagebuilder/resources/structures/templates/FlowDocCard.yml @@ -0,0 +1,5 @@ +# 流文档卡片,卡片内容为一个流文档 +# 流文档概述:https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/flow-document-overview +components: + - FlowDocumentViewer +base: Card \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/templates/Raw.yml b/src/homepagebuilder/resources/structures/templates/Raw.yml new file mode 100644 index 0000000..73d55a8 --- /dev/null +++ b/src/homepagebuilder/resources/structures/templates/Raw.yml @@ -0,0 +1,7 @@ +# 用于导入纯 xaml +components: + - $RawPresenter +base: void +filter: + file_exten: + - xaml \ No newline at end of file diff --git a/src/homepagebuilder/resources/structures/templates/RawCard.yml b/src/homepagebuilder/resources/structures/templates/RawCard.yml new file mode 100644 index 0000000..6cb133e --- /dev/null +++ b/src/homepagebuilder/resources/structures/templates/RawCard.yml @@ -0,0 +1,7 @@ +# 卡片内容为纯 xaml 的卡片 +components: + - $RawPresenter +base: CardFrame +filter: + file_exten: + - xaml \ No newline at end of file diff --git a/server.py b/src/homepagebuilder/server.py similarity index 51% rename from server.py rename to src/homepagebuilder/server.py index dd1c795..96997d7 100644 --- a/server.py +++ b/src/homepagebuilder/server.py @@ -1,5 +1,5 @@ -from Server.main import Server -from Core import config +from server.main import Server +from core import config config.fully_init() s = Server() diff --git a/src/homepagebuilder/server/__init__.py b/src/homepagebuilder/server/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Server/main.py b/src/homepagebuilder/server/main.py similarity index 59% rename from Server/main.py rename to src/homepagebuilder/server/main.py index 2a84823..5513f55 100644 --- a/Server/main.py +++ b/src/homepagebuilder/server/main.py @@ -1,13 +1,15 @@ ''' 服务器主模块 ''' -import traceback from flask import Flask, request, make_response -from Core.project import PageNotFoundError -from Core.logger import Logger -from Server.project_updater import request_update -from Server.project_api import ProjectAPI -from Core.i18n import locale as t +from typing import Tuple, Union +from ..core.project import PageNotFoundError +from ..core.logger import Logger +from .project_updater import request_update +from .project_api import ProjectAPI +from ..core.utils.property import PropertySetter +from ..core.i18n import locale as t +from ..core.config import config logger = Logger('Server') app = Flask(__name__) @@ -29,8 +31,10 @@ def get_flask_app(self): @app.route("/pull",methods=['POST']) def git_update(): '''收到 github 更新信号更新工程文件''' + if not config('process.update.github',False): + return 'pull not enabled',400 status,data = request_update(request,projapi.project_dir, - projapi.config['github_secret']) + config('server.update.github.webhook.secret')) if status == 200: projapi.clear_cache() else: @@ -45,6 +49,9 @@ def index_page(): else: return 'No Page Found',404 + + + @app.route("/") def getpage(alias:str): '''默认页面''' @@ -66,10 +73,10 @@ def getpage(alias:str): response_dict = projapi.get_page_json(alias) logger.debug(t("server.request.response.json",page=alias,args=args)) else: - response_dict = projapi.get_page_response(alias,args) + response_dict = projapi.get_page_response(alias,client=ClientArgs(request),args=args) logger.debug(t("server.request.response.page",page=alias,args=args)) response = make_response(response_dict['response']) - response.headers['Content-Type'] = response_dict['content-type'] + response.headers['Content-Type'] = response_dict['content-type'] return response except PageNotFoundError: logger.debug(t("server.request.response.not_found",page=alias,args=args)) @@ -94,6 +101,46 @@ def process_err_page_json(err_code): '''处理发生错误的 JSON 请求''' return f'{{"Title":"{err_code}"}}' +class ClientArgs: + def __init__(self,web_request): + self.is_pcl, self.is_open = self.__getpcltype(web_request=web_request) + self.pclver = self.__getpclver(web_request=web_request) + self.pclver_id = self.__getpclverid(web_request=web_request) + + def __getpcltype(self,web_request) -> Tuple[bool,Union[bool|None]]: + refer = web_request.headers.get('Referer','') + if refer.endswith('pcl2.server/'): + return True, False + if refer.endswith('pcl2.open.server/'): + return True, True + return False, None + + def __getpclverid(self,web_request): + refer = web_request.headers.get('Referer','') + if not self.is_pcl: + return None + return int(refer[7:10]) + + def __getpclver(self,web_request): + uas = web_request.headers.get('User-Agent','') + uas = uas.split() + if len(uas) >= 1: + if pclver := uas[0].split('/'): + if pclver[0] == 'PCL2': + return pclver[1] + return None + + def getsetter(self): + d = { + 'client':{ + 'ispcl': self.is_pcl, + 'isopensource': self.is_open, + 'version': self.pclver, + 'versionid': self.pclver_id + } + } + return PropertySetter(override=d) + if __name__ == "__main__": s = Server() diff --git a/Server/project_api.py b/src/homepagebuilder/server/project_api.py similarity index 66% rename from Server/project_api.py rename to src/homepagebuilder/server/project_api.py index ae7c5ed..449504d 100644 --- a/Server/project_api.py +++ b/src/homepagebuilder/server/project_api.py @@ -1,12 +1,14 @@ import os import subprocess import gc -from Core.project import Project -from Core.IO import read_string, write_string -from Core.IO.formats import read_yaml -from Core.utils import PropertySetter -from Core.logger import Logger from os.path import sep as sep +from ..core.project import Project +from ..core.builder import Builder +from ..core.io import read_string, write_string +from ..core.config import is_debugging +from ..core.utils.property import PropertySetter +from ..core.logger import Logger + logger = Logger('Server') @@ -16,21 +18,18 @@ def __init__(self,project_path = None): self.cache = {} if project_path: self.__set_project_path(project_path) - self.config_path = f"{self.project_dir}{sep}Config{sep}server_config.yml" - self.config = read_yaml(self.config_path) else: - envpath = os.path.dirname(os.path.dirname(__file__)) - self.config_path = f"{envpath}{os.path.sep}server_config.yml" - self.config = read_yaml(self.config_path) - project_path = self.config['project_path'] - self.__set_project_path(project_path) + raise NotImplementedError() try: - self.project = Project(self.project_file) - self.version_cache_path = f"{self.project_dir}{sep}Cache{sep}latest_version.cache" + self.builder = Builder() + self.project = Project(self.builder,self.project_file) + self.version_cache_path = f"{self.project_dir}{sep}cache{sep}latest_version.cache" self.default_page = self.project.default_page self.cache['version'] = self.write_latest_version_cache() except Exception as e: logger.fatal(e.args) + if is_debugging(): + raise e exit() def __set_project_path(self,path): @@ -86,10 +85,22 @@ def get_page_json(self,alias): return {'response': f'{{"Title":"{self.cache[key]}"}}', 'content-type': 'application/json'} - def get_page_response(self,alias,args = None): + def get_page_response(self,alias,client,args = None): '''获取页面内容''' if (alias,args) not in self.cache: - self.cache[(alias,args)] = { - 'response':self.project.get_page_xaml(alias,setter = PropertySetter(None,args)), - 'content-type' : self.project.get_page_content_type(alias,setter = PropertySetter(None,args)) } - return self.cache[(alias,args)] + setter = PropertySetter(None,args,False) + if len(setter) > 0: + setter.attach(client.getsetter()) + return self.get_response_dict(alias,setter,client) + else: + if rsp := self.cache.get((alias,client.pclver)): + return rsp + else: + setter.attach(client.getsetter()) + self.cache[(alias,client.pclver)] = self.get_response_dict(alias,setter,client) + return self.cache[(alias,client.pclver)] + + def get_response_dict(self,alias,setter,client): + setter.attach(client.getsetter()) + return {'response':self.project.get_page_xaml(alias,setter=setter), + 'content-type' : self.project.get_page_content_type(alias,setter=setter) } \ No newline at end of file diff --git a/Server/project_updater.py b/src/homepagebuilder/server/project_updater.py similarity index 100% rename from Server/project_updater.py rename to src/homepagebuilder/server/project_updater.py diff --git a/test/consume.bash b/test/consume.bash new file mode 100644 index 0000000..3e7823f --- /dev/null +++ b/test/consume.bash @@ -0,0 +1,2 @@ +python3 -m cProfile -o ./Test/running_data.prof main.py build /Users/morgan/Documents/Gits/PCL2-NewsHomepage/Project.yml none --dry-run +snakeviz ./Test/running_data.prof \ No newline at end of file diff --git a/test/running_data.prof b/test/running_data.prof new file mode 100644 index 0000000..6f7a94f Binary files /dev/null and b/test/running_data.prof differ