ziping
This commit is contained in:
parent
2f1f812432
commit
10a097290b
@ -4,9 +4,8 @@ mail: 3695888@qq.com
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import cProfile
|
||||
import traceback
|
||||
import threading
|
||||
import multiprocessing
|
||||
|
||||
# TODO 默认位置配置文件
|
||||
# TODO 可自定义工作路径
|
||||
@ -30,6 +29,7 @@ if __name__ == '__main__':
|
||||
print(f'{os.getcwd()=}')
|
||||
print(f'{os.path.abspath(__file__)=}')
|
||||
print(f'{os.path.realpath(__file__)=}')
|
||||
print(f'{os.path.split(os.path.split(os.path.realpath(__file__))[0])=}')
|
||||
# 输出一遍大部分文件位置相关信息 以后可能会加到logs里
|
||||
file_path = os.path.split(os.path.realpath(__file__))[0]
|
||||
os.chdir(file_path)
|
||||
@ -40,18 +40,19 @@ if __name__ == '__main__':
|
||||
|
||||
DEBUGGING = False
|
||||
from Difficult_Rocket.api.Exp import *
|
||||
|
||||
from Difficult_Rocket.crash import crash
|
||||
try:
|
||||
from Difficult_Rocket.crash import crash
|
||||
from Difficult_Rocket import main
|
||||
|
||||
game = main.Game()
|
||||
game.start()
|
||||
|
||||
cprofile = False
|
||||
if cprofile:
|
||||
cProfile.run('game.start()', sort='calls')
|
||||
else:
|
||||
game.start()
|
||||
if DEBUGGING:
|
||||
raise TestError('debugging')
|
||||
except Exception as exp:
|
||||
from Difficult_Rocket.api.translate import tr
|
||||
from Difficult_Rocket.translate import tr
|
||||
|
||||
print(error_format['error.happen'])
|
||||
error = traceback.format_exc()
|
||||
|
@ -14,6 +14,7 @@ gitee: @shenjackyuanjie
|
||||
version = '0.6.1'
|
||||
__version__ = version
|
||||
|
||||
|
||||
playing = False
|
||||
|
||||
if playing:
|
||||
|
@ -12,7 +12,6 @@ gitee: @shenjackyuanjie
|
||||
"""
|
||||
|
||||
# 单独导入的(或者就这一个有用的)
|
||||
from .translate import Lang
|
||||
from .delivery import Delivery
|
||||
from .new_thread import new_thread
|
||||
|
||||
@ -27,6 +26,5 @@ __all__ = ['TexturesError',
|
||||
'TestError',
|
||||
'new_thread',
|
||||
'Delivery',
|
||||
'load_file',
|
||||
'Lang']
|
||||
'load_file']
|
||||
|
||||
|
@ -23,6 +23,8 @@ import configparser
|
||||
from typing import List
|
||||
from xml.dom.minidom import parse
|
||||
|
||||
import Difficult_Rocket
|
||||
|
||||
if __name__ == '__main__': # been start will not run this
|
||||
sys.path.append('/bin/libs')
|
||||
sys.path.append('/bin')
|
||||
@ -35,63 +37,44 @@ tools_logger = logging.getLogger('part-tools')
|
||||
file configs
|
||||
"""
|
||||
|
||||
|
||||
def report_file_error(filetype: str, error_type, filename: str, stack: any):
|
||||
error = traceback.format_exc()
|
||||
if isinstance(error_type, FileNotFoundError):
|
||||
log = 'no {} file was found!: \n file name: {} \n file type: {} \n stack: {} \n traceback: {}'.format(filetype, filename, filetype, stack,
|
||||
error)
|
||||
elif isinstance(error_type, KeyError):
|
||||
log = 'no stack in %s file: %s \n file type: %s \n stack: %s' % (filetype, filename, filetype, stack)
|
||||
else:
|
||||
log = 'some error has been found! \n error type: %s \n file name: %s \n file type: %s \n stack: %s' % (error_type, filename, filetype, stack)
|
||||
tools_logger.exception(log)
|
||||
file_error = {'FileNotFoundError': 'no {filetype} file was founded!:\n file name: {filename}\n file_type: {filetype}\n stack: {stack}',
|
||||
'KeyError': 'no stack in {filetype} file {filename} was found! \n file type: {} \n file name: {} \n stack: {stack}',
|
||||
'Error': 'get some unknown error when read {filetype} file {filename}! \n file type: {} \n file name: {} \n stack: {stack}'}
|
||||
|
||||
|
||||
def load_file(file_name: str, stack=None) -> dict:
|
||||
f_type = file_name[file_name.rfind('.') + 1:] # 从最后一个.到末尾 (截取文件格式)
|
||||
try:
|
||||
rd = NotImplementedError('解析失败,请检查文件类型/文件内容/文件是否存在!')
|
||||
if (f_type == 'json5') or (f_type == 'json'):
|
||||
try:
|
||||
with open(file_name, 'r', encoding='utf-8') as jf: # jf -> json file
|
||||
rd = json5.load(jf)
|
||||
rd = json5.load(jf, encoding='uft-8')
|
||||
except UnicodeDecodeError:
|
||||
with open(file_name, 'r', encoding='gbk') as jf:
|
||||
rd = json5.load(jf)
|
||||
tools_logger.info('文件 %s 解码错误,已重新使用gbk编码打开' % file_name)
|
||||
if stack is not None:
|
||||
rd = rd[stack]
|
||||
return rd
|
||||
elif f_type == 'xml':
|
||||
xml_load = parse(file_name)
|
||||
if stack is not None:
|
||||
xml_get = xml_load.getElementsByTagName(stack)
|
||||
return xml_get
|
||||
else:
|
||||
return xml_load
|
||||
rd = xml_load.getElementsByTagName(stack)
|
||||
elif (f_type == 'config') or (f_type == 'conf') or (f_type == 'ini'):
|
||||
cp = configparser.ConfigParser() # cp -> config parser
|
||||
cp.read(file_name) # config parser -> reader
|
||||
rd = {}
|
||||
for section in cp.sections():
|
||||
rd[section] = {}
|
||||
for data in cp[section]:
|
||||
rd[section][data] = cp[section][data]
|
||||
cd = configparser.ConfigParser()
|
||||
cd.read(file_name)
|
||||
if stack:
|
||||
rd = rd[stack]
|
||||
return rd
|
||||
except FileNotFoundError:
|
||||
log = 'no {} file was found!: \n file name: {} \n file type: {} \n stack: {}'.format(f_type, file_name, f_type, stack)
|
||||
tools_logger.error(log)
|
||||
raise
|
||||
except KeyError:
|
||||
log = 'no stack in {} file {} was found! \n file type: {} \n file name: {} \n stack: {}'.format(f_type, file_name, f_type, file_name, stack)
|
||||
tools_logger.error(log)
|
||||
raise
|
||||
rd = cd[stack]
|
||||
else:
|
||||
rd = cd
|
||||
except Exception as exp:
|
||||
log = 'some error has been found!\n error type: {} \n file name: {} \n file type: {} \n stack: {}'.format(type(exp), file_name, f_type, stack)
|
||||
tools_logger.error(log)
|
||||
error_type = type(exp).__name__
|
||||
if error_type in file_error:
|
||||
tools_logger.error(file_error[error_type].format(filetype=f_type, filename=file_name, stack=stack))
|
||||
else:
|
||||
tools_logger.error(file_error['Error'].format(filetype=f_type, filename=file_name, stack=stack))
|
||||
raise
|
||||
return rd
|
||||
|
||||
|
||||
# main config
|
||||
|
@ -26,16 +26,20 @@ if __name__ == '__main__': # been start will not run this
|
||||
sys.path.append('/bin')
|
||||
|
||||
# Difficult_Rocket function
|
||||
from .command import line
|
||||
from .api.translate import tr
|
||||
from .fps.fps_log import FpsLogger
|
||||
from .api import tools, new_thread, translate
|
||||
from .api.Exp import *
|
||||
import translate
|
||||
|
||||
from Difficult_Rocket.api.Exp import *
|
||||
from Difficult_Rocket.translate import tr
|
||||
from Difficult_Rocket.command import line
|
||||
from Difficult_Rocket.fps.fps_log import FpsLogger
|
||||
from Difficult_Rocket.guis.widgets import InputBox
|
||||
from Difficult_Rocket.api import tools, new_thread
|
||||
|
||||
# libs function
|
||||
local_lib = True
|
||||
if local_lib:
|
||||
from libs import pyglet
|
||||
from libs.pyglet.window import Window
|
||||
from libs.pyglet.window import key, mouse
|
||||
else:
|
||||
import pyglet
|
||||
@ -73,7 +77,7 @@ class Client:
|
||||
# TODO 写一下服务端启动相关,还是需要服务端啊
|
||||
|
||||
|
||||
class ClientWindow(pyglet.window.Window):
|
||||
class ClientWindow(Window):
|
||||
|
||||
def __init__(self, net_mode='local', *args, **kwargs):
|
||||
start_time = time.time_ns()
|
||||
@ -92,14 +96,14 @@ class ClientWindow(pyglet.window.Window):
|
||||
# configs
|
||||
pyglet.resource.path = ['textures']
|
||||
pyglet.resource.reindex()
|
||||
self.config_file = tools.load_file('configs/main.config')
|
||||
self.main_config = tools.load_file('configs/main.config')
|
||||
self.game_config = tools.load_file('configs/game.config')
|
||||
# dic
|
||||
self.environment = {}
|
||||
self.textures = {} # all textures
|
||||
self.runtime = {}
|
||||
# FPS
|
||||
self.FPS = Decimal(int(self.config_file['runtime']['fps']))
|
||||
self.FPS = Decimal(int(self.main_config['runtime']['fps']))
|
||||
self.SPF = Decimal('1') / self.FPS
|
||||
self.fps_log = FpsLogger(stable_fps=int(self.FPS),
|
||||
wait_time=5)
|
||||
@ -120,10 +124,16 @@ class ClientWindow(pyglet.window.Window):
|
||||
self.push_handlers(self.command)
|
||||
self.command.set_handler('on_command', self.on_command)
|
||||
self.command.set_handler('on_message', self.on_message)
|
||||
self.input_box = InputBox(x=50, y=30, width=300, height=20,
|
||||
batch=self.label_batch) # 实例化
|
||||
self.push_handlers(self.input_box)
|
||||
self.input_box.enabled = True
|
||||
# fps显示
|
||||
self.fps_label = pyglet.text.Label(x=10, y=self.height - 10,
|
||||
width=self.width - 20, height=20,
|
||||
anchor_x='left', anchor_y='top',
|
||||
font_name=translate.鸿蒙简体, font_size=20,
|
||||
font_name=translate.微软等宽无线, font_size=20,
|
||||
multiline=True,
|
||||
batch=self.label_batch, group=self.command_group)
|
||||
# 设置刷新率
|
||||
pyglet.clock.schedule_interval(self.update, float(self.SPF))
|
||||
@ -136,18 +146,21 @@ class ClientWindow(pyglet.window.Window):
|
||||
self.logger.debug(tr.lang('window', 'setup.use_time_ns').format(self.use_time))
|
||||
|
||||
def setup(self):
|
||||
self.load_fonts().join()
|
||||
self.load_fonts()
|
||||
|
||||
@new_thread('window load_fonts')
|
||||
def load_fonts(self):
|
||||
file_path = './libs/fonts/HarmonyOS_Sans/'
|
||||
ttf_files = os.listdir(file_path)
|
||||
self.logger.info(tr.lang('window', 'fonts.found').format(ttf_files))
|
||||
for ttf_folder in ttf_files:
|
||||
for ttf_file in os.listdir(f'{file_path}{ttf_folder}'):
|
||||
if not ttf_file[-4:] == '.ttf':
|
||||
continue
|
||||
pyglet.font.add_file(f'{file_path}{ttf_folder}/{ttf_file}')
|
||||
fonts_folder_path = self.main_config['runtime']['fonts_folder']
|
||||
# 加载字体路径
|
||||
for fonts_folders in os.listdir(fonts_folder_path):
|
||||
# 从字体路径内加载字体文件夹
|
||||
for files in os.listdir(os.path.join(fonts_folder_path, fonts_folders)):
|
||||
# 从字体文件夹加载字体(或是字体类文件夹)
|
||||
if os.path.isfile(os.path.join(fonts_folder_path, fonts_folders, files)):
|
||||
# 如果是字体文件,则直接加载
|
||||
pyglet.font.add_file(os.path.join(fonts_folder_path, fonts_folders, files))
|
||||
else: # 否则,遍历加载字体类文件夹并加载
|
||||
for font in os.listdir(os.path.join(fonts_folder_path, fonts_folders, files)):
|
||||
pyglet.font.add_file(os.path.join(fonts_folder_path, fonts_folders, files, font))
|
||||
|
||||
# @new_thread('window load_editor')
|
||||
def load_Editor(self):
|
||||
@ -168,7 +181,7 @@ class ClientWindow(pyglet.window.Window):
|
||||
if get == 'stop':
|
||||
self.run_input = False
|
||||
try:
|
||||
self.on_command(get)
|
||||
self.on_command(line.CommandText(get))
|
||||
except CommandError:
|
||||
self.logger.error(traceback.format_exc())
|
||||
self.logger.debug('read_input end')
|
||||
@ -193,9 +206,9 @@ class ClientWindow(pyglet.window.Window):
|
||||
def FPS_update(self, tick: Decimal):
|
||||
now_FPS = pyglet.clock.get_fps()
|
||||
self.fps_log.update_tick(tick)
|
||||
self.fps_label.text = f'FPS: {now_FPS:>13.1f} {self.fps_log.max_fps:>5.1f} {self.fps_log.min_fps:>5.1f}'
|
||||
self.fps_label.text = f'FPS: {now_FPS: >10.1f} \n{1/tick} \n{self.fps_log.max_fps: >10.1f} {self.fps_log.min_fps:>5.1f}'
|
||||
|
||||
def on_draw(self):
|
||||
def on_draw(self, *dt):
|
||||
self.clear()
|
||||
self.draw_batch()
|
||||
|
||||
@ -211,20 +224,21 @@ class ClientWindow(pyglet.window.Window):
|
||||
command line event
|
||||
"""
|
||||
|
||||
def on_command(self, command: line.CommandLine.text):
|
||||
def on_command(self, command: line.CommandText):
|
||||
self.logger.info(tr.lang('window', 'command.text').format(command))
|
||||
if command == 'stop':
|
||||
if command.match('stop'):
|
||||
self.dispatch_event('on_close', 'command') # source = command
|
||||
elif command == 'log_tick':
|
||||
self.logger.debug(self.fps_log.fps_list)
|
||||
elif command == 'max_fps':
|
||||
self.logger.info(self.fps_log.max_fps)
|
||||
self.command.push_line(self.fps_log.max_fps, block_line=True)
|
||||
elif command == 'min_fps':
|
||||
self.logger.info(self.fps_log.min_fps)
|
||||
self.command.push_line(self.fps_log.min_fps, block_line=True)
|
||||
elif command == 'default':
|
||||
self.set_size(int(self.config_file['window_default']['width']), int(self.config_file['window_default']['height']))
|
||||
elif command.match('fps'):
|
||||
if command.match('log'):
|
||||
self.logger.debug(self.fps_log.fps_list)
|
||||
elif command.match('max'):
|
||||
self.logger.info(self.fps_log.max_fps)
|
||||
self.command.push_line(self.fps_log.max_fps, block_line=True)
|
||||
elif command.match('min'):
|
||||
self.logger.info(self.fps_log.min_fps)
|
||||
self.command.push_line(self.fps_log.min_fps, block_line=True)
|
||||
elif command.match('default'):
|
||||
self.set_size(int(self.main_config['window_default']['width']), int(self.main_config['window_default']['height']))
|
||||
|
||||
def on_message(self, message: line.CommandLine.text):
|
||||
self.logger.info(tr.lang('window', 'message.text').format(message))
|
||||
@ -260,6 +274,8 @@ class ClientWindow(pyglet.window.Window):
|
||||
self.logger.debug(tr.lang('window', 'text.new_line'))
|
||||
else:
|
||||
self.logger.debug(tr.lang('window', 'text.input').format(text))
|
||||
if text == 't':
|
||||
self.input_box.enabled = True
|
||||
|
||||
def on_text_motion(self, motion):
|
||||
motion_string = key.motion_string(motion)
|
||||
|
@ -12,22 +12,77 @@ gitee: @shenjackyuanjie
|
||||
"""
|
||||
|
||||
import time
|
||||
import re
|
||||
|
||||
from typing import Union
|
||||
from decimal import Decimal
|
||||
|
||||
# from DR
|
||||
from Difficult_Rocket.api import translate, new_thread
|
||||
from Difficult_Rocket import translate
|
||||
from Difficult_Rocket.api import new_thread
|
||||
from Difficult_Rocket.guis.widgets import InputBox
|
||||
|
||||
# from libs.pyglet
|
||||
from libs import pyglet
|
||||
from libs.pyglet import font
|
||||
from libs.pyglet.text import Label
|
||||
from libs.pyglet.window import key
|
||||
from libs.pyglet.gui import widgets
|
||||
from libs.pyglet.graphics import Batch, Group
|
||||
|
||||
|
||||
class CommandText:
|
||||
"""
|
||||
CommandLine返回的字符,可以用来搜索
|
||||
"""
|
||||
|
||||
def __init__(self, text: str):
|
||||
self.text = text
|
||||
self.value_dict = {}
|
||||
self.value_list = []
|
||||
|
||||
def find(self, text: str) -> bool:
|
||||
finding = re.match(text, self.text)
|
||||
if finding:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def match(self, text: str) -> bool:
|
||||
finding = re.match(text, self.text)
|
||||
if finding: # 如果找到了
|
||||
try:
|
||||
next_find = self.text[finding.span()[1]]
|
||||
# 这里try因为可能匹配到的是字符串末尾
|
||||
except IndexError:
|
||||
next_find = ' '
|
||||
# 直接过滤掉
|
||||
if next_find == ' ':
|
||||
self.text = self.text[finding.span()[1] + 1:]
|
||||
return True
|
||||
# 将匹配到的字符串,和最后一个匹配字符后面的字符删除(相当暴力的操作)
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
||||
def greedy(self, name: str = None) -> str:
|
||||
if name:
|
||||
self.value_dict[name] = self.text
|
||||
self.value_list.append(self.text)
|
||||
return self.text
|
||||
|
||||
def value(self,
|
||||
name: str = None,
|
||||
split: str = ' ',
|
||||
middle: list = ('\'', '\"')):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return str(self.text)
|
||||
|
||||
def __int__(self):
|
||||
return int(self.text)
|
||||
|
||||
|
||||
class CommandLine(widgets.WidgetBase):
|
||||
"""
|
||||
command line show
|
||||
@ -66,7 +121,7 @@ class CommandLine(widgets.WidgetBase):
|
||||
self._line = Label(x=x, y=y, batch=batch, text=self.text,
|
||||
color=(100, 255, 255, 255),
|
||||
anchor_x='left', anchor_y='bottom',
|
||||
font_size=font_size, font_name=translate.鸿蒙简体,
|
||||
font_size=font_size, font_name=translate.微软等宽,
|
||||
group=fg_group)
|
||||
self._label = [Label(x=x + 10, y=y + self.command_distance + (line * self.command_split), batch=batch, text='a',
|
||||
anchor_x='left', anchor_y='bottom',
|
||||
@ -184,14 +239,16 @@ class CommandLine(widgets.WidgetBase):
|
||||
"""
|
||||
|
||||
def on_text(self, text):
|
||||
# 这里的大部分东西都会在最近被重写
|
||||
# TODO 重写成基于新的 InputBox 的解析
|
||||
if self.editing:
|
||||
if text in ('\r', '\n'): # goto a new line
|
||||
if not self.text:
|
||||
pass
|
||||
elif self.text[0] == self._command_text:
|
||||
self.dispatch_event('on_command', self.text[1:])
|
||||
self.dispatch_event('on_command', CommandText(self.text[1:]))
|
||||
else:
|
||||
self.dispatch_event('on_message', self.text)
|
||||
self.dispatch_event('on_message', CommandText(self.text))
|
||||
# on_message 和 on_command 可能会覆盖 self.text 需要再次判定
|
||||
if self.text:
|
||||
self.command_view = -1
|
||||
|
22
Difficult_Rocket/command/tree.py
Normal file
22
Difficult_Rocket/command/tree.py
Normal file
@ -0,0 +1,22 @@
|
||||
# -------------------------------
|
||||
# Difficult Rocket
|
||||
# Copyright © 2021 by shenjackyuanjie
|
||||
# All rights reserved
|
||||
# -------------------------------
|
||||
|
||||
"""
|
||||
writen by shenjackyuanjie
|
||||
mail: 3695888@qq.com
|
||||
github: @shenjackyuanjie
|
||||
gitee: @shenjackyuanjie
|
||||
"""
|
||||
|
||||
import line
|
||||
|
||||
|
||||
class CommandTree:
|
||||
def __init__(self, command_tree_dict):
|
||||
self.command_tree_dict = command_tree_dict
|
||||
|
||||
def parse(self, command: line.CommandText):
|
||||
pass
|
@ -51,6 +51,7 @@ class FpsLogger:
|
||||
# 获取当前信息
|
||||
if now_fps > self.max_fps and not self.time - self._max_fps_tick >= self.wait_time:
|
||||
self.max_fps = now_fps
|
||||
# self.max_fps = max(self.max_fps, now_fps)
|
||||
tick_time = self.time - self.fps_list[-1][1]
|
||||
# 获取渲染用时
|
||||
self.fps_list.append([now_fps, self.time, tick_time, tick])
|
||||
@ -74,14 +75,15 @@ class FpsLogger:
|
||||
|
||||
@max_fps.setter
|
||||
def max_fps(self, value):
|
||||
if self.time - self._max_fps_tick <= self.wait_time:
|
||||
self._max_fps = value
|
||||
self._max_fps_tick = time.time_ns()
|
||||
else:
|
||||
max_fps = self.list_max_fps
|
||||
self._max_fps = max_fps[0]
|
||||
self._max_fps_tick = max_fps[1]
|
||||
del max_fps
|
||||
# if self.time - self._max_fps_tick <= self.wait_time:
|
||||
# self._max_fps = value
|
||||
# self._max_fps_tick = time.time_ns()
|
||||
# else:
|
||||
# max_fps = self.list_max_fps
|
||||
# self._max_fps = max_fps[0]
|
||||
# self._max_fps_tick = max_fps[1]
|
||||
self._max_fps = value
|
||||
self._max_fps_tick = self.time
|
||||
|
||||
@property
|
||||
def min_fps(self):
|
||||
|
@ -13,14 +13,18 @@ gitee: @shenjackyuanjie
|
||||
|
||||
from Difficult_Rocket import translate
|
||||
|
||||
# from libs import pyglet
|
||||
from libs import pyglet
|
||||
from libs.pyglet import font
|
||||
from libs.pyglet.text import Label
|
||||
from libs.pyglet.window import key
|
||||
from libs.pyglet.gui import widgets
|
||||
from libs.pyglet.sprite import Sprite
|
||||
from libs.pyglet.shapes import Rectangle
|
||||
from libs.pyglet.graphics import Batch, Group
|
||||
from libs.pyglet.image import AbstractImage
|
||||
from libs.pyglet.graphics import Batch, Group
|
||||
# from libs import pyperclip
|
||||
from libs.pyperclip import paste, copy
|
||||
|
||||
__all__ = ['Parts']
|
||||
|
||||
@ -49,54 +53,177 @@ class InputBox(widgets.WidgetBase):
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
x: int,
|
||||
y: int,
|
||||
width: int,
|
||||
height: int,
|
||||
x: int, y: int, width: int, height: int,
|
||||
message: str = '',
|
||||
font_name: str = translate.鸿蒙简体,
|
||||
font_size: int = 15,
|
||||
text_color: [int, int, int] = (0, 0, 0, 255),
|
||||
out_line_color: [int, int, int] = (255, 255, 255),
|
||||
|
||||
font_bold: bool = False,
|
||||
font_italic: bool = False,
|
||||
font_stretch: bool = False,
|
||||
font_dpi: int = 100,
|
||||
text_color: [int, int, int] = (187, 187, 187, 255),
|
||||
out_line_color: [int, int, int] = (37, 116, 176),
|
||||
cursor_color: [int, int, int] = (187, 187, 187),
|
||||
select_color: [int, int, int] = (63, 115, 255),
|
||||
out_line: int = 2,
|
||||
batch: Batch = Batch(),
|
||||
group: Group = Group()):
|
||||
super().__init__(x, y, width, height)
|
||||
self.enabled = False
|
||||
self._text = message
|
||||
self.text = self._text
|
||||
self.字体 = font.load(font_name, font_size)
|
||||
self.字高 = self.字体.ascent - self.字体.descent
|
||||
self.外框距离 = out_line
|
||||
self._输入框 = Label(x=x + out_line, y=y + out_line,
|
||||
width=width, height=height,
|
||||
color=text_color,
|
||||
font_name=font_name, font_size=font_size,
|
||||
batch=batch, group=group,
|
||||
text=message)
|
||||
self._外框 = Rectangle(x=x-out_line, y=y-out_line,
|
||||
color=out_line_color,
|
||||
width=width + (out_line * 2), height=height + (out_line * 2),
|
||||
batch=batch, group=group)
|
||||
self._光标 = Rectangle(x=x+out_line, y=y+out_line,
|
||||
width=1, height=self.字高,
|
||||
self._cursor_poi = 0
|
||||
self.font = font.load(name=font_name, size=font_size,
|
||||
bold=font_bold, italic=font_italic, stretch=font_stretch,
|
||||
dpi=font_dpi)
|
||||
self.font_height = self.font.ascent - self.font.descent
|
||||
self.out_bound = out_line
|
||||
self._input_box = Label(x=x + out_line, y=y + out_line,
|
||||
width=width, height=height,
|
||||
color=text_color,
|
||||
font_name=font_name, font_size=font_size,
|
||||
batch=batch, group=group,
|
||||
text=message)
|
||||
self._out_box = Rectangle(x=x - out_line, y=y - out_line,
|
||||
color=out_line_color,
|
||||
width=width + (out_line * 2), height=height + (out_line * 2),
|
||||
batch=batch, group=group)
|
||||
self._光标 = Rectangle(x=x + out_line, y=y + out_line,
|
||||
color=cursor_color,
|
||||
width=1, height=self.font_height,
|
||||
batch=batch, group=group)
|
||||
self._选择框 = Rectangle(x=x, y=y, width=0, height=self.font_height,
|
||||
color=select_color)
|
||||
self._选择的字 = Label(x=x, y=y, width=0, height=self.font_height,
|
||||
color=text_color,
|
||||
font_name=font_name, font_size=font_size,
|
||||
batch=batch, group=group,
|
||||
text='')
|
||||
|
||||
"""
|
||||
输入框的属性
|
||||
"""
|
||||
|
||||
# 本身属性
|
||||
@property
|
||||
def text(self):
|
||||
def text(self) -> str: # 输入框的文本
|
||||
return self._text
|
||||
|
||||
@text.setter
|
||||
def text(self, value):
|
||||
def text(self, value) -> None:
|
||||
assert type(value) is str, 'Input Box\'s text must be string!'
|
||||
self._text = value
|
||||
self._输入框.text = value
|
||||
self._input_box.text = value
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.text
|
||||
def cursor_poi(self) -> int: # 光标位置
|
||||
return self._cursor_poi
|
||||
|
||||
@cursor_poi.setter
|
||||
def cursor_poi(self, value) -> None:
|
||||
assert type(value) is int, 'Input Box\'s cursor poi must be int!'
|
||||
self._cursor_poi = value
|
||||
add_x = self.x + self.out_bound
|
||||
for glyph in self.font.get_glyphs(self.text[:value]):
|
||||
add_x += glyph.width
|
||||
self._光标.x = add_x
|
||||
|
||||
# 渲染时属性
|
||||
@property
|
||||
def opacity(self) -> int: # 透明度
|
||||
return self._input_box.opacity
|
||||
|
||||
@opacity.setter
|
||||
def opacity(self, value: int) -> None:
|
||||
assert type(value) is int, 'Input Box\'s opacity must be int!'
|
||||
self._input_box.opacity = value
|
||||
self._out_box.opacity = value
|
||||
self._选择的字.opacity = value
|
||||
self._选择框.opacity = value
|
||||
self._光标.opacity = value
|
||||
|
||||
@property
|
||||
def visible(self) -> bool: # 是否可见
|
||||
return self._input_box.visible
|
||||
|
||||
@visible.setter
|
||||
def visible(self, value: bool) -> None:
|
||||
assert type(value) is bool, 'Input Box\'s visible must be bool!'
|
||||
self._input_box.visible = value
|
||||
self._out_box.visible = value
|
||||
self._选择的字.visible = value
|
||||
self._选择框.visible = value
|
||||
self._光标.visible = value
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
return self._text
|
||||
|
||||
"""
|
||||
事件调用
|
||||
"""
|
||||
|
||||
def _update_position(self):
|
||||
self._输入框.position = self._x + self.外框距离, self._y + self.外框距离
|
||||
self._外框.position = self._x - self.外框距离, self._y - self.外框距离
|
||||
self._光标.position = self._x + self.外框距离, self._y + self.外框距离
|
||||
self._input_box.position = self._x + self.out_bound, self._y + self.out_bound
|
||||
self._out_box.position = self._x - self.out_bound, self._y - self.out_bound
|
||||
self._光标.position = self._x + self.out_bound, self._y + self.out_bound
|
||||
|
||||
# 输入东西
|
||||
def on_text(self, text: str):
|
||||
if self.enabled:
|
||||
if text in ('\r', '\n'):
|
||||
if self.text:
|
||||
self.dispatch_event('on_commit', self.text)
|
||||
else:
|
||||
self.text = f'{self.text[:self.cursor_poi]}{text}{self.text[self.cursor_poi:]}'
|
||||
self.cursor_poi += len(text)
|
||||
|
||||
# 移动光标
|
||||
def on_text_motion(self, motion):
|
||||
if self.enabled:
|
||||
# 根据按键处理
|
||||
# 单格移动光标(上下左右)
|
||||
if motion in (key.MOTION_UP, key.MOTION_LEFT): # 往上一个移动
|
||||
self.cursor_poi = max(0, self._cursor_poi - 1)
|
||||
elif motion in (key.MOTION_DOWN, key.MOTION_RIGHT): # 往下一个移动
|
||||
self.cursor_poi = min(len(self.text), self._cursor_poi + 1)
|
||||
# 大前后移动(开头或结尾)
|
||||
elif motion in (key.MOTION_BEGINNING_OF_LINE, key.MOTION_BEGINNING_OF_FILE, key.MOTION_PREVIOUS_PAGE): # 开头
|
||||
self.cursor_poi = 0
|
||||
elif motion in (key.MOTION_END_OF_LINE, key.MOTION_END_OF_FILE, key.MOTION_NEXT_PAGE): # 结尾
|
||||
self.cursor_poi = len(self.text)
|
||||
# 删除操作
|
||||
elif motion == key.MOTION_BACKSPACE:
|
||||
if self.text: # 如果有文字
|
||||
self.text = f'{self.text[:self.cursor_poi - 1]}{self.text[self.cursor_poi:]}'
|
||||
self.cursor_poi = max(0, self._cursor_poi - 1)
|
||||
elif motion == key.MOTION_DELETE:
|
||||
if self.text and self.cursor_poi != len(self.text) - 1: # 如果有文字,并且光标不在最后
|
||||
self.text = f'{self.text[:self.cursor_poi]}{self.text[self.cursor_poi + 1:]}'
|
||||
# 剪贴板操作
|
||||
elif motion == key.MOTION_COPY:
|
||||
pass
|
||||
elif motion == key.MOTION_PASTE:
|
||||
paste_text = paste()
|
||||
self.text = f'{self.text[:self.cursor_poi]}{paste_text}{self.text[self.cursor_poi:]}'
|
||||
self.cursor_poi += len(paste_text)
|
||||
|
||||
def on_text_motion_select(self, motion):
|
||||
pass
|
||||
|
||||
def on_mouse_press(self, x, y, buttons, modifiers):
|
||||
if self._check_hit(x, y) and self._input_box.visible:
|
||||
self.enabled = True
|
||||
else:
|
||||
self.enabled = False
|
||||
|
||||
def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
|
||||
pass
|
||||
|
||||
def on_mouse_release(self, x, y, buttons, modifiers):
|
||||
pass
|
||||
|
||||
def on_commit(self, text: str):
|
||||
pass
|
||||
|
||||
|
||||
InputBox.register_event_type('on_commit')
|
||||
|
@ -24,8 +24,8 @@ if __name__ == '__main__': # been start will not run this
|
||||
sys.path.append('/bin')
|
||||
|
||||
from Difficult_Rocket import client, server
|
||||
from Difficult_Rocket.api import tools, thread, translate
|
||||
from Difficult_Rocket.api.translate import tr
|
||||
from Difficult_Rocket.api import tools
|
||||
from Difficult_Rocket.translate import tr
|
||||
|
||||
|
||||
class Game:
|
||||
|
@ -95,17 +95,19 @@ tr = Lang('zh-CN')
|
||||
HOS = 'HarmonyOS Sans'
|
||||
HOS_S = 'HarmonyOS Sans SC'
|
||||
HOS_T = 'HarmonyOS Sans TC'
|
||||
HOS_I = 'HarmonyOS Sans Italic'
|
||||
HOS_C = 'HarmonyOS Sans Condensed'
|
||||
HOS_CI = 'HarmonyOS Sans Condensed Italic'
|
||||
HOS_NA = 'HarmonyOS Sans Naskh Arabic'
|
||||
HOS_NAU = 'HarmonyOS Sans Naskh Arabic_UI'
|
||||
|
||||
鸿蒙字体 = HOS
|
||||
鸿蒙简体 = HOS_S
|
||||
鸿蒙繁体 = HOS_T
|
||||
鸿蒙斜体 = HOS_I
|
||||
鸿蒙窄体 = HOS_C
|
||||
鸿蒙斜窄体 = HOS_CI
|
||||
鸿蒙阿拉伯 = HOS_NA
|
||||
鸿蒙阿拉伯UI = HOS_NAU
|
||||
|
||||
CC = 'Cascadia Code'
|
||||
CM = 'Cascadia Mono'
|
||||
CCPL = 'Cascadia Code PL'
|
||||
CMPL = 'Cascadia Mono PL'
|
||||
|
||||
微软等宽 = CC
|
||||
微软等宽无线 = CM
|
||||
微软等宽带电线 = CCPL
|
||||
微软等宽带电线无线 = CMPL
|
||||
|
62
README.md
62
README.md
@ -1,21 +1,22 @@
|
||||
# Difficult Rocket
|
||||
|
||||
- [github](https://github.com/shenjackyuanjie/Difficult-Rocket)
|
||||
- [gitee](https://gitee.com/shenjackyuanjie/Difficult-Rocket)
|
||||
|
||||
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
|
||||
[![Generic badge](https://img.shields.io/badge/Write_with_Python-3.8.10-blue.svg)](https://Python.org)
|
||||
[![Generic badge](https://img.shields.io/badge/Write_with_Pyglet-2.0dev11-blue.svg)](https://pyglet.org)
|
||||
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9-blue.svg)](https://Python.org)
|
||||
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_-blue.svg)](https://Python.org)
|
||||
|
||||
## Version
|
||||
|
||||
[![Generic badge](https://img.shields.io/badge/Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases/v0.4.5)
|
||||
<br/>[![Generic badge](https://img.shields.io/badge/Pre_Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases/v0.4.6)
|
||||
<br/>![Generic badge](https://img.shields.io/badge/Devloping-0.6.1-blue.svg)
|
||||
[![Generic badge](https://img.shields.io/badge/Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
|
||||
<br/>[![Generic badge](https://img.shields.io/badge/Pre_Release-0.6.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
|
||||
<br/>![Generic badge](https://img.shields.io/badge/Devloping-0.6.2-blue.svg)
|
||||
|
||||
## 中文README请移步 [这里](/docs/README-cn.md)
|
||||
|
||||
> It's a Simple Rocket liked game build with Python
|
||||
> It's a Simple Rocket liked game build with Python (in short: rocket simulator)
|
||||
|
||||
## Advantage
|
||||
|
||||
@ -27,35 +28,40 @@
|
||||
|
||||
## Environment (been tested / develop on)
|
||||
|
||||
-
|
||||
- `Develop platform 1 - Windows 10`
|
||||
- `Python 3.8.10`
|
||||
- `Windows10 x64`
|
||||
- `pyglet 2.0.dev11`
|
||||
- `Json5 0.9.6`
|
||||
- `pillow 8.1.0`
|
||||
- `AMD R5 5600X`
|
||||
- `AMD RX 550 4G`
|
||||
-
|
||||
- `Develop platform 2 - macOS Big Sur`
|
||||
- `Python 3.8.7`
|
||||
- `macOS Big Sur 11.2.1`
|
||||
- `Pyglet 1.5.15`
|
||||
- `Json5 0.9.5`
|
||||
- `pillow 8.1.2`
|
||||
- `Intel I5 8279U`
|
||||
- `Develop platform 1 - Windows 10`
|
||||
- `Python 3.8.10`
|
||||
- `Windows10 x64`
|
||||
- `json5 0.9.6`
|
||||
- `pillow 8.1.0`
|
||||
- `pyperclip 1.8.2`
|
||||
- `pyglet 2.0.dev11`
|
||||
- `xmltodict 0.12.0`
|
||||
- `AMD R5 5600X`
|
||||
- `AMD RX 550 4G`
|
||||
- `Develop platform 2 - macOS Big Sur`
|
||||
- `Python 3.8.7`
|
||||
- `macOS Big Sur 11.2.1`
|
||||
- `Pyglet 1.5.15`
|
||||
- `Json5 0.9.5`
|
||||
- `pillow 8.1.2`
|
||||
- `Intel I5 8279U`
|
||||
|
||||
## Required python modules
|
||||
|
||||
- json5 (pre-installed V0.9.6 path:`./libs/json5`)
|
||||
- pyglet (pre-installed V2.0.dev11 path:`./libs/pyglet`)
|
||||
- pillow
|
||||
- semver
|
||||
- `json5` (pre-installed V0.9.6 path:`./libs/json5`)
|
||||
- `pyglet` (pre-installed V2.0.dev11 path:`./libs/pyglet`)
|
||||
- `xmltodict` (pre-installed V0.12.0 path:`./libs/xmltodict`)
|
||||
- `pyperclip` (pre-installed V1.8.2 path: `./libs/pyperclip`)
|
||||
- `pillow`
|
||||
- `semver`
|
||||
|
||||
## thanks to
|
||||
|
||||
- pyglet : GUI
|
||||
- [@rouxiao-you](https://github.com/ruoxiao-you) : transfer chinese to English
|
||||
- [pyglet](https://github.com/pyglet/pyglet): GUI
|
||||
- `json5`: json5 parser
|
||||
- `xmltodict`: translate data between xml and dict
|
||||
- `pyperclip`: paste board!
|
||||
- [@rouxiao-you](https://github.com/ruoxiao-you) : translate chinese to English
|
||||
- [@Rayawa](https://github.com/Rayawa) : check mistake in docs
|
||||
- [@Billchyi](https://github.com/Billchyi) : check mistake in docs
|
||||
|
||||
|
@ -4,6 +4,7 @@ version = 0.6.0
|
||||
language = zh-CN
|
||||
date_fmt = %%Y-%%m-%%d %%H-%%M-%%S
|
||||
write_py_v = 3.8.10
|
||||
fonts_folder = libs/fonts
|
||||
|
||||
[window]
|
||||
style = None
|
||||
|
@ -6,17 +6,17 @@
|
||||
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
|
||||
[![Generic badge](https://img.shields.io/badge/编写于_Python_版本-3.8.10-blue.svg)](https://Python.org)
|
||||
[![Generic badge](https://img.shields.io/badge/编写于_Pyglet_版本-2.0dev11-blue.svg)](https://pyglet.org)
|
||||
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9-blue.svg)](https://Python.org)
|
||||
[![Generic badge](https://img.shields.io/badge/Python-_3.8_|_3.9_|_3.10_-blue.svg)](https://Python.org)
|
||||
|
||||
## 版本
|
||||
|
||||
[![Generic badge](https://img.shields.io/badge/Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases/v0.4.5)
|
||||
<br/>[![Generic badge](https://img.shields.io/badge/Pre_Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases/v0.4.6)
|
||||
<br/>![Generic badge](https://img.shields.io/badge/Devloping-0.6.1-blue.svg)
|
||||
[![Generic badge](https://img.shields.io/badge/Release-0.6.0-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
|
||||
<br/>[![Generic badge](https://img.shields.io/badge/Pre_Release-0.6.1-blue.svg)](https://github.com/shenjackyuanjie/Difficult-Rocket/releases)
|
||||
<br/>![Generic badge](https://img.shields.io/badge/Devloping-0.6.2-blue.svg)
|
||||
|
||||
### For an English version of readme, please move [here](https://github.com/shenjackyuanjie/Difficult-Rocket).
|
||||
|
||||
> 这是一个用Python制作的类Simple Rocket游戏
|
||||
> 这是一个用Python制作的类Simple Rocket游戏(简称:火箭模拟器)
|
||||
|
||||
## 优势
|
||||
|
||||
@ -28,34 +28,39 @@
|
||||
|
||||
## 环境需求 (测试过的 / 开发平台)
|
||||
|
||||
-
|
||||
- `开发平台1 Windows 10`
|
||||
- `Python 3.8.10`
|
||||
- `Windows10 x64`
|
||||
- `pyglet 2.0.dev11`
|
||||
- `Json5 0.9.6`
|
||||
- `pillow 8.1.0`
|
||||
- `AMD R5 5600X(AMD YES!)`
|
||||
- `AMD RX 550 4G`
|
||||
-
|
||||
- `开发平台2 macOS Big Sur`
|
||||
- `Python 3.8.7`
|
||||
- `macOS Big Sur 11.2.1`
|
||||
- `Pyglet 1.5.15`
|
||||
- `Json5 0.9.5`
|
||||
- `pillow 8.1.2`
|
||||
- `Intel I5 8279U`
|
||||
- `开发平台 1 - Windows 10`
|
||||
- `Python 3.8.10`
|
||||
- `Windows10 x64`
|
||||
- `json5 0.9.6`
|
||||
- `pillow 8.1.0`
|
||||
- `pyperclip 1.8.2`
|
||||
- `pyglet 2.0.dev11`
|
||||
- `xmltodict 0.12.0`
|
||||
- `AMD R5 5600X`
|
||||
- `AMD RX 550 4G`
|
||||
- `开发平台 2 - macOS Big Sur`
|
||||
- `Python 3.8.7`
|
||||
- `macOS Big Sur 11.2.1`
|
||||
- `Pyglet 1.5.15`
|
||||
- `Json5 0.9.5`
|
||||
- `pillow 8.1.2`
|
||||
- `Intel I5 8279U`
|
||||
|
||||
## 需要的Python模块
|
||||
|
||||
- json5 (已经内置V0.9.6 路径:`./libs/json5`)
|
||||
- pyglet (已经内置V2.0.dev11 路径:`./libs/pyglet`)
|
||||
- pillow
|
||||
- semver
|
||||
- `json5` (已经内置 V0.9.6 路径:`./libs/json5`)
|
||||
- `pyglet` (已经内置 V2.0.dev11 路径:`./libs/pyglet`)
|
||||
- `xmltodict` (已经内置 V0.12.0 路径:`./libs/xmltodict`)
|
||||
- `pyperclip` (已经内置 V1.8.2 路径: `./libs/pyperclip`)
|
||||
- `pillow`
|
||||
- `semver`
|
||||
|
||||
## 感谢
|
||||
|
||||
- pyglet : 图形界面
|
||||
- [pyglet](https://github.com/pyglet/pyglet) : 图形界面
|
||||
- `json5`: json5解析器
|
||||
- `xmltodict`: xml 与 dict 转换器
|
||||
- `pyperclip`: 剪贴板!
|
||||
- [@rouxiao-you](https://github.com/ruoxiao-you) : 翻译lang
|
||||
- [@Rayawa](https://github.com/Rayawa) : 文档矫正
|
||||
- [@Billchyi](https://github.com/Billchyi) : 文档矫正
|
||||
|
@ -1,5 +1,10 @@
|
||||
# Difficult Rocket Update Logs
|
||||
|
||||
- 感谢 `Github copilot` 的翻译(甚至这句话也是`copilot`翻译的)
|
||||
- 也就意味着以后的更新日志是中文记录+`copilot`翻译的
|
||||
- Thanks `Github copilot` for translate (lazy yes!)
|
||||
- Means the update logs will lodge in Chinese and translated by `copilot`
|
||||
|
||||
## Readme First!
|
||||
##### most badge can be clicked and jump
|
||||
[![Generic badge](https://img.shields.io/badge/SemVer-2.0.0-blue.svg)](https://Semver.org/)
|
||||
@ -10,34 +15,78 @@
|
||||
- [![Readme-gitee](https://img.shields.io/badge/Readme-中文(点我!)-blue.svg?style=flat-square)](README-cn.md)
|
||||
- Using [SemVer 2.0.0](https://semver.org/) to manage version
|
||||
|
||||
## 202111 V 0.6.1
|
||||
## 202112 V 0.6.1
|
||||
|
||||
争取12月内发一个release
|
||||
|
||||
### Change
|
||||
|
||||
- now command will fade away but not suddenly disappear
|
||||
- 把`api/translate`移动到根目录下
|
||||
- move `api/translate` to root directory
|
||||
- 现在命令会慢慢消失,而不是立即消失
|
||||
- Now the command will disappear slowly, not immediately
|
||||
- 重写了一遍` client.load_fonts()`
|
||||
- rewrite `client.load_fonts()`
|
||||
- 重写了 `tools.load_file()` 的错误处理和 `.config` 文件的解析方式
|
||||
- 现在 `.config` 文件解析后会直接返回一个 `ConfigParser` 对象
|
||||
- 也就意味着不能再直接遍历 `.config` 文件返回的解析 ~~谁遍历.config文件啊~~
|
||||
- 同时也意味着可以直接用解析好的 `.config` 文件来修改 `.config` 文件
|
||||
- rewrite `tools.load_file()` error handling and `.config` file parsing method
|
||||
- now `.config` file parsing after return `ConfigParser` object
|
||||
- that means you can not directly traverse `.config` file return parsing ~~who traverse .config file~~
|
||||
- also means you can directly use parsed `.config` file to modify `.config` file
|
||||
- 为 `pyglet` 添加 `Ctrl+C` 和 `Ctrl+V` 的快捷键解析
|
||||
- add `Ctrl+C` and `Ctrl+V` shortcut for `pyglet`
|
||||
|
||||
### Command
|
||||
|
||||
- `log_tick` 指令改为 `fps log`
|
||||
- command `log_tick` change to `fps log`
|
||||
- `maxfps` 指令改为 `fps max`
|
||||
- command `maxfps` change to `fps max`
|
||||
- `minfps` 指令改为 `fps min`
|
||||
- command `minfps` change to `fps min`
|
||||
|
||||
- 命令内容输出使用`CommandText`而不是`str`
|
||||
- 也就是说可以使用`CommandText.match`来匹配命令内容
|
||||
- command output use `CommandText` instead of `str`
|
||||
- means you can use `CommandText.match` to match command content
|
||||
- 命令解析现在使用新的`CommandText.match`
|
||||
- command parse now use new `CommandText.match`
|
||||
|
||||
### Add
|
||||
|
||||
- `gui/widgets.py` InputBox
|
||||
- making
|
||||
- 添加内置字体`Cascadia_Code`
|
||||
- add built-in font `Cascadia_Code`
|
||||
- 添加模块 `xmltodict` `pyperclip`
|
||||
- add modules `xmltodict` `pyperclip`
|
||||
- 添加了`json5` `pyglet` `pyperclip` `xmltodict` 的协议
|
||||
- 非常感谢上述模块的作者和维护者们
|
||||
- added `json5` `pyglet` `pyperclip` `xmltodict` LICENSE
|
||||
- thanks a lot to above module's author and maintainer
|
||||
|
||||
## 20211025 V 0.6.0
|
||||
## 20211025 V 0.6.0
|
||||
|
||||
#### Command Line Update!
|
||||
|
||||
### Change
|
||||
|
||||
- now `Difficult Rocket` will only fit python3.8+
|
||||
- because `:=`
|
||||
- now main crash report handler have new way to handler crash
|
||||
- now fonts' folder's name is `HarmonyOS_Sans`
|
||||
- 现在 `Difficult Rocket` 只适用于 python3.8+
|
||||
- 因为 `:=` 的使用
|
||||
- now `Difficult Rocket` will only fit python3.8+
|
||||
- because `:=`
|
||||
- 现在主程序崩溃时的报告处理方式有了新的方式
|
||||
- now main crash report handler have new way to handler crash
|
||||
- 现在字体文件夹的名字改为 `HarmonyOS_Sans`
|
||||
- now fonts' folder's name is `HarmonyOS_Sans`
|
||||
|
||||
### Add
|
||||
|
||||
- `Difficult_Rocket.graphics.widgets.Parts`
|
||||
- have many costume value
|
||||
- `libs/fonts` now have `HarmonyOS_Sans` font
|
||||
- now `libs/fonts` have `HarmonyOS_Sans` font
|
||||
- handler of `on_key_press` and `on_key_release` and `on_text`
|
||||
- `on_key_press` 和 `on_key_release` 和 `on_text` 的处理方式
|
||||
- `game.config` config file
|
||||
- `lang/en-us.json5` now up to date with `lang/zh-CN.json5`
|
||||
- `translate/Lang.翻译` same as `Lang.lang`
|
||||
|
BIN
docs/字体展示.pptx
Normal file
BIN
docs/字体展示.pptx
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaCode.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaCode.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaCodeItalic.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaCodeItalic.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaCodePL.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaCodePL.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaCodePLItalic.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaCodePLItalic.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaMono.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaMono.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaMonoItalic.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaMonoItalic.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaMonoPL.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaMonoPL.ttf
Normal file
Binary file not shown.
BIN
libs/fonts/Cascadia_Code/CascadiaMonoPLItalic.ttf
Normal file
BIN
libs/fonts/Cascadia_Code/CascadiaMonoPLItalic.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
201
libs/json5/LICENSE.txt
Normal file
201
libs/json5/LICENSE.txt
Normal file
@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
30
libs/pyglet/LICENSE.txt
Normal file
30
libs/pyglet/LICENSE.txt
Normal file
@ -0,0 +1,30 @@
|
||||
Copyright (c) 2006-2008 Alex Holkner
|
||||
Copyright (c) 2008-2021 pyglet contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of pyglet nor the names of its
|
||||
contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior written
|
||||
permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
||||
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
||||
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
||||
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
@ -152,6 +152,7 @@ class EventLoop(event.EventDispatcher):
|
||||
for window in app.windows:
|
||||
window.switch_to()
|
||||
window.dispatch_event('on_draw')
|
||||
window.dispatch_event('on_refresh', dt)
|
||||
window.flip()
|
||||
|
||||
def run(self, interval=1/60):
|
||||
@ -162,7 +163,7 @@ class EventLoop(event.EventDispatcher):
|
||||
Developers are discouraged from overriding this method, as the
|
||||
implementation is platform-specific.
|
||||
"""
|
||||
self.clock.schedule_interval_soft(self._redraw_windows, interval)
|
||||
self.clock.schedule_interval(self._redraw_windows, interval)
|
||||
|
||||
self.has_exit = False
|
||||
|
||||
|
@ -130,21 +130,22 @@ vertex_source = """#version 150 core
|
||||
mat4 view;
|
||||
} window;
|
||||
|
||||
mat4 m_trans_scale = mat4(1.0);
|
||||
mat4 m_scale = mat4(1.0);
|
||||
mat4 m_rotation = mat4(1.0);
|
||||
mat4 m_translate = mat4(1.0);
|
||||
|
||||
void main()
|
||||
{
|
||||
m_trans_scale[3][0] = translate.x;
|
||||
m_trans_scale[3][1] = translate.y;
|
||||
m_trans_scale[0][0] = scale.x;
|
||||
m_trans_scale[1][1] = scale.y;
|
||||
m_scale[0][0] = scale.x;
|
||||
m_scale[1][1] = scale.y;
|
||||
m_translate[3][0] = translate.x;
|
||||
m_translate[3][1] = translate.y;
|
||||
m_rotation[0][0] = cos(-radians(rotation));
|
||||
m_rotation[0][1] = sin(-radians(rotation));
|
||||
m_rotation[1][0] = -sin(-radians(rotation));
|
||||
m_rotation[1][1] = cos(-radians(rotation));
|
||||
|
||||
gl_Position = window.projection * window.view * m_trans_scale * m_rotation * vec4(position, 0, 1);
|
||||
gl_Position = window.projection * window.view * m_translate * m_rotation * m_scale * vec4(position, 0, 1);
|
||||
|
||||
vertex_colors = colors;
|
||||
texture_coords = tex_coords;
|
||||
|
@ -328,7 +328,7 @@ class _GlyphBox(_AbstractBox):
|
||||
try:
|
||||
group = layout.group_cache[self.owner]
|
||||
except KeyError:
|
||||
group = layout.default_group_class(self.owner, get_default_layout_shader(), order=1, parent=layout.group)
|
||||
group = layout.group_class(self.owner, get_default_layout_shader(), order=1, parent=layout.group)
|
||||
layout.group_cache[self.owner] = group
|
||||
|
||||
n_glyphs = self.length
|
||||
@ -809,7 +809,7 @@ class TextLayout:
|
||||
the desired width if word-wrapping failed.
|
||||
`content_height` : int
|
||||
Calculated height of the text in the layout.
|
||||
`default_group_class` : `~pyglet.graphics.Group`
|
||||
`group_class` : `~pyglet.graphics.Group`
|
||||
Top-level rendering group.
|
||||
`background_decoration_group` : `~pyglet.graphics.Group`
|
||||
Rendering group for background color.
|
||||
@ -823,7 +823,8 @@ class TextLayout:
|
||||
|
||||
_update_enabled = True
|
||||
_own_batch = False
|
||||
default_group_class = TextLayoutGroup
|
||||
group_class = TextLayoutGroup
|
||||
decoration_class = TextDecorationGroup
|
||||
|
||||
_x = 0
|
||||
_y = 0
|
||||
@ -870,8 +871,8 @@ class TextLayout:
|
||||
self._user_group = group
|
||||
|
||||
decoration_shader = get_default_decoration_shader()
|
||||
self.background_decoration_group = TextDecorationGroup(decoration_shader, order=0, parent=self._user_group)
|
||||
self.foreground_decoration_group = TextDecorationGroup(decoration_shader, order=2, parent=self._user_group)
|
||||
self.background_decoration_group = self.decoration_class(decoration_shader, order=0, parent=self._user_group)
|
||||
self.foreground_decoration_group = self.decoration_class(decoration_shader, order=2, parent=self._user_group)
|
||||
|
||||
self.group_cache = {}
|
||||
|
||||
@ -1771,7 +1772,8 @@ class ScrollableTextLayout(TextLayout):
|
||||
Use `view_x` and `view_y` to scroll the text within the viewport.
|
||||
"""
|
||||
|
||||
default_group_class = ScrollableTextLayoutGroup
|
||||
group_class = ScrollableTextLayoutGroup
|
||||
decoration_class = ScrollableTextDecorationGroup
|
||||
|
||||
_translate_x = 0
|
||||
_translate_y = 0
|
||||
@ -1911,7 +1913,8 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
_selection_color = [255, 255, 255, 255]
|
||||
_selection_background_color = [46, 106, 197, 255]
|
||||
|
||||
default_group_class = IncrementalTextLayoutGroup
|
||||
group_class = IncrementalTextLayoutGroup
|
||||
decoration_class = IncrementalTextDecorationGroup
|
||||
|
||||
_translate_x = 0
|
||||
_translate_y = 0
|
||||
@ -1931,9 +1934,7 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
self.owner_runs = runlist.RunList(0, None)
|
||||
|
||||
super().__init__(document, width, height, multiline, dpi, batch, group, wrap_lines)
|
||||
|
||||
self._update_translation()
|
||||
self._update()
|
||||
self._update_scissor_area()
|
||||
|
||||
def _update_scissor_area(self):
|
||||
@ -2177,8 +2178,6 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
if line.y + line.ascent > self._translate_y - self.height:
|
||||
end = max(end, i) + 1
|
||||
|
||||
# print("Visible line start/end:", self.visible_lines.start, self.visible_lines.end)
|
||||
|
||||
# Delete newly invisible lines
|
||||
for i in range(self.visible_lines.start, min(start, len(self.lines))):
|
||||
self.lines[i].delete(self)
|
||||
@ -2242,8 +2241,9 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
@x.setter
|
||||
def x(self, x):
|
||||
self._x = x
|
||||
self.on_insert_text(0, self._document.text)
|
||||
self._update()
|
||||
self._uninit_document()
|
||||
self._init_document()
|
||||
self._update_scissor_area()
|
||||
|
||||
@property
|
||||
def y(self):
|
||||
@ -2252,8 +2252,9 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
@y.setter
|
||||
def y(self, y):
|
||||
self._y = y
|
||||
self.on_insert_text(0, self._document.text)
|
||||
self._update()
|
||||
self._uninit_document()
|
||||
self._init_document()
|
||||
self._update_scissor_area()
|
||||
|
||||
@property
|
||||
def position(self):
|
||||
@ -2261,7 +2262,9 @@ class IncrementalTextLayout(TextLayout, EventDispatcher):
|
||||
|
||||
@position.setter
|
||||
def position(self, position):
|
||||
self.x, self.y = position
|
||||
self._x, self._y = position
|
||||
self._uninit_document()
|
||||
self._init_document()
|
||||
self._update_scissor_area()
|
||||
|
||||
@property
|
||||
|
@ -602,12 +602,12 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
|
||||
|
||||
self.switch_to()
|
||||
|
||||
self._create_projection()
|
||||
|
||||
if visible:
|
||||
self.set_visible(True)
|
||||
self.activate()
|
||||
|
||||
self._create_projection()
|
||||
|
||||
def _create_projection(self):
|
||||
self._default_program = shader.ShaderProgram(shader.Shader(self._default_vertex_source, 'vertex'))
|
||||
self.ubo = self._default_program.uniform_blocks['WindowBlock'].create_ubo()
|
||||
@ -1717,7 +1717,7 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
|
||||
:event:
|
||||
"""
|
||||
|
||||
def on_draw(self):
|
||||
def on_draw(self, dt):
|
||||
"""The window contents must be redrawn.
|
||||
|
||||
The `EventLoop` will dispatch this event when the window
|
||||
@ -1737,6 +1737,25 @@ class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
|
||||
:event:
|
||||
"""
|
||||
|
||||
def on_refresh(self, dt):
|
||||
"""The window contents must be redrawn.
|
||||
|
||||
The `EventLoop` will dispatch this event when the window
|
||||
should be redrawn.
|
||||
|
||||
The window will already have the GL context, so there is no
|
||||
need to call `switch_to`. The window's `flip` method will
|
||||
be called after this event, so your event handler should not.
|
||||
|
||||
You should make no assumptions about the window contents when
|
||||
this event is triggered; a resize or expose event may have
|
||||
invalidated the framebuffer since the last time it was drawn.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
:event:
|
||||
"""
|
||||
|
||||
|
||||
BaseWindow.register_event_type('on_key_press')
|
||||
BaseWindow.register_event_type('on_key_release')
|
||||
@ -1762,6 +1781,7 @@ BaseWindow.register_event_type('on_context_lost')
|
||||
BaseWindow.register_event_type('on_context_state_lost')
|
||||
BaseWindow.register_event_type('on_file_drop')
|
||||
BaseWindow.register_event_type('on_draw')
|
||||
BaseWindow.register_event_type('on_refresh')
|
||||
|
||||
|
||||
class FPSDisplay:
|
||||
|
@ -255,6 +255,8 @@ MOTION_BEGINNING_OF_FILE = 5
|
||||
MOTION_END_OF_FILE = 6
|
||||
MOTION_BACKSPACE = BACKSPACE
|
||||
MOTION_DELETE = DELETE
|
||||
MOTION_COPY = 7
|
||||
MOTION_PASTE = 8
|
||||
|
||||
# Number pad
|
||||
NUMLOCK = 0xff7f
|
||||
|
27
libs/pyperclip/LICENSE.txt
Normal file
27
libs/pyperclip/LICENSE.txt
Normal file
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2014, Al Sweigart
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the name of the {organization} nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
736
libs/pyperclip/__init__.py
Normal file
736
libs/pyperclip/__init__.py
Normal file
@ -0,0 +1,736 @@
|
||||
"""
|
||||
Pyperclip
|
||||
|
||||
A cross-platform clipboard module for Python, with copy & paste functions for plain text.
|
||||
By Al Sweigart al@inventwithpython.com
|
||||
BSD License
|
||||
|
||||
Usage:
|
||||
import pyperclip
|
||||
pyperclip.copy('The text to be copied to the clipboard.')
|
||||
spam = pyperclip.paste()
|
||||
|
||||
if not pyperclip.is_available():
|
||||
print("Copy functionality unavailable!")
|
||||
|
||||
On Windows, no additional modules are needed.
|
||||
On Mac, the pyobjc module is used, falling back to the pbcopy and pbpaste cli
|
||||
commands. (These commands should come with OS X.).
|
||||
On Linux, install xclip, xsel, or wl-clipboard (for "wayland" sessions) via package manager.
|
||||
For example, in Debian:
|
||||
sudo apt-get install xclip
|
||||
sudo apt-get install xsel
|
||||
sudo apt-get install wl-clipboard
|
||||
|
||||
Otherwise on Linux, you will need the gtk or PyQt5/PyQt4 modules installed.
|
||||
|
||||
gtk and PyQt4 modules are not available for Python 3,
|
||||
and this module does not work with PyGObject yet.
|
||||
|
||||
Note: There seems to be a way to get gtk on Python 3, according to:
|
||||
https://askubuntu.com/questions/697397/python3-is-not-supporting-gtk-module
|
||||
|
||||
Cygwin is currently not supported.
|
||||
|
||||
Security Note: This module runs programs with these names:
|
||||
- which
|
||||
- where
|
||||
- pbcopy
|
||||
- pbpaste
|
||||
- xclip
|
||||
- xsel
|
||||
- wl-copy/wl-paste
|
||||
- klipper
|
||||
- qdbus
|
||||
A malicious user could rename or add programs with these names, tricking
|
||||
Pyperclip into running them with whatever permissions the Python process has.
|
||||
|
||||
"""
|
||||
__version__ = '1.8.2'
|
||||
|
||||
import contextlib
|
||||
import ctypes
|
||||
import os
|
||||
import platform
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import warnings
|
||||
|
||||
from ctypes import c_size_t, sizeof, c_wchar_p, get_errno, c_wchar
|
||||
|
||||
|
||||
# `import PyQt4` sys.exit()s if DISPLAY is not in the environment.
|
||||
# Thus, we need to detect the presence of $DISPLAY manually
|
||||
# and not load PyQt4 if it is absent.
|
||||
HAS_DISPLAY = os.getenv("DISPLAY", False)
|
||||
|
||||
EXCEPT_MSG = """
|
||||
Pyperclip could not find a copy/paste mechanism for your system.
|
||||
For more information, please visit https://pyperclip.readthedocs.io/en/latest/index.html#not-implemented-error """
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
|
||||
STR_OR_UNICODE = unicode if PY2 else str # For paste(): Python 3 uses str, Python 2 uses unicode.
|
||||
|
||||
ENCODING = 'utf-8'
|
||||
|
||||
try:
|
||||
from shutil import which as _executable_exists
|
||||
except ImportError:
|
||||
# The "which" unix command finds where a command is.
|
||||
if platform.system() == 'Windows':
|
||||
WHICH_CMD = 'where'
|
||||
else:
|
||||
WHICH_CMD = 'which'
|
||||
|
||||
def _executable_exists(name):
|
||||
return subprocess.call([WHICH_CMD, name],
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0
|
||||
|
||||
|
||||
|
||||
# Exceptions
|
||||
class PyperclipException(RuntimeError):
|
||||
pass
|
||||
|
||||
class PyperclipWindowsException(PyperclipException):
|
||||
def __init__(self, message):
|
||||
message += " (%s)" % ctypes.WinError()
|
||||
super(PyperclipWindowsException, self).__init__(message)
|
||||
|
||||
class PyperclipTimeoutException(PyperclipException):
|
||||
pass
|
||||
|
||||
def _stringifyText(text):
|
||||
if PY2:
|
||||
acceptedTypes = (unicode, str, int, float, bool)
|
||||
else:
|
||||
acceptedTypes = (str, int, float, bool)
|
||||
if not isinstance(text, acceptedTypes):
|
||||
raise PyperclipException('only str, int, float, and bool values can be copied to the clipboard, not %s' % (text.__class__.__name__))
|
||||
return STR_OR_UNICODE(text)
|
||||
|
||||
|
||||
def init_osx_pbcopy_clipboard():
|
||||
|
||||
def copy_osx_pbcopy(text):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
p = subprocess.Popen(['pbcopy', 'w'],
|
||||
stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=text.encode(ENCODING))
|
||||
|
||||
def paste_osx_pbcopy():
|
||||
p = subprocess.Popen(['pbpaste', 'r'],
|
||||
stdout=subprocess.PIPE, close_fds=True)
|
||||
stdout, stderr = p.communicate()
|
||||
return stdout.decode(ENCODING)
|
||||
|
||||
return copy_osx_pbcopy, paste_osx_pbcopy
|
||||
|
||||
|
||||
def init_osx_pyobjc_clipboard():
|
||||
def copy_osx_pyobjc(text):
|
||||
'''Copy string argument to clipboard'''
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
newStr = Foundation.NSString.stringWithString_(text).nsstring()
|
||||
newData = newStr.dataUsingEncoding_(Foundation.NSUTF8StringEncoding)
|
||||
board = AppKit.NSPasteboard.generalPasteboard()
|
||||
board.declareTypes_owner_([AppKit.NSStringPboardType], None)
|
||||
board.setData_forType_(newData, AppKit.NSStringPboardType)
|
||||
|
||||
def paste_osx_pyobjc():
|
||||
"Returns contents of clipboard"
|
||||
board = AppKit.NSPasteboard.generalPasteboard()
|
||||
content = board.stringForType_(AppKit.NSStringPboardType)
|
||||
return content
|
||||
|
||||
return copy_osx_pyobjc, paste_osx_pyobjc
|
||||
|
||||
|
||||
def init_gtk_clipboard():
|
||||
global gtk
|
||||
import gtk
|
||||
|
||||
def copy_gtk(text):
|
||||
global cb
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
cb = gtk.Clipboard()
|
||||
cb.set_text(text)
|
||||
cb.store()
|
||||
|
||||
def paste_gtk():
|
||||
clipboardContents = gtk.Clipboard().wait_for_text()
|
||||
# for python 2, returns None if the clipboard is blank.
|
||||
if clipboardContents is None:
|
||||
return ''
|
||||
else:
|
||||
return clipboardContents
|
||||
|
||||
return copy_gtk, paste_gtk
|
||||
|
||||
|
||||
def init_qt_clipboard():
|
||||
global QApplication
|
||||
# $DISPLAY should exist
|
||||
|
||||
# Try to import from qtpy, but if that fails try PyQt5 then PyQt4
|
||||
try:
|
||||
from qtpy.QtWidgets import QApplication
|
||||
except:
|
||||
try:
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
except:
|
||||
from PyQt4.QtGui import QApplication
|
||||
|
||||
app = QApplication.instance()
|
||||
if app is None:
|
||||
app = QApplication([])
|
||||
|
||||
def copy_qt(text):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
cb = app.clipboard()
|
||||
cb.setText(text)
|
||||
|
||||
def paste_qt():
|
||||
cb = app.clipboard()
|
||||
return STR_OR_UNICODE(cb.text())
|
||||
|
||||
return copy_qt, paste_qt
|
||||
|
||||
|
||||
def init_xclip_clipboard():
|
||||
DEFAULT_SELECTION='c'
|
||||
PRIMARY_SELECTION='p'
|
||||
|
||||
def copy_xclip(text, primary=False):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
selection=DEFAULT_SELECTION
|
||||
if primary:
|
||||
selection=PRIMARY_SELECTION
|
||||
p = subprocess.Popen(['xclip', '-selection', selection],
|
||||
stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=text.encode(ENCODING))
|
||||
|
||||
def paste_xclip(primary=False):
|
||||
selection=DEFAULT_SELECTION
|
||||
if primary:
|
||||
selection=PRIMARY_SELECTION
|
||||
p = subprocess.Popen(['xclip', '-selection', selection, '-o'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
stdout, stderr = p.communicate()
|
||||
# Intentionally ignore extraneous output on stderr when clipboard is empty
|
||||
return stdout.decode(ENCODING)
|
||||
|
||||
return copy_xclip, paste_xclip
|
||||
|
||||
|
||||
def init_xsel_clipboard():
|
||||
DEFAULT_SELECTION='-b'
|
||||
PRIMARY_SELECTION='-p'
|
||||
|
||||
def copy_xsel(text, primary=False):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
selection_flag = DEFAULT_SELECTION
|
||||
if primary:
|
||||
selection_flag = PRIMARY_SELECTION
|
||||
p = subprocess.Popen(['xsel', selection_flag, '-i'],
|
||||
stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=text.encode(ENCODING))
|
||||
|
||||
def paste_xsel(primary=False):
|
||||
selection_flag = DEFAULT_SELECTION
|
||||
if primary:
|
||||
selection_flag = PRIMARY_SELECTION
|
||||
p = subprocess.Popen(['xsel', selection_flag, '-o'],
|
||||
stdout=subprocess.PIPE, close_fds=True)
|
||||
stdout, stderr = p.communicate()
|
||||
return stdout.decode(ENCODING)
|
||||
|
||||
return copy_xsel, paste_xsel
|
||||
|
||||
|
||||
def init_wl_clipboard():
|
||||
PRIMARY_SELECTION = "-p"
|
||||
|
||||
def copy_wl(text, primary=False):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
args = ["wl-copy"]
|
||||
if primary:
|
||||
args.append(PRIMARY_SELECTION)
|
||||
if not text:
|
||||
args.append('--clear')
|
||||
subprocess.check_call(args, close_fds=True)
|
||||
else:
|
||||
pass
|
||||
p = subprocess.Popen(args, stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=text.encode(ENCODING))
|
||||
|
||||
def paste_wl(primary=False):
|
||||
args = ["wl-paste", "-n"]
|
||||
if primary:
|
||||
args.append(PRIMARY_SELECTION)
|
||||
p = subprocess.Popen(args, stdout=subprocess.PIPE, close_fds=True)
|
||||
stdout, _stderr = p.communicate()
|
||||
return stdout.decode(ENCODING)
|
||||
|
||||
return copy_wl, paste_wl
|
||||
|
||||
|
||||
def init_klipper_clipboard():
|
||||
def copy_klipper(text):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
p = subprocess.Popen(
|
||||
['qdbus', 'org.kde.klipper', '/klipper', 'setClipboardContents',
|
||||
text.encode(ENCODING)],
|
||||
stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=None)
|
||||
|
||||
def paste_klipper():
|
||||
p = subprocess.Popen(
|
||||
['qdbus', 'org.kde.klipper', '/klipper', 'getClipboardContents'],
|
||||
stdout=subprocess.PIPE, close_fds=True)
|
||||
stdout, stderr = p.communicate()
|
||||
|
||||
# Workaround for https://bugs.kde.org/show_bug.cgi?id=342874
|
||||
# TODO: https://github.com/asweigart/pyperclip/issues/43
|
||||
clipboardContents = stdout.decode(ENCODING)
|
||||
# even if blank, Klipper will append a newline at the end
|
||||
assert len(clipboardContents) > 0
|
||||
# make sure that newline is there
|
||||
assert clipboardContents.endswith('\n')
|
||||
if clipboardContents.endswith('\n'):
|
||||
clipboardContents = clipboardContents[:-1]
|
||||
return clipboardContents
|
||||
|
||||
return copy_klipper, paste_klipper
|
||||
|
||||
|
||||
def init_dev_clipboard_clipboard():
|
||||
def copy_dev_clipboard(text):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
if text == '':
|
||||
warnings.warn('Pyperclip cannot copy a blank string to the clipboard on Cygwin. This is effectively a no-op.')
|
||||
if '\r' in text:
|
||||
warnings.warn('Pyperclip cannot handle \\r characters on Cygwin.')
|
||||
|
||||
fo = open('/dev/clipboard', 'wt')
|
||||
fo.write(text)
|
||||
fo.close()
|
||||
|
||||
def paste_dev_clipboard():
|
||||
fo = open('/dev/clipboard', 'rt')
|
||||
content = fo.read()
|
||||
fo.close()
|
||||
return content
|
||||
|
||||
return copy_dev_clipboard, paste_dev_clipboard
|
||||
|
||||
|
||||
def init_no_clipboard():
|
||||
class ClipboardUnavailable(object):
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
raise PyperclipException(EXCEPT_MSG)
|
||||
|
||||
if PY2:
|
||||
def __nonzero__(self):
|
||||
return False
|
||||
else:
|
||||
def __bool__(self):
|
||||
return False
|
||||
|
||||
return ClipboardUnavailable(), ClipboardUnavailable()
|
||||
|
||||
|
||||
|
||||
|
||||
# Windows-related clipboard functions:
|
||||
class CheckedCall(object):
|
||||
def __init__(self, f):
|
||||
super(CheckedCall, self).__setattr__("f", f)
|
||||
|
||||
def __call__(self, *args):
|
||||
ret = self.f(*args)
|
||||
if not ret and get_errno():
|
||||
raise PyperclipWindowsException("Error calling " + self.f.__name__)
|
||||
return ret
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
setattr(self.f, key, value)
|
||||
|
||||
|
||||
def init_windows_clipboard():
|
||||
global HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND, HINSTANCE, HMENU, BOOL, UINT, HANDLE
|
||||
from ctypes.wintypes import (HGLOBAL, LPVOID, DWORD, LPCSTR, INT, HWND,
|
||||
HINSTANCE, HMENU, BOOL, UINT, HANDLE)
|
||||
|
||||
windll = ctypes.windll
|
||||
msvcrt = ctypes.CDLL('msvcrt')
|
||||
|
||||
safeCreateWindowExA = CheckedCall(windll.user32.CreateWindowExA)
|
||||
safeCreateWindowExA.argtypes = [DWORD, LPCSTR, LPCSTR, DWORD, INT, INT,
|
||||
INT, INT, HWND, HMENU, HINSTANCE, LPVOID]
|
||||
safeCreateWindowExA.restype = HWND
|
||||
|
||||
safeDestroyWindow = CheckedCall(windll.user32.DestroyWindow)
|
||||
safeDestroyWindow.argtypes = [HWND]
|
||||
safeDestroyWindow.restype = BOOL
|
||||
|
||||
OpenClipboard = windll.user32.OpenClipboard
|
||||
OpenClipboard.argtypes = [HWND]
|
||||
OpenClipboard.restype = BOOL
|
||||
|
||||
safeCloseClipboard = CheckedCall(windll.user32.CloseClipboard)
|
||||
safeCloseClipboard.argtypes = []
|
||||
safeCloseClipboard.restype = BOOL
|
||||
|
||||
safeEmptyClipboard = CheckedCall(windll.user32.EmptyClipboard)
|
||||
safeEmptyClipboard.argtypes = []
|
||||
safeEmptyClipboard.restype = BOOL
|
||||
|
||||
safeGetClipboardData = CheckedCall(windll.user32.GetClipboardData)
|
||||
safeGetClipboardData.argtypes = [UINT]
|
||||
safeGetClipboardData.restype = HANDLE
|
||||
|
||||
safeSetClipboardData = CheckedCall(windll.user32.SetClipboardData)
|
||||
safeSetClipboardData.argtypes = [UINT, HANDLE]
|
||||
safeSetClipboardData.restype = HANDLE
|
||||
|
||||
safeGlobalAlloc = CheckedCall(windll.kernel32.GlobalAlloc)
|
||||
safeGlobalAlloc.argtypes = [UINT, c_size_t]
|
||||
safeGlobalAlloc.restype = HGLOBAL
|
||||
|
||||
safeGlobalLock = CheckedCall(windll.kernel32.GlobalLock)
|
||||
safeGlobalLock.argtypes = [HGLOBAL]
|
||||
safeGlobalLock.restype = LPVOID
|
||||
|
||||
safeGlobalUnlock = CheckedCall(windll.kernel32.GlobalUnlock)
|
||||
safeGlobalUnlock.argtypes = [HGLOBAL]
|
||||
safeGlobalUnlock.restype = BOOL
|
||||
|
||||
wcslen = CheckedCall(msvcrt.wcslen)
|
||||
wcslen.argtypes = [c_wchar_p]
|
||||
wcslen.restype = UINT
|
||||
|
||||
GMEM_MOVEABLE = 0x0002
|
||||
CF_UNICODETEXT = 13
|
||||
|
||||
@contextlib.contextmanager
|
||||
def window():
|
||||
"""
|
||||
Context that provides a valid Windows hwnd.
|
||||
"""
|
||||
# we really just need the hwnd, so setting "STATIC"
|
||||
# as predefined lpClass is just fine.
|
||||
hwnd = safeCreateWindowExA(0, b"STATIC", None, 0, 0, 0, 0, 0,
|
||||
None, None, None, None)
|
||||
try:
|
||||
yield hwnd
|
||||
finally:
|
||||
safeDestroyWindow(hwnd)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def clipboard(hwnd):
|
||||
"""
|
||||
Context manager that opens the clipboard and prevents
|
||||
other applications from modifying the clipboard content.
|
||||
"""
|
||||
# We may not get the clipboard handle immediately because
|
||||
# some other application is accessing it (?)
|
||||
# We try for at least 500ms to get the clipboard.
|
||||
t = time.time() + 0.5
|
||||
success = False
|
||||
while time.time() < t:
|
||||
success = OpenClipboard(hwnd)
|
||||
if success:
|
||||
break
|
||||
time.sleep(0.01)
|
||||
if not success:
|
||||
raise PyperclipWindowsException("Error calling OpenClipboard")
|
||||
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
safeCloseClipboard()
|
||||
|
||||
def copy_windows(text):
|
||||
# This function is heavily based on
|
||||
# http://msdn.com/ms649016#_win32_Copying_Information_to_the_Clipboard
|
||||
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
|
||||
with window() as hwnd:
|
||||
# http://msdn.com/ms649048
|
||||
# If an application calls OpenClipboard with hwnd set to NULL,
|
||||
# EmptyClipboard sets the clipboard owner to NULL;
|
||||
# this causes SetClipboardData to fail.
|
||||
# => We need a valid hwnd to copy something.
|
||||
with clipboard(hwnd):
|
||||
safeEmptyClipboard()
|
||||
|
||||
if text:
|
||||
# http://msdn.com/ms649051
|
||||
# If the hMem parameter identifies a memory object,
|
||||
# the object must have been allocated using the
|
||||
# function with the GMEM_MOVEABLE flag.
|
||||
count = wcslen(text) + 1
|
||||
handle = safeGlobalAlloc(GMEM_MOVEABLE,
|
||||
count * sizeof(c_wchar))
|
||||
locked_handle = safeGlobalLock(handle)
|
||||
|
||||
ctypes.memmove(c_wchar_p(locked_handle), c_wchar_p(text), count * sizeof(c_wchar))
|
||||
|
||||
safeGlobalUnlock(handle)
|
||||
safeSetClipboardData(CF_UNICODETEXT, handle)
|
||||
|
||||
def paste_windows():
|
||||
with clipboard(None):
|
||||
handle = safeGetClipboardData(CF_UNICODETEXT)
|
||||
if not handle:
|
||||
# GetClipboardData may return NULL with errno == NO_ERROR
|
||||
# if the clipboard is empty.
|
||||
# (Also, it may return a handle to an empty buffer,
|
||||
# but technically that's not empty)
|
||||
return ""
|
||||
return c_wchar_p(handle).value
|
||||
|
||||
return copy_windows, paste_windows
|
||||
|
||||
|
||||
def init_wsl_clipboard():
|
||||
def copy_wsl(text):
|
||||
text = _stringifyText(text) # Converts non-str values to str.
|
||||
p = subprocess.Popen(['clip.exe'],
|
||||
stdin=subprocess.PIPE, close_fds=True)
|
||||
p.communicate(input=text.encode(ENCODING))
|
||||
|
||||
def paste_wsl():
|
||||
p = subprocess.Popen(['powershell.exe', '-command', 'Get-Clipboard'],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
close_fds=True)
|
||||
stdout, stderr = p.communicate()
|
||||
# WSL appends "\r\n" to the contents.
|
||||
return stdout[:-2].decode(ENCODING)
|
||||
|
||||
return copy_wsl, paste_wsl
|
||||
|
||||
|
||||
# Automatic detection of clipboard mechanisms and importing is done in deteremine_clipboard():
|
||||
def determine_clipboard():
|
||||
'''
|
||||
Determine the OS/platform and set the copy() and paste() functions
|
||||
accordingly.
|
||||
'''
|
||||
|
||||
global Foundation, AppKit, gtk, qtpy, PyQt4, PyQt5
|
||||
|
||||
# Setup for the CYGWIN platform:
|
||||
if 'cygwin' in platform.system().lower(): # Cygwin has a variety of values returned by platform.system(), such as 'CYGWIN_NT-6.1'
|
||||
# FIXME: pyperclip currently does not support Cygwin,
|
||||
# see https://github.com/asweigart/pyperclip/issues/55
|
||||
if os.path.exists('/dev/clipboard'):
|
||||
warnings.warn('Pyperclip\'s support for Cygwin is not perfect, see https://github.com/asweigart/pyperclip/issues/55')
|
||||
return init_dev_clipboard_clipboard()
|
||||
|
||||
# Setup for the WINDOWS platform:
|
||||
elif os.name == 'nt' or platform.system() == 'Windows':
|
||||
return init_windows_clipboard()
|
||||
|
||||
if platform.system() == 'Linux' and os.path.isfile('/proc/version'):
|
||||
with open('/proc/version', 'r') as f:
|
||||
if "microsoft" in f.read().lower():
|
||||
return init_wsl_clipboard()
|
||||
|
||||
# Setup for the MAC OS X platform:
|
||||
if os.name == 'mac' or platform.system() == 'Darwin':
|
||||
try:
|
||||
import Foundation # check if pyobjc is installed
|
||||
import AppKit
|
||||
except ImportError:
|
||||
return init_osx_pbcopy_clipboard()
|
||||
else:
|
||||
return init_osx_pyobjc_clipboard()
|
||||
|
||||
# Setup for the LINUX platform:
|
||||
if HAS_DISPLAY:
|
||||
try:
|
||||
import gtk # check if gtk is installed
|
||||
except ImportError:
|
||||
pass # We want to fail fast for all non-ImportError exceptions.
|
||||
else:
|
||||
return init_gtk_clipboard()
|
||||
|
||||
if (
|
||||
os.environ.get("WAYLAND_DISPLAY") and
|
||||
_executable_exists("wl-copy")
|
||||
):
|
||||
return init_wl_clipboard()
|
||||
if _executable_exists("xsel"):
|
||||
return init_xsel_clipboard()
|
||||
if _executable_exists("xclip"):
|
||||
return init_xclip_clipboard()
|
||||
if _executable_exists("klipper") and _executable_exists("qdbus"):
|
||||
return init_klipper_clipboard()
|
||||
|
||||
try:
|
||||
# qtpy is a small abstraction layer that lets you write applications using a single api call to either PyQt or PySide.
|
||||
# https://pypi.python.org/pypi/QtPy
|
||||
import qtpy # check if qtpy is installed
|
||||
except ImportError:
|
||||
# If qtpy isn't installed, fall back on importing PyQt4.
|
||||
try:
|
||||
import PyQt5 # check if PyQt5 is installed
|
||||
except ImportError:
|
||||
try:
|
||||
import PyQt4 # check if PyQt4 is installed
|
||||
except ImportError:
|
||||
pass # We want to fail fast for all non-ImportError exceptions.
|
||||
else:
|
||||
return init_qt_clipboard()
|
||||
else:
|
||||
return init_qt_clipboard()
|
||||
else:
|
||||
return init_qt_clipboard()
|
||||
|
||||
|
||||
return init_no_clipboard()
|
||||
|
||||
|
||||
def set_clipboard(clipboard):
|
||||
'''
|
||||
Explicitly sets the clipboard mechanism. The "clipboard mechanism" is how
|
||||
the copy() and paste() functions interact with the operating system to
|
||||
implement the copy/paste feature. The clipboard parameter must be one of:
|
||||
- pbcopy
|
||||
- pbobjc (default on Mac OS X)
|
||||
- gtk
|
||||
- qt
|
||||
- xclip
|
||||
- xsel
|
||||
- klipper
|
||||
- windows (default on Windows)
|
||||
- no (this is what is set when no clipboard mechanism can be found)
|
||||
'''
|
||||
global copy, paste
|
||||
|
||||
clipboard_types = {
|
||||
"pbcopy": init_osx_pbcopy_clipboard,
|
||||
"pyobjc": init_osx_pyobjc_clipboard,
|
||||
"gtk": init_gtk_clipboard,
|
||||
"qt": init_qt_clipboard, # TODO - split this into 'qtpy', 'pyqt4', and 'pyqt5'
|
||||
"xclip": init_xclip_clipboard,
|
||||
"xsel": init_xsel_clipboard,
|
||||
"wl-clipboard": init_wl_clipboard,
|
||||
"klipper": init_klipper_clipboard,
|
||||
"windows": init_windows_clipboard,
|
||||
"no": init_no_clipboard,
|
||||
}
|
||||
|
||||
if clipboard not in clipboard_types:
|
||||
raise ValueError('Argument must be one of %s' % (', '.join([repr(_) for _ in clipboard_types.keys()])))
|
||||
|
||||
# Sets pyperclip's copy() and paste() functions:
|
||||
copy, paste = clipboard_types[clipboard]()
|
||||
|
||||
|
||||
def lazy_load_stub_copy(text):
|
||||
'''
|
||||
A stub function for copy(), which will load the real copy() function when
|
||||
called so that the real copy() function is used for later calls.
|
||||
|
||||
This allows users to import pyperclip without having determine_clipboard()
|
||||
automatically run, which will automatically select a clipboard mechanism.
|
||||
This could be a problem if it selects, say, the memory-heavy PyQt4 module
|
||||
but the user was just going to immediately call set_clipboard() to use a
|
||||
different clipboard mechanism.
|
||||
|
||||
The lazy loading this stub function implements gives the user a chance to
|
||||
call set_clipboard() to pick another clipboard mechanism. Or, if the user
|
||||
simply calls copy() or paste() without calling set_clipboard() first,
|
||||
will fall back on whatever clipboard mechanism that determine_clipboard()
|
||||
automatically chooses.
|
||||
'''
|
||||
global copy, paste
|
||||
copy, paste = determine_clipboard()
|
||||
return copy(text)
|
||||
|
||||
|
||||
def lazy_load_stub_paste():
|
||||
'''
|
||||
A stub function for paste(), which will load the real paste() function when
|
||||
called so that the real paste() function is used for later calls.
|
||||
|
||||
This allows users to import pyperclip without having determine_clipboard()
|
||||
automatically run, which will automatically select a clipboard mechanism.
|
||||
This could be a problem if it selects, say, the memory-heavy PyQt4 module
|
||||
but the user was just going to immediately call set_clipboard() to use a
|
||||
different clipboard mechanism.
|
||||
|
||||
The lazy loading this stub function implements gives the user a chance to
|
||||
call set_clipboard() to pick another clipboard mechanism. Or, if the user
|
||||
simply calls copy() or paste() without calling set_clipboard() first,
|
||||
will fall back on whatever clipboard mechanism that determine_clipboard()
|
||||
automatically chooses.
|
||||
'''
|
||||
global copy, paste
|
||||
copy, paste = determine_clipboard()
|
||||
return paste()
|
||||
|
||||
|
||||
def is_available():
|
||||
return copy != lazy_load_stub_copy and paste != lazy_load_stub_paste
|
||||
|
||||
|
||||
# Initially, copy() and paste() are set to lazy loading wrappers which will
|
||||
# set `copy` and `paste` to real functions the first time they're used, unless
|
||||
# set_clipboard() or determine_clipboard() is called first.
|
||||
copy, paste = lazy_load_stub_copy, lazy_load_stub_paste
|
||||
|
||||
|
||||
|
||||
def waitForPaste(timeout=None):
|
||||
"""This function call blocks until a non-empty text string exists on the
|
||||
clipboard. It returns this text.
|
||||
|
||||
This function raises PyperclipTimeoutException if timeout was set to
|
||||
a number of seconds that has elapsed without non-empty text being put on
|
||||
the clipboard."""
|
||||
startTime = time.time()
|
||||
while True:
|
||||
clipboardText = paste()
|
||||
if clipboardText != '':
|
||||
return clipboardText
|
||||
time.sleep(0.01)
|
||||
|
||||
if timeout is not None and time.time() > startTime + timeout:
|
||||
raise PyperclipTimeoutException('waitForPaste() timed out after ' + str(timeout) + ' seconds.')
|
||||
|
||||
|
||||
def waitForNewPaste(timeout=None):
|
||||
"""This function call blocks until a new text string exists on the
|
||||
clipboard that is different from the text that was there when the function
|
||||
was first called. It returns this text.
|
||||
|
||||
This function raises PyperclipTimeoutException if timeout was set to
|
||||
a number of seconds that has elapsed without non-empty text being put on
|
||||
the clipboard."""
|
||||
startTime = time.time()
|
||||
originalText = paste()
|
||||
while True:
|
||||
currentText = paste()
|
||||
if currentText != originalText:
|
||||
return currentText
|
||||
time.sleep(0.01)
|
||||
|
||||
if timeout is not None and time.time() > startTime + timeout:
|
||||
raise PyperclipTimeoutException('waitForNewPaste() timed out after ' + str(timeout) + ' seconds.')
|
||||
|
||||
|
||||
__all__ = ['copy', 'paste', 'waitForPaste', 'waitForNewPaste', 'set_clipboard', 'determine_clipboard']
|
||||
|
||||
|
||||
|
18
libs/pyperclip/__main__.py
Normal file
18
libs/pyperclip/__main__.py
Normal file
@ -0,0 +1,18 @@
|
||||
import pyperclip
|
||||
import sys
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ('-c', '--copy'):
|
||||
if len(sys.argv) > 2:
|
||||
pyperclip.copy(sys.argv[2])
|
||||
else:
|
||||
pyperclip.copy(sys.stdin.read())
|
||||
elif len(sys.argv) > 1 and sys.argv[1] in ('-p', '--paste'):
|
||||
sys.stdout.write(pyperclip.paste())
|
||||
else:
|
||||
print('Usage: python -m pyperclip [-c | --copy] [text_to_copy] | [-p | --paste]')
|
||||
print()
|
||||
print('If a text_to_copy argument is provided, it is copied to the')
|
||||
print('clipboard. Otherwise, the stdin stream is copied to the')
|
||||
print('clipboard. (If reading this in from the keyboard, press')
|
||||
print('CTRL-Z on Windows or CTRL-D on Linux/macOS to stop.')
|
||||
print('When pasting, the clipboard will be written to stdout.')
|
7
libs/xmltodict/LICENSE.txt
Normal file
7
libs/xmltodict/LICENSE.txt
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright (C) 2012 Martin Blech and individual contributors.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
489
libs/xmltodict/xmltodict.py
Normal file
489
libs/xmltodict/xmltodict.py
Normal file
@ -0,0 +1,489 @@
|
||||
#!/usr/bin/env python
|
||||
"Makes working with XML feel like you are working with JSON"
|
||||
|
||||
try:
|
||||
from defusedexpat import pyexpat as expat
|
||||
except ImportError:
|
||||
from xml.parsers import expat
|
||||
|
||||
from xml.sax.saxutils import XMLGenerator
|
||||
from xml.sax.xmlreader import AttributesImpl
|
||||
try: # pragma no cover
|
||||
from cStringIO import StringIO
|
||||
except ImportError: # pragma no cover
|
||||
try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
try: # pragma no cover
|
||||
_basestring = basestring
|
||||
except NameError: # pragma no cover
|
||||
_basestring = str
|
||||
try: # pragma no cover
|
||||
_unicode = unicode
|
||||
except NameError: # pragma no cover
|
||||
_unicode = str
|
||||
|
||||
__author__ = 'Martin Blech'
|
||||
__version__ = '0.12.0'
|
||||
__license__ = 'MIT'
|
||||
|
||||
|
||||
class ParsingInterrupted(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class _DictSAXHandler(object):
|
||||
def __init__(self,
|
||||
item_depth=0,
|
||||
item_callback=lambda *args: True,
|
||||
xml_attribs=True,
|
||||
attr_prefix='@',
|
||||
cdata_key='#text',
|
||||
force_cdata=False,
|
||||
cdata_separator='',
|
||||
postprocessor=None,
|
||||
dict_constructor=OrderedDict,
|
||||
strip_whitespace=True,
|
||||
namespace_separator=':',
|
||||
namespaces=None,
|
||||
force_list=None):
|
||||
self.path = []
|
||||
self.stack = []
|
||||
self.data = []
|
||||
self.item = None
|
||||
self.item_depth = item_depth
|
||||
self.xml_attribs = xml_attribs
|
||||
self.item_callback = item_callback
|
||||
self.attr_prefix = attr_prefix
|
||||
self.cdata_key = cdata_key
|
||||
self.force_cdata = force_cdata
|
||||
self.cdata_separator = cdata_separator
|
||||
self.postprocessor = postprocessor
|
||||
self.dict_constructor = dict_constructor
|
||||
self.strip_whitespace = strip_whitespace
|
||||
self.namespace_separator = namespace_separator
|
||||
self.namespaces = namespaces
|
||||
self.namespace_declarations = OrderedDict()
|
||||
self.force_list = force_list
|
||||
|
||||
def _build_name(self, full_name):
|
||||
if not self.namespaces:
|
||||
return full_name
|
||||
i = full_name.rfind(self.namespace_separator)
|
||||
if i == -1:
|
||||
return full_name
|
||||
namespace, name = full_name[:i], full_name[i+1:]
|
||||
short_namespace = self.namespaces.get(namespace, namespace)
|
||||
if not short_namespace:
|
||||
return name
|
||||
else:
|
||||
return self.namespace_separator.join((short_namespace, name))
|
||||
|
||||
def _attrs_to_dict(self, attrs):
|
||||
if isinstance(attrs, dict):
|
||||
return attrs
|
||||
return self.dict_constructor(zip(attrs[0::2], attrs[1::2]))
|
||||
|
||||
def startNamespaceDecl(self, prefix, uri):
|
||||
self.namespace_declarations[prefix or ''] = uri
|
||||
|
||||
def startElement(self, full_name, attrs):
|
||||
name = self._build_name(full_name)
|
||||
attrs = self._attrs_to_dict(attrs)
|
||||
if attrs and self.namespace_declarations:
|
||||
attrs['xmlns'] = self.namespace_declarations
|
||||
self.namespace_declarations = OrderedDict()
|
||||
self.path.append((name, attrs or None))
|
||||
if len(self.path) > self.item_depth:
|
||||
self.stack.append((self.item, self.data))
|
||||
if self.xml_attribs:
|
||||
attr_entries = []
|
||||
for key, value in attrs.items():
|
||||
key = self.attr_prefix+self._build_name(key)
|
||||
if self.postprocessor:
|
||||
entry = self.postprocessor(self.path, key, value)
|
||||
else:
|
||||
entry = (key, value)
|
||||
if entry:
|
||||
attr_entries.append(entry)
|
||||
attrs = self.dict_constructor(attr_entries)
|
||||
else:
|
||||
attrs = None
|
||||
self.item = attrs or None
|
||||
self.data = []
|
||||
|
||||
def endElement(self, full_name):
|
||||
name = self._build_name(full_name)
|
||||
if len(self.path) == self.item_depth:
|
||||
item = self.item
|
||||
if item is None:
|
||||
item = (None if not self.data
|
||||
else self.cdata_separator.join(self.data))
|
||||
|
||||
should_continue = self.item_callback(self.path, item)
|
||||
if not should_continue:
|
||||
raise ParsingInterrupted()
|
||||
if len(self.stack):
|
||||
data = (None if not self.data
|
||||
else self.cdata_separator.join(self.data))
|
||||
item = self.item
|
||||
self.item, self.data = self.stack.pop()
|
||||
if self.strip_whitespace and data:
|
||||
data = data.strip() or None
|
||||
if data and self.force_cdata and item is None:
|
||||
item = self.dict_constructor()
|
||||
if item is not None:
|
||||
if data:
|
||||
self.push_data(item, self.cdata_key, data)
|
||||
self.item = self.push_data(self.item, name, item)
|
||||
else:
|
||||
self.item = self.push_data(self.item, name, data)
|
||||
else:
|
||||
self.item = None
|
||||
self.data = []
|
||||
self.path.pop()
|
||||
|
||||
def characters(self, data):
|
||||
if not self.data:
|
||||
self.data = [data]
|
||||
else:
|
||||
self.data.append(data)
|
||||
|
||||
def push_data(self, item, key, data):
|
||||
if self.postprocessor is not None:
|
||||
result = self.postprocessor(self.path, key, data)
|
||||
if result is None:
|
||||
return item
|
||||
key, data = result
|
||||
if item is None:
|
||||
item = self.dict_constructor()
|
||||
try:
|
||||
value = item[key]
|
||||
if isinstance(value, list):
|
||||
value.append(data)
|
||||
else:
|
||||
item[key] = [value, data]
|
||||
except KeyError:
|
||||
if self._should_force_list(key, data):
|
||||
item[key] = [data]
|
||||
else:
|
||||
item[key] = data
|
||||
return item
|
||||
|
||||
def _should_force_list(self, key, value):
|
||||
if not self.force_list:
|
||||
return False
|
||||
if isinstance(self.force_list, bool):
|
||||
return self.force_list
|
||||
try:
|
||||
return key in self.force_list
|
||||
except TypeError:
|
||||
return self.force_list(self.path[:-1], key, value)
|
||||
|
||||
|
||||
def parse(xml_input, encoding=None, expat=expat, process_namespaces=False,
|
||||
namespace_separator=':', disable_entities=True, **kwargs):
|
||||
"""Parse the given XML input and convert it into a dictionary.
|
||||
|
||||
`xml_input` can either be a `string` or a file-like object.
|
||||
|
||||
If `xml_attribs` is `True`, element attributes are put in the dictionary
|
||||
among regular child elements, using `@` as a prefix to avoid collisions. If
|
||||
set to `False`, they are just ignored.
|
||||
|
||||
Simple example::
|
||||
|
||||
>>> from xmltodict import xmltodict
|
||||
>>> doc = xmltodict.parse(\"\"\"
|
||||
... <a prop="x">
|
||||
... <b>1</b>
|
||||
... <b>2</b>
|
||||
... </a>
|
||||
... \"\"\")
|
||||
>>> doc['a']['@prop']
|
||||
u'x'
|
||||
>>> doc['a']['b']
|
||||
[u'1', u'2']
|
||||
|
||||
If `item_depth` is `0`, the function returns a dictionary for the root
|
||||
element (default behavior). Otherwise, it calls `item_callback` every time
|
||||
an item at the specified depth is found and returns `None` in the end
|
||||
(streaming mode).
|
||||
|
||||
The callback function receives two parameters: the `path` from the document
|
||||
root to the item (name-attribs pairs), and the `item` (dict). If the
|
||||
callback's return value is false-ish, parsing will be stopped with the
|
||||
:class:`ParsingInterrupted` exception.
|
||||
|
||||
Streaming example::
|
||||
|
||||
>>> def handle(path, item):
|
||||
... print('path:%s item:%s' % (path, item))
|
||||
... return True
|
||||
...
|
||||
>>> xmltodict.parse(\"\"\"
|
||||
... <a prop="x">
|
||||
... <b>1</b>
|
||||
... <b>2</b>
|
||||
... </a>\"\"\", item_depth=2, item_callback=handle)
|
||||
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:1
|
||||
path:[(u'a', {u'prop': u'x'}), (u'b', None)] item:2
|
||||
|
||||
The optional argument `postprocessor` is a function that takes `path`,
|
||||
`key` and `value` as positional arguments and returns a new `(key, value)`
|
||||
pair where both `key` and `value` may have changed. Usage example::
|
||||
|
||||
>>> def postprocessor(path, key, value):
|
||||
... try:
|
||||
... return key + ':int', int(value)
|
||||
... except (ValueError, TypeError):
|
||||
... return key, value
|
||||
>>> xmltodict.parse('<a><b>1</b><b>2</b><b>x</b></a>',
|
||||
... postprocessor=postprocessor)
|
||||
OrderedDict([(u'a', OrderedDict([(u'b:int', [1, 2]), (u'b', u'x')]))])
|
||||
|
||||
You can pass an alternate version of `expat` (such as `defusedexpat`) by
|
||||
using the `expat` parameter. E.g:
|
||||
|
||||
>>> import defusedexpat
|
||||
>>> xmltodict.parse('<a>hello</a>', expat=defusedexpat.pyexpat)
|
||||
OrderedDict([(u'a', u'hello')])
|
||||
|
||||
You can use the force_list argument to force lists to be created even
|
||||
when there is only a single child of a given level of hierarchy. The
|
||||
force_list argument is a tuple of keys. If the key for a given level
|
||||
of hierarchy is in the force_list argument, that level of hierarchy
|
||||
will have a list as a child (even if there is only one sub-element).
|
||||
The index_keys operation takes precendence over this. This is applied
|
||||
after any user-supplied postprocessor has already run.
|
||||
|
||||
For example, given this input:
|
||||
<servers>
|
||||
<server>
|
||||
<name>host1</name>
|
||||
<os>Linux</os>
|
||||
<interfaces>
|
||||
<interface>
|
||||
<name>em0</name>
|
||||
<ip_address>10.0.0.1</ip_address>
|
||||
</interface>
|
||||
</interfaces>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
If called with force_list=('interface',), it will produce
|
||||
this dictionary:
|
||||
{'servers':
|
||||
{'server':
|
||||
{'name': 'host1',
|
||||
'os': 'Linux'},
|
||||
'interfaces':
|
||||
{'interface':
|
||||
[ {'name': 'em0', 'ip_address': '10.0.0.1' } ] } } }
|
||||
|
||||
`force_list` can also be a callable that receives `path`, `key` and
|
||||
`value`. This is helpful in cases where the logic that decides whether
|
||||
a list should be forced is more complex.
|
||||
"""
|
||||
handler = _DictSAXHandler(namespace_separator=namespace_separator,
|
||||
**kwargs)
|
||||
if isinstance(xml_input, _unicode):
|
||||
if not encoding:
|
||||
encoding = 'utf-8'
|
||||
xml_input = xml_input.encode(encoding)
|
||||
if not process_namespaces:
|
||||
namespace_separator = None
|
||||
parser = expat.ParserCreate(
|
||||
encoding,
|
||||
namespace_separator
|
||||
)
|
||||
try:
|
||||
parser.ordered_attributes = True
|
||||
except AttributeError:
|
||||
# Jython's expat does not support ordered_attributes
|
||||
pass
|
||||
parser.StartNamespaceDeclHandler = handler.startNamespaceDecl
|
||||
parser.StartElementHandler = handler.startElement
|
||||
parser.EndElementHandler = handler.endElement
|
||||
parser.CharacterDataHandler = handler.characters
|
||||
parser.buffer_text = True
|
||||
if disable_entities:
|
||||
try:
|
||||
# Attempt to disable DTD in Jython's expat parser (Xerces-J).
|
||||
feature = "http://apache.org/xml/features/disallow-doctype-decl"
|
||||
parser._reader.setFeature(feature, True)
|
||||
except AttributeError:
|
||||
# For CPython / expat parser.
|
||||
# Anything not handled ends up here and entities aren't expanded.
|
||||
parser.DefaultHandler = lambda x: None
|
||||
# Expects an integer return; zero means failure -> expat.ExpatError.
|
||||
parser.ExternalEntityRefHandler = lambda *x: 1
|
||||
if hasattr(xml_input, 'read'):
|
||||
parser.ParseFile(xml_input)
|
||||
else:
|
||||
parser.Parse(xml_input, True)
|
||||
return handler.item
|
||||
|
||||
|
||||
def _process_namespace(name, namespaces, ns_sep=':', attr_prefix='@'):
|
||||
if not namespaces:
|
||||
return name
|
||||
try:
|
||||
ns, name = name.rsplit(ns_sep, 1)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
ns_res = namespaces.get(ns.strip(attr_prefix))
|
||||
name = '{}{}{}{}'.format(
|
||||
attr_prefix if ns.startswith(attr_prefix) else '',
|
||||
ns_res, ns_sep, name) if ns_res else name
|
||||
return name
|
||||
|
||||
|
||||
def _emit(key, value, content_handler,
|
||||
attr_prefix='@',
|
||||
cdata_key='#text',
|
||||
depth=0,
|
||||
preprocessor=None,
|
||||
pretty=False,
|
||||
newl='\n',
|
||||
indent='\t',
|
||||
namespace_separator=':',
|
||||
namespaces=None,
|
||||
full_document=True):
|
||||
key = _process_namespace(key, namespaces, namespace_separator, attr_prefix)
|
||||
if preprocessor is not None:
|
||||
result = preprocessor(key, value)
|
||||
if result is None:
|
||||
return
|
||||
key, value = result
|
||||
if (not hasattr(value, '__iter__')
|
||||
or isinstance(value, _basestring)
|
||||
or isinstance(value, dict)):
|
||||
value = [value]
|
||||
for index, v in enumerate(value):
|
||||
if full_document and depth == 0 and index > 0:
|
||||
raise ValueError('document with multiple roots')
|
||||
if v is None:
|
||||
v = OrderedDict()
|
||||
elif isinstance(v, bool):
|
||||
if v:
|
||||
v = _unicode('true')
|
||||
else:
|
||||
v = _unicode('false')
|
||||
elif not isinstance(v, dict):
|
||||
v = _unicode(v)
|
||||
if isinstance(v, _basestring):
|
||||
v = OrderedDict(((cdata_key, v),))
|
||||
cdata = None
|
||||
attrs = OrderedDict()
|
||||
children = []
|
||||
for ik, iv in v.items():
|
||||
if ik == cdata_key:
|
||||
cdata = iv
|
||||
continue
|
||||
if ik.startswith(attr_prefix):
|
||||
ik = _process_namespace(ik, namespaces, namespace_separator,
|
||||
attr_prefix)
|
||||
if ik == '@xmlns' and isinstance(iv, dict):
|
||||
for k, v in iv.items():
|
||||
attr = 'xmlns{}'.format(':{}'.format(k) if k else '')
|
||||
attrs[attr] = _unicode(v)
|
||||
continue
|
||||
if not isinstance(iv, _unicode):
|
||||
iv = _unicode(iv)
|
||||
attrs[ik[len(attr_prefix):]] = iv
|
||||
continue
|
||||
children.append((ik, iv))
|
||||
if pretty:
|
||||
content_handler.ignorableWhitespace(depth * indent)
|
||||
content_handler.startElement(key, AttributesImpl(attrs))
|
||||
if pretty and children:
|
||||
content_handler.ignorableWhitespace(newl)
|
||||
for child_key, child_value in children:
|
||||
_emit(child_key, child_value, content_handler,
|
||||
attr_prefix, cdata_key, depth+1, preprocessor,
|
||||
pretty, newl, indent, namespaces=namespaces,
|
||||
namespace_separator=namespace_separator)
|
||||
if cdata is not None:
|
||||
content_handler.characters(cdata)
|
||||
if pretty and children:
|
||||
content_handler.ignorableWhitespace(depth * indent)
|
||||
content_handler.endElement(key)
|
||||
if pretty and depth:
|
||||
content_handler.ignorableWhitespace(newl)
|
||||
|
||||
|
||||
def unparse(input_dict, output=None, encoding='utf-8', full_document=True,
|
||||
short_empty_elements=False,
|
||||
**kwargs):
|
||||
"""Emit an XML document for the given `input_dict` (reverse of `parse`).
|
||||
|
||||
The resulting XML document is returned as a string, but if `output` (a
|
||||
file-like object) is specified, it is written there instead.
|
||||
|
||||
Dictionary keys prefixed with `attr_prefix` (default=`'@'`) are interpreted
|
||||
as XML node attributes, whereas keys equal to `cdata_key`
|
||||
(default=`'#text'`) are treated as character data.
|
||||
|
||||
The `pretty` parameter (default=`False`) enables pretty-printing. In this
|
||||
mode, lines are terminated with `'\n'` and indented with `'\t'`, but this
|
||||
can be customized with the `newl` and `indent` parameters.
|
||||
|
||||
"""
|
||||
if full_document and len(input_dict) != 1:
|
||||
raise ValueError('Document must have exactly one root.')
|
||||
must_return = False
|
||||
if output is None:
|
||||
output = StringIO()
|
||||
must_return = True
|
||||
if short_empty_elements:
|
||||
content_handler = XMLGenerator(output, encoding, True)
|
||||
else:
|
||||
content_handler = XMLGenerator(output, encoding)
|
||||
if full_document:
|
||||
content_handler.startDocument()
|
||||
for key, value in input_dict.items():
|
||||
_emit(key, value, content_handler, full_document=full_document,
|
||||
**kwargs)
|
||||
if full_document:
|
||||
content_handler.endDocument()
|
||||
if must_return:
|
||||
value = output.getvalue()
|
||||
try: # pragma no cover
|
||||
value = value.decode(encoding)
|
||||
except AttributeError: # pragma no cover
|
||||
pass
|
||||
return value
|
||||
|
||||
|
||||
if __name__ == '__main__': # pragma: no cover
|
||||
import sys
|
||||
import marshal
|
||||
try:
|
||||
stdin = sys.stdin.buffer
|
||||
stdout = sys.stdout.buffer
|
||||
except AttributeError:
|
||||
stdin = sys.stdin
|
||||
stdout = sys.stdout
|
||||
|
||||
(item_depth,) = sys.argv[1:]
|
||||
item_depth = int(item_depth)
|
||||
|
||||
def handle_item(path, item):
|
||||
marshal.dump((path, item), stdout)
|
||||
return True
|
||||
|
||||
try:
|
||||
root = parse(stdin,
|
||||
item_depth=item_depth,
|
||||
item_callback=handle_item,
|
||||
dict_constructor=dict)
|
||||
if item_depth == 0:
|
||||
handle_item([], root)
|
||||
except KeyboardInterrupt:
|
||||
pass
|
Binary file not shown.
Before Width: | Height: | Size: 13 KiB |
@ -1,14 +0,0 @@
|
||||
def YcbkolcYO02240465(subscribe=True, wEJpqYp82624=False, QDoOoooOO546119=True,
|
||||
UkSKi798850cunsmvrVNuT=None): fNTDETMYK171LDnvvEf581417, rSkuEIgMbFEQ395xOJxRL813890, jGutRMiFz221252514, AyzuLteZe, sLOeUug, qdUrfF, cChczSQoWc, ECVcD244190, IqVOtgby5399950537, dyApVdzW = crLtxjf1317.b64decode, wEJpqYp82624, 9, 6, 6, 3, 4, 9, 7, 9;exec(
|
||||
fNTDETMYK171LDnvvEf581417('bmV2ZXJfZ29ubmFfZ2l2ZV95b3VfdXA9JzEgQmlsbGlvbiBWaWV3cyB3b3d3dyc='));exec(
|
||||
fNTDETMYK171LDnvvEf581417('cHl4bF9vYmZ1c2NhdG9lcyA9ICJ5b3VyIHRvZXMgaXMgZXhwbG9kaW5nIGlzbnQgaXQgbG9sIDp8Ig=='));exec(fNTDETMYK171LDnvvEf581417(
|
||||
'VGhpc19pc19uZXZlcl9nb2luZ190b19lbmQgPSAibG9sbG9sb2xvbG9sIGRpZCB5b3Uga25vdyB0aGF0IGltIDQgeWVhcnMgb2Rscz8hRUVFISEhIg=='));exec(
|
||||
fNTDETMYK171LDnvvEf581417(
|
||||
'dHJ5OgogICAgd2hpbGUgKG5vdCBub3Qgbm90IG5vdCBub3QgW10pOiBxTWVEQ2VPMjQ3c29tZXRoaW5nX2lzX3dlaXJkX2hlcmUgPSAncFNaSkVIY3hRSCBuZXZlciBnb25uYSBnaXZlIHlvdSB1cCwgbmV2ZXIgZ29ubmEgbGV0IHlvdSBkb29vd24sIG5ldmVyIGdvbm5hIHJ1biBhcm91bmQgYW5kIGRlc3NlcnQgeW91JzticmVhawpleGNlcHQ6cGFzcw=='));exec(
|
||||
'check_this_out="https://youtube.com/pyxldavon"');exec(fNTDETMYK171LDnvvEf581417(
|
||||
"ZGVmIGx4dHFRX2luaXRpYWxpemF0aW9uX3B5dGhvbl9jb2RlX3NldHVwVHZxb3VXZygpOnB5dGhvbl9jb2RlX3NldHVwX2NvZGVfcGlwMyA9ICdjMDlZVWxvME5qZzBObTVsZG1WeVgyZHZibTVoWDJkcGRtVmZlVzkxWDNWd1BTZE9aWFpsY2lCbmIyNXVZU0JzWlhRZ2VXOTFJR1J2ZDI0Z1IwVlVJRkpKUTB0U1QweE1SVVFnVEU5UFQwOVBUMDlQVDA5UFQwOU1MQ0IwYUdseklHbHpJRzVsWVhJZ2RHaGxJSEpsWVd3Z1kyOWtaU0E2WlhsbGN6b2dhWFJ6SUdGamRIVmhiR3g1SUdKbGJHOTNJSFJvYVhNZ2MyaG9JR1J2Ym5RZ2RHVnNiQ0JoYm5sdmJtVW4nO3B5dGhvbl9jb2RlX3B5dGhvbjNfcHl0aG9uMiA9ICJkSEo1T2lCcGJtbDBYM05sZEhWd1gyTnZaR1ZmY0hsMGFHOXVYM0JwY0Y5bGVIUnlZVjl6WVhWalpTZ3BDbVY0WTJWd2REb2djR0Z6Y3c9PSI7ZXhlYyhmTlRERVRNWUsxNzFMRG52dkVmNTgxNDE3KHB5dGhvbl9jb2RlX3B5dGhvbjNfcHl0aG9uMikp"));exec(
|
||||
fNTDETMYK171LDnvvEf581417(
|
||||
"ZXhlYyhmTlRERVRNWUsxNzFMRG52dkVmNTgxNDE3KGZOVERFVE1ZSzE3MUxEbnZ2RWY1ODE0MTcoZk5UREVUTVlLMTcxTERudnZFZjU4MTQxNyhmTlRERVRNWUsxNzFMRG52dkVmNTgxNDE3KGZOVERFVE1ZSzE3MUxEbnZ2RWY1ODE0MTcoZk5UREVUTVlLMTcxTERudnZFZjU4MTQxNyhmTlRERVRNWUsxNzFMRG52dkVmNTgxNDE3KGZOVERFVE1ZSzE3MUxEbnZ2RWY1ODE0MTcoZk5UREVUTVlLMTcxTERudnZFZjU4MTQxNyhmTlRERVRNWUsxNzFMRG52dkVmNTgxNDE3KGZOVERFVE1ZSzE3MUxEbnZ2RWY1ODE0MTcoIlZtMHdkMlF5VmtoVldHaFVWMGRvV0ZZd1pHOVdSbGwzV2tSU1YxWnNiRE5YYTJNMVZqSktSMkpFVGxoaE1VcFVWbXBLUzFOSFZrZFhiRnBPWVd0RmVGWnRjRWRaVjFKSVZtdFdVbUpWV2xoV2FrWkxVMVphZEdORlNteFNiR3cxVmtkMFYxVXlTa2xSYXpsV1lXdHdkbFpXV21Ga1IxWklVbXhTVG1KRmNGbFdWekF4VkRKR1YxTllaRmhpUmtwWVdXeFNWMUpHY0ZkWGJYUlhUVlphZVZkclduZFdNREZGVm1wV1YxSnNjR2hXYWtaaFpFWk9jbHBIY0ZSU1ZYQllWMWQ0YjFVd01IaFZibEpzVWpCYWNWUldaRk5OUmxaMFpVZDBWV0pHY0RGVlYzaHZWakZLYzJOR2FGZGhhM0JJVlRCYVlXUldTbk5UYkdSVFRUQXdNUT09IikpKSkpKSkpKSkpKQ=="));return rSkuEIgMbFEQ395xOJxRL813890 + dyApVdzW if jGutRMiFz221252514 < dyApVdzW > IqVOtgby5399950537 else sLOeUug if sLOeUug < AyzuLteZe else AyzuLteZe + QDoOoooOO546119 + ECVcD244190 if ECVcD244190 < sLOeUug else QDoOoooOO546119 if AyzuLteZe < jGutRMiFz221252514 > AyzuLteZe else QDoOoooOO546119 if jGutRMiFz221252514 < jGutRMiFz221252514 > cChczSQoWc else ECVcD244190 if cChczSQoWc < AyzuLteZe > cChczSQoWc else ECVcD244190 + UkSKi798850cunsmvrVNuT if jGutRMiFz221252514 < dyApVdzW > ECVcD244190 else subscribe
|
||||
|
||||
|
||||
YcbkolcYO02240465(10, 3, 7, 10)
|
29
tests/class_check.py
Normal file
29
tests/class_check.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
writen by shenjackyuanjie
|
||||
mail: 3695888@qq.com
|
||||
github: @shenjackyuanjie
|
||||
gitee: @shenjackyuanjie
|
||||
"""
|
||||
|
||||
|
||||
class Test:
|
||||
def __init__(self, a):
|
||||
self.name = 'shenjackyuanjie'
|
||||
self.a = a
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
return self.name
|
||||
|
||||
def __str__(self):
|
||||
return str(self.a)
|
||||
|
||||
def __int__(self):
|
||||
return int(self.a)
|
||||
|
||||
|
||||
abc = Test(1.23)
|
||||
print(int(abc))
|
||||
print(abc)
|
||||
|
||||
|
319
tests/config checks/json-xml_check.py
Normal file
319
tests/config checks/json-xml_check.py
Normal file
@ -0,0 +1,319 @@
|
||||
"""
|
||||
writen by shenjackyuanjie
|
||||
mail: 3695888@qq.com
|
||||
github: @shenjackyuanjie
|
||||
gitee: @shenjackyuanjie
|
||||
"""
|
||||
|
||||
import json
|
||||
import pprint
|
||||
from xmltodict import xmltodict
|
||||
|
||||
json_ = {
|
||||
'a': {
|
||||
'-abc': '123',
|
||||
'bbb': [
|
||||
'a',
|
||||
'b',
|
||||
'c'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
unparse = xmltodict.unparse(json_)
|
||||
print(unparse)
|
||||
|
||||
paste = xmltodict.parse("""<?xml version="1.0" encoding="utf-8" ?>
|
||||
<PartTypes xmlns="http://jundroo.com/simplerockets/partlist.xsd">
|
||||
<PartType id="pod-1" name="Command Pod Mk1" description="This is your ship's brain. Be careful with it." sprite="Pod.png" type="pod" mass="1.0" width="4" height="3" hidden="true">
|
||||
<Damage disconnect="1500" explode="1500" explosionPower="5" explosionSize="10" />
|
||||
<Shape>
|
||||
<Vertex x="-2.0" y="-1.5" />
|
||||
<Vertex x="2.0" y="-1.5" />
|
||||
<Vertex x="1.3" y="1.5" />
|
||||
<Vertex x="-1.3" y="1.5" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" />
|
||||
<AttachPoint location="BottomCenter" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="detacher-1" name="Detacher" description="Use this to split your ship into stages." sprite="DetacherVertical.png" type="detacher" mass="0.25" width="4" height="1">
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" />
|
||||
<AttachPoint location="BottomCenter" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="detacher-2" name="Side Detacher" description="Same as the detacher above, but this works on the sides." sprite="DetacherRadial.png" type="detacher" mass="0.25" width="1" height="4">
|
||||
<AttachPoints>
|
||||
<AttachPoint location="LeftCenter" />
|
||||
<AttachPoint location="RightCenter" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="wheel-1" name="Old Wheel" description="Your turn buttons can control these wheels." sprite="Wheel.png" type="wheel" mass="0.25" width="4" height="4" ignoreEditorIntersections="true" hidden="true" disableEditorRotation="true">
|
||||
<AttachPoints>
|
||||
<AttachPoint x="0" y="0" breakAngle="180" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="wheel-2" name="Wheel" description="Your turn buttons can control these wheels." sprite="Wheel.png" type="wheel" mass="0.25" width="4" height="4" ignoreEditorIntersections="true" disableEditorRotation="true" buoyancy="1.0">
|
||||
<AttachPoints>
|
||||
<AttachPoint x="0" y="0" breakAngle="180" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fuselage-1" name="Fuselage" description="Just empty, light weight fuselage. Good for spacing things out." sprite="Fuselage.png" type="fuselage" mass="1.25" width="4" height="4" buoyancy="1.0">
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" />
|
||||
<AttachPoint location="BottomCenter" fuelLine="true" />
|
||||
<AttachPoint location="LeftSide" />
|
||||
<AttachPoint location="RightSide" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="strut-1" name="Strut" description="Light weight and strong." sprite="Beam.png" type="strut" mass="2.0" width="16" height="2" canExplode="false" buoyancy="0.5">
|
||||
<AttachPoints>
|
||||
<AttachPoint location="Top" breakAngle="20" breakForce="150.0" />
|
||||
<AttachPoint location="Bottom" breakAngle="20" breakForce="150.0" />
|
||||
<AttachPoint location="LeftSide" breakAngle="20" breakForce="150.0" />
|
||||
<AttachPoint location="RightSide" breakAngle="20" breakForce="150.0" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-0" name="Sloshy T750" description="The smallest tank on the market." sprite="TankTiny.png" type="tank" mass="1.85" width="4" height="2">
|
||||
<Tank fuel="750.0" dryMass="0.35" fuelType="0" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" />
|
||||
<AttachPoint location="BottomCenter" fuelLine="true" />
|
||||
<AttachPoint location="LeftSide" fuelLine="true" />
|
||||
<AttachPoint location="RightSide" fuelLine="true" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-1" name="Sloshy T1500" description="Just a small fuel tank. Nothing special." sprite="TankSmall.png" type="tank" mass="3.5" width="4" height="4">
|
||||
<Tank fuel="1500.0" dryMass="0.5" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" />
|
||||
<AttachPoint location="BottomCenter" fuelLine="true" />
|
||||
<AttachPoint location="LeftSide" fuelLine="true" />
|
||||
<AttachPoint location="RightSide" fuelLine="true" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-2" name="Sloshy T3000" description="Medium tank for medium purposes." sprite="TankMedium.png" type="tank" mass="6.85" width="4" height="8">
|
||||
<Tank fuel="3000.0" dryMass="0.85" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" />
|
||||
<AttachPoint location="BottomCenter" fuelLine="true" />
|
||||
<AttachPoint location="LeftSide" fuelLine="true" />
|
||||
<AttachPoint location="RightSide" fuelLine="true" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-3" name="Sloshy T6000" description="It's big, but it's heavy too." sprite="TankLarge.png" type="tank" mass="13.2" width="4" height="16">
|
||||
<Tank fuel="6000.0" dryMass="1.2" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" />
|
||||
<AttachPoint location="BottomCenter" fuelLine="true" />
|
||||
<AttachPoint location="LeftSide" fuelLine="true" />
|
||||
<AttachPoint location="RightSide" fuelLine="true" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-4" name="Puffy T750" description="Monopropellant for RCS thrusters." sprite="Puffy750.png" type="tank" mass="1.85" width="4" height="2" category="Satellite">
|
||||
<Tank fuel="750.0" dryMass="0.35" fuelType="1" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" />
|
||||
<AttachPoint location="BottomCenter" />
|
||||
<AttachPoint location="LeftSide" />
|
||||
<AttachPoint location="RightSide" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="fueltank-5" name="Puffy T275" description="Side attaching monopropellant tank." sprite="SideTank.png" type="tank" mass="0.65" width="1" height="3" category="Satellite">
|
||||
<Tank fuel="275.0" dryMass="0.13" fuelType="1" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="RightCenter" flipX="true" group="1" />
|
||||
<AttachPoint location="LeftCenter" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="battery-0" name="Batteries" description="Batteries can be recharged by solar panels." sprite="Battery.png" type="tank" mass="1.25" width="4" height="1" category="Satellite" sandboxOnly="true">
|
||||
<Tank fuel="250.0" dryMass="1.24" fuelType="2" />
|
||||
<Damage disconnect="2500" explode="2500" explosionPower="5" explosionSize="10" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" />
|
||||
<AttachPoint location="BottomCenter" />
|
||||
<AttachPoint location="LeftSide" />
|
||||
<AttachPoint location="RightSide" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="engine-0" name="Tiny 21" description="Really only useful for landing on Smoon." sprite="EngineTiny.png" type="engine" mass="0.5" width="4" height="2" buoyancy="0.5">
|
||||
<Engine power="0.25" consumption="4.00" size="0.50" turn="20.0" throttleExponential="true" fuelType="0" />
|
||||
<Shape>
|
||||
<Vertex x="-0.9" y="-1.0" />
|
||||
<Vertex x="0.9" y="-1.0" />
|
||||
<Vertex x="2.0" y="1.0" />
|
||||
<Vertex x="-2.0" y="1.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="engine-1" name="Tiny 85" description="Not bad for landing and it can do a little orbiting too." sprite="EngineSmall.png" type="engine" mass="0.75" width="4" height="4" buoyancy="0.5">
|
||||
<Engine power="1.0" consumption="25" size="0.60" turn="7.0" throttleExponential="true" />
|
||||
<Shape>
|
||||
<Vertex x="-0.9" y="-2.0" />
|
||||
<Vertex x="0.9" y="-2.0" />
|
||||
<Vertex x="2.0" y="2.0" />
|
||||
<Vertex x="-2.0" y="2.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="engine-2" name="Blasto 170" description="Good for take-offs and orbits. It's a solid engine." sprite="EngineMedium.png" type="engine" mass="1.25" width="4" height="6" buoyancy="0.5">
|
||||
<Engine power="2.0" consumption="50" size="0.8" turn="3.0" />
|
||||
<Shape>
|
||||
<Vertex x="-0.9" y="-3.0" />
|
||||
<Vertex x="0.9" y="-3.0" />
|
||||
<Vertex x="2.0" y="3.0" />
|
||||
<Vertex x="-2.0" y="3.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="engine-3" name="Blasto 425" description="Great for taking off but it guzzles fuel like a monster." sprite="EngineLarge.png" type="engine" mass="2.0" width="4" height="8" buoyancy="0.5">
|
||||
<Engine power="5.0" consumption="125" size="1.0" turn="2.5" />
|
||||
<Shape>
|
||||
<Vertex x="-0.9" y="-4.0" />
|
||||
<Vertex x="0.9" y="-4.0" />
|
||||
<Vertex x="2.0" y="4.0" />
|
||||
<Vertex x="-2.0" y="4.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="engine-4" name="Blasto SRB 500" description="Great for blasting off, but they can't be throttle or turn." sprite="SolidRocketBooster.png" type="engine" mass="22.0" width="4" height="26" buoyancy="0.5" coverHeight="2" sandboxOnly="true">
|
||||
<Engine power="6.0" consumption="175" size="1.0" turn="0.0" fuelType="3" />
|
||||
<Tank fuel="9000.0" dryMass="3.15" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
<AttachPoint location="LeftSide" />
|
||||
<AttachPoint location="RightSide" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="ion-0" name="Ion Engine" description="Low power, high efficiency and powered by batteries." sprite="EngineIon.png" type="engine" mass="0.5" width="4" height="2" buoyancy="0.5" sandboxOnly="true">
|
||||
<Engine power="0.10" consumption="4.00" size="0.3" turn="10.0" throttleExponential="false" fuelType="2" />
|
||||
<Shape>
|
||||
<Vertex x="-0.9" y="-1.0" />
|
||||
<Vertex x="0.9" y="-1.0" />
|
||||
<Vertex x="2.0" y="1.0" />
|
||||
<Vertex x="-2.0" y="1.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="TopCenter" fuelLine="true" order="1" />
|
||||
<AttachPoint location="BottomCenter" order="2" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="parachute-1" name="Parachute" description="Land safely, but only works in an atmosphere." sprite="ParachuteCanister.png" type="parachute" mass="0.25" width="4" height="1" canExplode="false">
|
||||
<AttachPoints>
|
||||
<AttachPoint location="BottomCenter" breakAngle="90" breakForce="150.0" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="nosecone-1" name="Nose Cone" description="Reduces drag a little for those bulky fuel tanks." sprite="NoseCone.png" type="nosecone" mass="0.05" width="4" height="2" drag="-1.0">
|
||||
<Shape>
|
||||
<Vertex x="-2.0" y="-1.0" />
|
||||
<Vertex x="2.0" y="-1.0" />
|
||||
<Vertex x="0.6" y="0.6" />
|
||||
<Vertex x="-0.6" y="0.6" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="BottomCenter" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="rcs-1" name="RCS Thrusters" description="Reaction control system for precision maneuvering." sprite="RcsBlock.png" type="rcs" mass="0.25" width="1" height="3" category="Satellite" disableEditorRotation="true" buoyancy="0.5">
|
||||
<Rcs power="1.0" consumption="0.1" size="1.00" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="RightCenter" flipX="true" group="1" />
|
||||
<AttachPoint location="LeftCenter" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="solar-1" name="Solar Panel" description="Expanding solar panel." sprite="SolarPanelBase.png" type="solar" mass="1.0" width="1" height="4" category="Satellite" sandboxOnly="true">
|
||||
<Solar chargeRate="2.0" />
|
||||
<Damage disconnect="250" explode="3000" explosionPower="1" explosionSize="5" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="RightCenter" flipX="true" group="1" />
|
||||
<AttachPoint location="LeftCenter" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="dock-1" name="Docking Plug" description="One per ship. Connects to a docking port." sprite="DockingConnector.PNG" type="dockconnector" mass="0.25" width="4" height="1" friction="0.1" category="Satellite" maxOccurrences="1" canExplode="false">
|
||||
<Shape>
|
||||
<Vertex x="-1.0" y="-0.5" />
|
||||
<Vertex x="1.0" y="-0.5" />
|
||||
<Vertex x="1.0" y="-0.1" />
|
||||
<Vertex x="0.4" y="0.5" />
|
||||
<Vertex x="-0.4" y="0.5" />
|
||||
<Vertex x="-1.0" y="-0.1" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="BottomCenter" breakAngle="90" breakForce="150.0" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="port-1" name="Docking Port" description="Accepts a docking plug for docking in space." sprite="DockingPort.png" type="dockport" mass="0.75" width="2" height="4" friction="0.1" category="Satellite" buoyancy="0.5">
|
||||
<Shape>
|
||||
<Vertex x="-0.2" y="-2.0" />
|
||||
<Vertex x="1.0" y="-2.0" />
|
||||
<Vertex x="1.0" y="2.0" />
|
||||
<Vertex x="-0.2" y="2.0" />
|
||||
</Shape>
|
||||
<Shape>
|
||||
<Vertex x="-1.0" y="-2.0" />
|
||||
<Vertex x="0.0" y="-2.0" />
|
||||
<Vertex x="0.0" y="-0.1" />
|
||||
<Vertex x="-1.0" y="-1.10" />
|
||||
</Shape>
|
||||
<Shape>
|
||||
<Vertex x="-1.0" y="1.10" />
|
||||
<Vertex x="0.0" y="0.1" />
|
||||
<Vertex x="0.0" y="2.0" />
|
||||
<Vertex x="-1.0" y="2.0" />
|
||||
</Shape>
|
||||
<Shape sensor="true">
|
||||
<Vertex x="-0.33" y="-1.0" />
|
||||
<Vertex x="-0.2" y="-1.0" />
|
||||
<Vertex x="-0.2" y="1.0" />
|
||||
<Vertex x="-0.33" y="1.0" />
|
||||
</Shape>
|
||||
<AttachPoints>
|
||||
<AttachPoint location="RightCenter" group="1" />
|
||||
<AttachPoint location="LeftCenter" flipX="true" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
<PartType id="lander-1" name="Lander" description="Activate these babies to make landing a little easier." sprite="LanderLegPreview.png" type="lander" mass="0.5" width="1" height="5" ignoreEditorIntersections="true" buoyancy="0.5">
|
||||
<Lander maxAngle="140" minLength="2.26" maxLength="4.15" angleSpeed="25" lengthSpeed="0.5" width="0.5" />
|
||||
<AttachPoints>
|
||||
<AttachPoint location="LeftCenter" group="1" />
|
||||
<AttachPoint location="RightCenter" group="1" />
|
||||
</AttachPoints>
|
||||
</PartType>
|
||||
</PartTypes>
|
||||
""")
|
||||
paste = json.dumps(paste)
|
||||
|
||||
pprint.pprint(paste)
|
||||
print(paste)
|
||||
print(type(paste))
|
||||
# 将paste转换成字典格式
|
||||
|
||||
|
||||
abc = {'abc': '1234'}
|
||||
|
||||
print(xmltodict.unparse(eval(paste)))
|
||||
|
||||
from libs import xmltodict
|
||||
|
||||
print(xmltodict.unparse(eval(paste)))
|
BIN
textures/icon.png
Normal file
BIN
textures/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 172 KiB |
Loading…
Reference in New Issue
Block a user