This commit is contained in:
shenjackyuanjie 2021-11-06 19:07:32 +08:00
parent 2f1f812432
commit 10a097290b
74 changed files with 2380 additions and 241 deletions

View File

@ -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()

View File

@ -14,6 +14,7 @@ gitee: @shenjackyuanjie
version = '0.6.1'
__version__ = version
playing = False
if playing:

View File

@ -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']

View 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

View File

@ -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)

View File

@ -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

View 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

View File

@ -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):

View File

@ -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')

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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) : 文档矫正

View File

@ -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

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
View 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
View 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.

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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:

View File

@ -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

View 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
View 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']

View 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.')

View 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
View 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

View File

@ -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
View 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)

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB