initial commit

This commit is contained in:
2019-12-23 12:42:31 +08:00
parent 30458acfd8
commit 1ac6d0bb9c
22 changed files with 2126 additions and 1 deletions

304
lib/console.py Normal file
View File

@@ -0,0 +1,304 @@
from . import utils
import os
import traceback
class console():
def __init__(self, name='base'):
self.name = name
self.hint = '$ '
self.exit_cmd = ['exit', 'quit', 'bye']
self.exit_info = 'Bye~'
self.commands = {}
self.alias = {}
self.warn_level = 4
self.exit_flag = False
self.debug = True
self.platform = utils.detect_platform()
self.is_child = False
self.father = None
self.regist_internal_command()
def get_hint(self):
if self.platform == 'Linux':
hint = '\033[0;33m({0})\033[0;31m{1}\033[0m'.format(self.name, self.hint)
else:
hint = '({0}){1}'.format(self.name, self.hint)
return hint
def regist_internal_command(self):
self.regist(
'help',
action=self.command_help,
alias=['h'],
help_info='display this help info.',
kind='sys'
)
self.regist(
'exit',
action=self.command_exit_console,
alias=['quit','bye'],
help_info='exit current console.',
kind='sys'
)
self.regist(
'cls',
action=self.command_clear_screen,
alias=['clear', 'clc'],
help_info='clear screen.',
kind='sys'
)
self.regist(
'alias',
action=self.command_alias,
help_info='display alias info or create new alias.',
kind='sys'
)
self.regist(
'os',
action=self.command_os,
help_info='run a system command.',
kind='sys'
)
def translate_command(self, command):
while command in self.alias and command not in self.commands:
command = self.alias[command]
return command
def find_equal_command(self, command, ret_type = str, ignored = []):
finished = []
new = []
cmds = [command]
while len(finished) != len(cmds):
# find child
if command in self.alias:
if self.alias[command] not in cmds:
cmds.append(self.alias[command])
# find fathers
for al in self.alias:
if self.alias[al] == command:
if al not in cmds:
cmds.append(al)
# found finished.
finished.append(command)
for cmd in cmds:
if cmd not in finished:
command = cmd
if ret_type is str:
finished = utils.list2csv(finished)
return finished
def get_alias(self, command, ret_type=str):
alias = []
for al in self.alias:
if self.alias[al] == command:
alias.append(al)
if ret_type is str:
alias = utils.list2csv(alias)
return alias
def command_exist(self, command):
if command in self.commands or command in self.alias:
return True
else:
return False
def add_alias(self, command, alias):
if self.command_exist(alias):
if warn_level >= 3:
print('Alias {0} will not be added since already used'.format(al))
else:
self.alias[alias] = command
# kind: standard or shared
# standard: help info will be displayed
# shared: help info will not be displayed in sub command.
def regist(self, command, action, alias=None, help_info='no help provided.', kind='standard'):
if type(action) == console:
action.is_child = True
action.father = self
exist = self.command_exist(command)
if exist:
if self.warn_level >=3:
print('Command {0} will not be added sinece already exist.'.format(command))
return
if type(alias) is list:
for al in alias:
self.add_alias(command, al)
elif type(alias) is str:
self.add_alias(command, alias)
elif alias is None:
pass
else:
if self.warn_level > 3:
print('Unknown alias type, no alias will be added.')
self.commands[command] = {}
self.commands[command]['action'] = action
self.commands[command]['help'] = help_info
self.commands[command]['kind'] = kind
def handle_command(self, command, args):
if command in self.commands:
act = self.commands[command]['action']
try:
act(args)
except KeyboardInterrupt:
pass
except:
print('Exception occured while processing command \"{0} {1}\".'.format(command, args))
print('More information are shown below.\n', traceback.format_exc())
else:
print('Unknown command \"{0}\"'.format(command))
# seperate command and its args.
def parse_command(self, string):
string += ' '
length = len(string)
command_end = 0
parse_start = False
for i in range(length):
blank = utils.is_blank(string[i])
if not blank:
parse_start=True
if parse_start and blank:
command_end = i
break
command = string[:command_end]
command = utils.remove_blank_in_endpoint(command)
args = utils.remove_blank_in_endpoint(string[command_end:])
return command, args
def parse(self, string):
command, args = self.parse_command(string)
exitsted_commands = []
while command in self.alias:
if command not in exitsted_commands:
exitsted_commands.append(command)
command = self.alias[command]
string = command + ' ' + args
command, args = self.parse_command(string)
else:
break
return command, args
def show_help_info(self, command, prefix, indent, depth=0):
command = self.translate_command(command)
action = self.commands[command]['action']
kind = self.commands[command]['kind']
if kind == 'sys' and depth > 0:
return
alias = self.get_alias(command, ret_type=str)
if alias != '':
print('{0}{1}({2}):'.format(prefix, command, alias))
else:
print('{0}{1}:'.format(prefix, command))
print('{0}{1}{2}'.format(prefix, indent, self.commands[command]['help']))
if type(action) == console:
action.command_help('', prefix=prefix+indent, indent=indent, depth=depth+1)
def debug_log(self, command, args):
if self.debug:
print('command:[{0}] args:[{1}]'.format(command, args))
def command_exit_console(self, args):
if not self.is_child:
print(self.exit_info)
self.exit_flag = True
def command_clear_screen(self, args):
if self.platform == 'Windows':
os.system('cls')
elif self.platform == 'Linux':
os.system('clear')
return False
def command_help(self, args, prefix = '', indent=' ', depth=0):
command, args = self.parse_command(args)
if command is not "":
if self.command_exist(command):
self.show_help_info(command, prefix, indent, depth)
else:
print('Unknown command \"{0}\"'.format(command))
else:
for command in self.commands:
self.show_help_info(command, prefix, indent, depth)
def command_alias(self, args):
alias_parse = args.split('=')
if len(alias_parse) == 2:
alias = utils.remove_blank_in_endpoint(alias_parse[0])
command = utils.remove_blank_in_endpoint(alias_parse[1])
if command is not '':
self.alias[alias]=command
else:
del self.alias[alias]
elif args == '':
for alias in self.alias:
print('{0}={1}'.format(alias, self.alias[alias]))
elif len(alias_parse) == 1:
if args in self.alias:
print('{0}={1}'.format(args, self.alias[args]))
equal_alias = self.find_equal_command(args)
if equal_alias != '':
print('Hint: {0} are all equivalent.'.format(equal_alias))
elif args in self.commands:
als = self.get_alias(args, ret_type=str)
if als == '':
print('command {0} has no alias.'.format(args))
else:
print('command {0} is aliased as {1}'.format(args, als))
equal_alias = self.find_equal_command(args)
if equal_alias != '' and equal_alias != args:
print('Hint: {0} are all equivalent.'.format(equal_alias))
else:
print('No alias \"{0}\" found.'.format(args))
else:
print('Syntax error, command not understood.')
def command_os(self, args):
if args == '':
print('please specify os command')
else:
os.system(args)
def execute(self, string):
command, args = self.parse(string)
if command is not "":
self.handle_command(command, args)
def __call__(self, args):
if args != '':
self.execute(args)
else:
self.exit_flag=False
self.interactive()
def interactive(self):
while not self.exit_flag:
try:
input_str = input(self.get_hint())
self.execute(input_str)
except(KeyboardInterrupt):
print('')
if __name__ == '__main__':
con = console()
con_sub = console()
con_sub_sub = console()
con_sub.regist('test_subsubcommand', con_sub_sub, alias='tss', help_info='A sub command.')
con.regist('test_subcommand', con_sub, alias='ts', help_info='A sub command.')
con.interactive()

127
lib/parallel.py Normal file
View File

@@ -0,0 +1,127 @@
import threading
import queue
import time
class Job():
def __init__(self, func, args=[], kwargs={}, name=None):
if name == None:
name = 'job'
self.id = None
self.name = name
self.func = func
self.args = args
self.kwargs = kwargs
self.results = None
def run(self):
self.results = self.func(*self.args, **self.kwargs)
def set_name(self, name):
self.name = name
def set_id(self, jid):
self.id = jid
def __call__(self):
self.run()
class Worker(threading.Thread):
def __init__(self, work_queue, finished_queue):
super(Worker, self).__init__()
self.queue = work_queue
self.finished = finished_queue
self.terminate = False
self.daemon=True
def stop(self):
self.terminate = True
def run(self):
while not self.terminate:
try:
task = self.queue.get(timeout=1)
task.run()
self.queue.task_done()
self.finished.put(task)
except queue.Empty:
pass
except KeyboardInterrupt:
print("you stop the threading")
class ParallelHost():
def __init__(self, num_threads=8):
self.num_threads = num_threads
self.workers = []
self.tasks = queue.Queue()
self.results = queue.Queue()
self.rets = {}
self.id = 0
for i in range(self.num_threads):
worker = Worker(self.tasks, self.results)
self.workers.append(worker)
for worker in self.workers:
worker.start()
def __del__(self):
self.stop('kill')
# soft stop: wait until all job done
# hard stop: stop even with unfinished job
# kill stop: whatever the thread is doing, exit.
def stop(self, mode='soft'):
print('Trying to stop.')
if mode == 'soft':
self.tasks.join()
print('All job finished.')
for worker in self.workers:
worker.stop()
if mode == 'kill':
worker.join(0.01)
def commit(self, job):
self.id += 1
job.set_id(self.id)
self.tasks.put(job)
return self.id
def add_job(self, func, args=[], kwargs={}, name=None):
job = Job(func, args, kwargs, name)
return self.commit(job)
def collect_all(self):
while not self.results.empty():
task = self.results.get()
jid = task.id
self.rets[jid] = task.results
def get_result(self, jid, block=False):
if jid in self.rets:
ret = self.rets[jid]
del self.rets[jid]
return ret
while True:
if self.results.empty() and not block:
break
task = self.results.get()
if task.jid == jid:
return task.results
else:
self.rets[task.jid] = task.results
def clear_results(self):
while not self.results.empty():
self.results.get()
self.rets = {}
if __name__ == '__main__':
host = ParallelHost()
def loop_print(info, num):
for i in range(num):
print(info + ':' + str(i))
time.sleep(1)
for i in range(10):
host.add_job(loop_print, ["loop_print_{0}".format(i), 5])
host.terminate('kill')

151
lib/parser.py Normal file
View File

@@ -0,0 +1,151 @@
from html.parser import HTMLParser
from . import utils
def dict_to_arrtibute_string(attributes):
string = ''
for key in attributes:
string += key + '=\"{0}\";'.format(str(attributes[key]))
return string
def attribute_string_to_dict(attrs):
attr_dict = {}
for attr in attrs:
attr_dict[attr[0]] = attr[1]
return attr_dict
class dom_node():
def __init__(self, name = None, attributes = {}):
if name is not None:
self.name = name
else:
self.name = 'Node'
self.attributes = attributes
self.childs = []
self.data = None
self.father = None
def add_child(self, child):
if child is not None:
child.father = self
self.childs.append(child)
def to_string(self, prefix='', indent=' '):
string = prefix + '<' + self.name
if self.attributes:
string += ' ' + dict_to_arrtibute_string(self.attributes)
string += '>\n'
for child in self.childs:
string += child.to_string(prefix=prefix+indent, indent=indent)
if self.data is not None:
string += prefix + indent + self.data + '\n'
string += prefix + '</{0}>\n'.format(self.name)
return string
def has_child(self, name):
has = False
for child in self.childs:
if child.name == name:
has = True
break;
return has
def search(self, name):
founded_node = []
if type(name) is list:
if self.name in name:
founded_node.append(self)
else:
if self.name == name:
founded_node.append(self)
for child in self.childs:
search_result = child.search(name)
founded_node += search_result
return founded_node
def dict2dom(d, root_name='root'):
node = dom_node(root_name)
for key in d:
elem = d[key]
child_node = dom_node(name=str(key))
if type(elem) is dict:
child_node = dict2dom(elem, root_name=str(key))
elif type(elem) is list:
for subelem in elem:
if type(subelem) is dict:
sub_node = dict2dom(subelem, root_name='li')
child_node.add_child(sub_node)
else:
sub_node = dom_node('li')
sub_node.data = str(subelem)
child_node.add_child(sub_node)
else:
child_node.data = str(elem)
node.add_child(child_node)
return node
# if a dom node has data only, then it's {'name':'data'}
# if a dom node has childs, then it's {'name':{}}
# if a dom node has data as well as childs, data will be ignored.
# if a dom has multi child with same name, it will be stored as list.
def dom2dict(dom, replace_li = True):
dictionary = {}
for child in dom.childs:
name = child.name
content = None
if len(child.childs) != 0:
content = dom2dict(child, replace_li)
else:
content = child.data
if content is None:
content = ''
content = utils.clean_text(content)
if name in dictionary:
if type(dictionary[name]) is not list:
previous = dictionary[name]
dictionary[name] = [previous, content]
else:
dictionary[name].append(content)
else:
dictionary[name] = content
if replace_li:
for key in dictionary:
item = dictionary[key]
if type(item) is dict:
li = None
if len(item.keys()) == 1:
for subkey in item:
if subkey == 'li':
li = item[subkey]
if li is not None:
dictionary[key] = li
return dictionary
class simple_parser(HTMLParser):
def __init__(self):
super(simple_parser, self).__init__()
self.root = dom_node('root')
self.current_node = self.root
def handle_starttag(self, tag, attrs):
attrs_dict = attribute_string_to_dict(attrs)
this_node = dom_node(tag, attrs_dict)
self.current_node.add_child(this_node)
self.current_node = this_node
def handle_endtag(self, tag):
self.current_node = self.current_node.father
def handle_data(self, data):
if self.current_node.data is None:
self.current_node.data = data
else:
self.current_node.data += data

19
lib/screen.py Normal file
View File

@@ -0,0 +1,19 @@
import sys
class VirtualScreen():
def __init__(self, max_history=1000):
self.max_history = max_history
self.contents = []
def write(self, message):
self.contents.append(message)
def last(self, line=10, output=sys.stdout):
num_lines = len(self.contents)
start_line = num_lines - line
if start_line < 0:
start_line = 0
display = self.contents[start_line:]
for line in display:
output.write(line)
output.write('\n')

244
lib/service.py Normal file
View File

@@ -0,0 +1,244 @@
import time
import sys
import shlex
import argparse
from croniter import croniter
from . import utils
from . import parallel
from . import console
from . import screen
from . import utils
class service():
def __init__(self, action, args=[], kwargs={}, cron='* * * * *', managed_output=False, name='service'):
self.name = name
self.action = action
self.managed_output = managed_output
self.args = args
self.kwargs = kwargs
self.output = sys.stdout
self.last_result = None
self.cronexpr = cron
self.croniter = croniter(self.cronexpr, time.time())
self.next_time = self.croniter.get_next()
def run(self, daemon=None, dry=False):
if not dry:
self.next_time = self.croniter.get_next()
new_args = []
if self.managed_output:
new_args = [self.output, *self.args]
else:
new_args = self.args
if daemon is None:
self.last_result = self.action(*new_args, **self.kwargs)
else:
daemon.add_job(self.action, new_args, self.kwargs, self.name)
class ServiceManager():
def __init__(self, debug=False, output=sys.stdout):
self.debug = debug
self.services = {}
self.deleted_services = {}
self.protected_service = []
self.daemon = parallel.ParallelHost()
self.sid = 0
self.terminate = False
self.output = output
self.set_refresh_time()
def stop(self):
self.daemon.stop()
self.terminate = True
def __del__(self):
self.stop()
def log(self, *args, end='\n'):
self.output.write('[{0}]'.format(utils.str_time()))
for arg in args:
arg = str(arg)
self.output.write(arg)
self.output.write(end)
def add(self, service, protected=False):
self.sid += 1
service.output = self.output
self.services[self.sid] = service
if protected:
self.protected_service.append(self.sid)
return self.sid
def delete(self, sid):
if sid in self.protected_service:
self.log('Can not delete protected service.')
return
if sid in self.services:
self.deleted_services[sid] = self.services[sid]
del self.services[sid]
else:
self.log('The sid [{0}] do not exist!'.format(sid))
def recover(self, sid):
if sid in self.deleted_services:
self.services[sid] = self.deleted_services[sid]
del self.deleted_services[sid]
else:
self.log('The sid [{0}] is not found recycle bin.'.format(sid))
def set_refresh_time(self, refresh_cron='* * * * *'):
def refresh():
pass
refresh_service = service(refresh, cron=refresh_cron, name='refresh')
self.add(refresh_service, protected = True)
def get_next(self):
next_sid = -1
next_time = -1
for sid in self.services:
service = self.services[sid]
if service.next_time < next_time or next_sid < 0:
next_sid = sid
next_time = service.next_time
return next_sid, next_time
def loop(self):
while not self.terminate:
next_sid, next_time = self.get_next()
service = self.services[next_sid]
sleep_time = next_time - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
self.log('Running service {0} (SID={1})'.format(service.name, next_sid))
if next_sid in self.services:
service.run(self.daemon)
else:
self.log('the sheduled service wiil not run since it is canceled.')
# mode: background: return immidietly
# foreground: stuck here.
def start(self, mode='background'):
if mode == 'background':
self.daemon.add_job(self.loop, name='service main loop')
else:
self.loop()
def get_service_console(manager, name='service'):
con = console.console(name)
def command_show(args):
print('Active services:')
for sid in manager.services:
print('SID: {0} | Name: {1}'.format(sid, manager.services[sid].name))
print('Deleted services:')
for sid in manager.deleted_services:
print('SID: {0} | Name: {1}'.format(sid, manager.deleted_services[sid].name))
def command_add(args):
parser = argparse.ArgumentParser()
parser.add_argument('cron', type=str, help='A cron expr')
parser.add_argument('task', type=str, help='task to run, should be a valid command')
parser.add_argument('--name', '-n', type=str, default='command service', help='name of the task')
args = shlex.split(args)
args = parser.parse_args(args)
cron = args.cron
if not croniter.is_valid(cron):
print('Invalid cron expression.')
task = args.task
name = args.name
service_to_add = service(con.execute, args=[task], cron=cron, name=name)
manager.add(service_to_add)
def command_delete(args):
sid = None
if args.isdigit():
if int(args) in manager.services:
sid = int(args)
if sid is not None:
manager.delete(sid)
else:
print('command arugment \"{0}\" is not understood.'.format(args))
def command_recover(args):
sid = None
if args.isdigit():
if int(args) in manager.deleted_services:
sid = int(args)
if sid is not None:
manager.recover(sid)
else:
print('command arugment \"{0}\" is not understood.'.format(args))
def command_run(args):
sid = None
if args.isdigit():
if int(args) in manager.services:
sid = int(args)
if sid is not None:
manager.services[sid].run(dry=True)
else:
print('command arugment \"{0}\" is not understood.'.format(args))
def command_info(args):
line = None
if args != '':
if args.isdigit():
line = int(args)
if line is None:
line = 10
manager.output.last(line)
def command_next(args):
next_sid, next_time = manager.get_next()
info = ''
indent = ' '
info += 'Next Job: {0}'.format(manager.services[next_sid].name)
info += '\n{0}SID: {1}'.format(indent, next_sid)
info += '\n{0}Scheduled Running Time: {1}'.format(indent, utils.time2str(next_time))
info += '\n{0}Remeaning Time: {1}s'.format(indent, utils.float2str(next_time-time.time()))
print(info)
con.regist('show', command_show, help_info='Show all services.', alias=['ls'])
con.regist('run', command_run, help_info='Run a service.')
con.regist('info', command_info, help_info='Display service output log.')
con.regist('next', command_next, help_info='Next job to run.')
con.regist('add', command_add, help_info='Register a command as service.')
con.regist('delete', command_delete, help_info='Delete a service', alias=['del'])
con.regist('recover', command_recover, help_info='Recover a service.')
return con
if __name__ == '__main__':
def func1(output):
output.write('func1')
def func2(output):
output.write('func2')
def add(a, b):
print('{0} + {1} = {2}'.format(a, b, a+b))
def command_add(args):
numbers = args.split(' ')
a = float(numbers[0])
b = float(numbers[1])
add(a, b)
log_screen = screen.VirtualScreen()
manager = ServiceManager(output=log_screen)
test1 = service(func1, cron='* * * * *', name='test1', managed_output=True)
test2 = service(func2, cron='* * * * *', name='test2', managed_output=True)
manager.add(test1)
manager.add(test2)
manager.start('background')
con = get_service_console(manager)
master = console.console()
master.regist('service', con, help_info='service console')
master.regist('add', command_add, help_info='Add two numbers.')
master.interactive()

15
lib/try.py Normal file
View File

@@ -0,0 +1,15 @@
def func(a, b, c, time=0, work=1):
print('a:{0} b:{1} c:{2}'.format(a, b, c))
print('time:{0} work:{1}'.format(time, work))
def funcwrap(func, kargs, kkargs):
func(*kargs, **kkargs)
kargs = [1, 2, 3]
kkargs = {
"time":1234,
"work":1232
}
funcwrap(func, kargs, kkargs)

139
lib/utils.py Normal file
View File

@@ -0,0 +1,139 @@
import pickle
import time
import os
import re
import platform
def detect_platform():
p = 'Unknown'
if platform.platform().find('Windows') != -1:
p = 'Windows'
elif platform.platform().find('Linux') != -1:
p = 'Linux'
return p
def ensure_dir_exist(directory, show_info = True):
exist = os.path.isdir(directory)
if not exist:
print('directory', directory, ' not found, creating...')
os.mkdir(directory)
def validateTitle(title):
rstr = r"[\/\\\:\*\?\"\<\>\|]" # '/ \ : * ? " < > |'
new_title = re.sub(rstr, " ", title) # 替换为空格
return new_title
def list2csv(l):
csv = ''
for item in l:
csv += str(item) + ','
csv = csv[:-1]
return csv
def clean_text(string):
if string is None:
return ''
while '\n' in string:
string = string.replace('\n', ' ')
splits = clean_split(string)
string = ''
for split in splits:
string += split + ' '
string = string[:-1]
return string
def clean_split(string, delimiter=' '):
sub_strs = string.split(delimiter)
splits = []
for sub_str in sub_strs:
if sub_str is not '':
splits.append(sub_str)
return splits
def remove_blank_in_endpoint(string):
length = len(string)
first_index = 0
for i in range(length):
if is_blank(string[first_index]):
first_index += 1
else:
break
last_index = length - 1
for i in range(length):
if is_blank(string[last_index]):
last_index -= 1
else:
break
last_index += 1
return string[first_index:last_index]
def is_blank(ch):
blank_ch = [' ', '\t', '\n']
if ch in blank_ch:
return True
else:
return False
def dict_to_arrtibute_string(attributes):
string = ''
for key in attributes:
string += key + '=\"{0}\";'.format(str(attributes[key]))
return string
def attribute_string_to_dict(attrs):
attr_dict = {}
for attr in attrs:
attr_dict[attr[0]] = attr[1]
return attr_dict
def save_python_object(obj, save_path):
with open(save_path, 'wb') as file:
pickle.dump(obj, file)
def load_python_object(path):
with open(path, 'rb') as file:
return pickle.load(file)
def delete_n(string):
while '\n' in string:
string = string.replace('\n', ' ')
return string
def remove_additional_blank(string):
words = string.split(' ')
string = ''
for word in words:
if word is not '':
string += word + ' '
return string[:-1]
def formal_text(text):
text = delete_n(text)
text = remove_additional_blank(text)
return text
def float2str(f, precision=2):
f = str(f)
f_base = f[:f.find('.') + precision]
return f_base
# ========== time realted operation ========== #
def str_day():
day = time.strftime("%Y-%m-%d", time.localtime())
return day
def time2str(t):
localtime = time.localtime(int(t))
return str_time(localtime)
def str_time(local_time = None):
if local_time is None:
local_time = time.localtime()
day = time.strftime("%Y-%m-%d-%Hh-%Mm-%Ss)", local_time)
return day
if __name__ == '__main__':
print(str_day())