import time import subprocess,re,datetime,time,os,platform,json,PySimpleGUI as sg; from subprocess import Popen; from make_real_readme import main # mkdir import os cd = CD = os.path.dirname(os.path.abspath(__file__)) dir_name = os.path.join(cd, 'output') if not os.path.exists(dir_name): os.mkdir(dir_name) else: print(f'Такая папка уже есть: "{dir_name}"') sg.theme('Dark2') cd = os.path.dirname(os.path.abspath(__file__)) def readfile(filename): with open(filename, 'r', encoding='utf-8') as ff: return ff.read() def writefile(fpath, content): with open(fpath, 'w', encoding='utf-8') as ff: ff.write(content) def writejson(a_path:str, a_dict:dict) -> None: with open(a_path, 'w', encoding='utf-8') as output_file: json.dump(a_dict, output_file, ensure_ascii=False, indent=2) def readjson(a_path:str) -> dict: with open(a_path, 'r', encoding='utf-8') as f: return json.load(f) def openfile(a_path): # File exists? if not os.path.exists(a_path): return sg.Popup(f"Error! This file doesn't exists: {a_path}") # check: OS if 'Windows' in platform.system(): os.startfile(a_path) elif 'Linux' in platform.system(): Popen(f'exo-open "{a_path}"', shell=True) def opendir(a_path): # Folder exists? if not os.path.exists(a_path): return sg.Popup(f"Error! This directory doesn't exists: {a_path}") try: # check: OS if 'Windows' in platform.system(): os.startfile(a_path) elif 'Linux' in platform.system(): Popen(f'exo-open --launch FileManager --working-directory "{a_path}"', shell=True) except Exception as e: sg.Popen(f"Error, can't open a file: '{e}'") ######################################################################## # __ _ _ # # / _(_) | | # # __ __ ___ ___ _ __ | |_ _ __ _ | |__ ___ _ __ ___ # # \ \ / / / __/ _ \| '_ \| _| |/ _` | | '_ \ / _ \ '__/ _ \ # # \ V / | (_| (_) | | | | | | | (_| | | | | | __/ | | __/ # # \_/ \___\___/|_| |_|_| |_|\__, | |_| |_|\___|_| \___| # # __/ | # # |___/ # ######################################################################## def load_configs(): return readjson(os.path.join(cd, 'app_configs.json')) def save_configs(a_config:dict): writejson(os.path.join(cd, 'app_configs.json'), a_config) APP_CONFIGS = load_configs() README_OFILENAME = APP_CONFIGS['README_OFILE'] CALL_REFERENCE_OFILENAME = APP_CONFIGS['CALL_REF_OFILE'] ##-#-#-# ##-#-#-# # Post-process logic ##-#-#-# ##-#-#-# insert_md_section_for__class_methods = False remove_repeated_sections_classmethods = False import time def timeit(f): def wrapper(*args, **kwargs): start = time.time() res = f(*args, **kwargs) end = time.time() # print('\nНачало в : ', start) # print('\n ({}) Начало в : '.format(f.__name__, start)) # print('Окончено в : ', end) # print('Длительность: ', end - start) # print('') return res return wrapper class BESTLOG(object): def __init__(self, filename): # my_file = logging.FileHandler(filename, mode='w') # my_file.setLevel(logging.DEBUG) # my_file.setFormatter(logging.Formatter('%(asctime)s>%(levelname)s: %(message)s')) # logger = logging.getLogger(__name__) # logger.setLevel(logging.DEBUG) # logger.addHandler(my_file) self.filename = filename self.json_name = filename + '.json' self.error_list = [] self.warning_list = [] self.info_list = [] self.debug_list = [] self.tick_amount=1 self.names = self.messages_names = 'error warning info debug'.split(' ') def tick(self): self.tick_amount+=1 return self.tick_amount ####################################################################### # __ _ _ _ # # / _| | | (_) | # # | |_ ___ _ __ | |_ _ __ __ _ _ __ ___ _ __ _| | ___ _ __ # # | _/ _ \| '__| | __| '__/ _` | '_ \/ __| '_ \| | |/ _ \ '__| # # | || (_) | | | |_| | | (_| | | | \__ \ |_) | | | __/ | # # |_| \___/|_| \__|_| \__,_|_| |_|___/ .__/|_|_|\___|_| # # | | # # |_| # ####################################################################### def error(self, m, metadata={}): self.error_list.append([self.tick(), m, metadata]) def warning(self, m, metadata={}): self.warning_list.append([self.tick(), m, metadata]) def info(self, m, metadata={}): self.info_list.append([self.tick(), m, metadata]) def debug(self, m, metadata={}): self.debug_list.append([self.tick(), m, metadata]) ########################################## # __ # # / _| # # | |_ ___ _ __ _ __ ___ ___ # # | _/ _ \| '__| | '_ ` _ \ / _ \ # # | || (_) | | | | | | | | __/ # # |_| \___/|_| |_| |_| |_|\___| # # # # # ########################################## def tolist(self): return zip([self.error_list, self.warning_list, self.info_list, self.debug_list], self.names) def todict(self): return {'error' : self.error_list, 'warning' : self.warning_list, 'info' : self.info_list, 'debug' : self.debug_list} @timeit def save(self): ''' { 'message_type' : message_type, 'message_text' : m_text, 'message_time' : m_time, 'message_metadata' : m_metadata } ''' all_messages_list = [] for messages, message_type in self.tolist(): results_ = [{'message_type' : message_type, 'message_text' : m_text, 'message_time' : m_time, 'message_metadata' : m_metadata} for m_time, m_text, m_metadata in messages] all_messages_list.extend(results_) # sort messages on time all_messages_list = sorted(all_messages_list, key=lambda x: x['message_time']) # convert time # for i in all_messages_list: i['message_time'] = i['message_time'].strftime('%Y-%m-%d %H:%M:%S.%f') writejson(self.json_name, all_messages_list) @timeit def load(self, **kw): ''' return dict with messages kw = { use_psg_color : bool show_time : bool } ''' # plan: # read json, convert time # read all_messages_list = readjson(self.json_name) # convert time # for i in all_messages_list: i['message_time'] = datetime.datetime.strptime(i['message_time'], '%Y-%m-%d %H:%M:%S.%f') def format_message(message): if kw['show_time']: return str(message['message_time']) + ':' + message['message_text'] else: return message['message_text'] #=========# # 4 lists # #=========# error_list = [i for i in all_messages_list if i['message_type'] == 'error'] warning_list = [i for i in all_messages_list if i['message_type'] == 'warning'] info_list = [i for i in all_messages_list if i['message_type'] == 'info'] debug_list = [i for i in all_messages_list if i['message_type'] == 'debug'] #=================# # and 1 more list # #=================# # colors = {'warning' : 'magenta', 'info' : 'black'} colors = {'warning' : 'blue', 'info' : 'black'} warning_info_ = [] for message in sorted(warning_list + info_list, key=lambda x: x['message_time']): if kw['use_psg_color']: warning_info_.append([ format_message(message), colors.get(message['message_type']) ]) else: warning_info_.append(format_message(message)) error_list = [format_message(i) for i in error_list] warning_list = [format_message(i) for i in warning_list] info_list = [format_message(i) for i in info_list] debug_list = [format_message(i) for i in debug_list] return error_list, warning_list, info_list, debug_list, warning_info_ @timeit def load_to_listbox(self): ''' read .json ''' return sorted(readjson(self.json_name), key=lambda x: x['message_time']) @timeit def compile_call_ref(output_filename='LoG_call_ref', **kw): ''' Compile a "5_call_reference.md" file''' log_obj = BESTLOG(os.path.join(cd, output_filename)) main(logger=log_obj, main_md_file='markdown input files/5_call_reference.md', insert_md_section_for__class_methods=insert_md_section_for__class_methods, remove_repeated_sections_classmethods=remove_repeated_sections_classmethods, files_to_include=[], output_name=CALL_REFERENCE_OFILENAME, delete_html_comments=True) log_obj.save() return log_obj.load(**kw), log_obj.load_to_listbox() @timeit def compile_readme(output_filename='LoG', **kw): ''' Compile a "2_readme.md" file''' log_obj = BESTLOG(os.path.join(cd, output_filename)) main(logger=log_obj, insert_md_section_for__class_methods=insert_md_section_for__class_methods, remove_repeated_sections_classmethods=remove_repeated_sections_classmethods, files_to_include=[0, 1, 2, 3], output_name=README_OFILENAME, delete_html_comments=True) log_obj.save() return log_obj.load(**kw), log_obj.load_to_listbox() def compile_all_stuff(**kw): ''' Compile a "2_ and 5_" .md filess return output from them ''' return compile_readme(**kw), compile_call_ref(**kw) ######################################## # _____ # # | __ \ # # | |__) |__ _ __ _ _ _ __ # # | ___/ _ \| '_ \| | | | '_ \ # # | | | (_) | |_) | |_| | |_) | # # |_| \___/| .__/ \__,_| .__/ # # | | | | # # |_| |_| # ######################################## def md2psg(target_text): r''' ibcolor i italic b bold color = can be word can be color red #ff00111 green blue i?b?\s?\w+? usage *i*a** italic *b*a** bold *ib*a** italic bold *ib red*a** italic bold red *b green*a** bold green 'This was *I*special** message from *B*him**. And from *Igreen*this** to *Ired*this**' ''' # format # ====== font_norm = ('Mono 12 ') # (*sg.DEFAULT_FONT, 'italic') font_bold = ('Mono 12 italic') # (*sg.DEFAULT_FONT, 'italic') font_italic = ('Mono 12 bold') # (*sg.DEFAULT_FONT, 'bold') list_of_Ts = [] parts = [i for i in re.compile(r'(\*I?B?[a-z]*?\*[\d\D]*?\*\*)', flags=re.M|re.DOTALL).split(target_text) if i is not None] for index, text in enumerate(parts): if index % 2 == 0: # Normal text T_text = text T = sg.T(T_text, size=(len(T_text), 1), pad=(0,0), font=font_norm) else: # SPECIAL format T_parameters = { 'font': font_norm } my_format = text[1:].split('*')[0] # ::: italic if 'I' in my_format: T_parameters['font'] = font_italic # ::: bold if 'B' in my_format: T_parameters['font'] = font_bold # ::: colors color_left = my_format.replace('I', '').replace('B', '') if color_left: T_parameters['text_color'] = color_left # making psg element T_text = '*'.join(text.split('*')[2:-2]) T = sg.T(T_text, size=(len(T_text), 1), pad=(0,0), **T_parameters) list_of_Ts.append(T) return list_of_Ts def mini_GUI(): my_font = ("Helvetica", 12) my_font2 = ("Helvetica", 12, "bold") my_font3 = ("Helvetica", 15, "bold") my_font4 = ("Mono", 18, "bold") def make_tab(word): def tabs(*layouts): return sg.TabGroup( [[ sg.Tab(title, lay, key=f'-tab-{word_}-{index}-') for index, (title, word_, lay) in enumerate(layouts) ]] ) return [[ sg.Column(layout=[ [sg.T('debug', font=my_font, text_color='grey')], [sg.ML(size=(50-15, 15), key=f'-{word}-debug-')], [sg.T('error', font=my_font, text_color='red')], [sg.ML(size=(50-15, 15), key=f'-{word}-error-')], ], pad=(0, 0)), sg.T(' '), sg.Column(layout=[ [sg.T('warning', font=my_font2)], [sg.ML(size=(70-12, 15), key=f'-{word}-warning-')], [sg.T('info', font=my_font2)], [sg.ML(size=(70-12, 15), key=f'-{word}-info-')], ], pad=(0, 0)), tabs( ('Text', word, [ [sg.T('warning info', font=my_font3)] ,[sg.ML(size=(110, 30), key=f'-{word}-warning_info-')] ]), ('Listbox', word, [ [sg.T('warning info listbox', font=my_font3)] ,[sg.Listbox([], size=(110, 30-1), key=f'-{word}-listbox-', enable_events=True, background_color='#ffccaa')] ]) ) ]] settings_layout = [ [sg.CB('Toggle progressbar', False, enable_events=True, key='toggle_progressbar')], [ sg.Frame('Text editor', [[ sg.Combo(['pycharm', 'subl'], default_value='subl', enable_events=True, key='_text_editor_combo_') ]] ), sg.Frame('Pycharm path:', [[ sg.I('', size=(40, 1), enable_events=True, key='_PyCharm_path_') ]] ) ], [ sg.Frame('⅀∉ Filter "empty tables"', [ [sg.T('''This is for filtering stirng, like:''')], [sg.T('''Warning ======= We got empty md_table for "EasyPrintClose"''', font='Mono 8')], [sg.CB('enable', True, key='checkbox_enable_empty_tables_filter', enable_events=True)], [sg.ML('PrintClose\nEasyPrintClose\nmain\ntheme\nRead', size=(30,10), enable_events=True, key='_filter_empty_tables_ml_')]]), sg.Frame('⅀∉ Filter "tkinter class methods"', [ [sg.T('''This is for filtering stirng, like:''')], [sg.T('''Please, fix ':return:' in 'SetFocus' IF you want to see 'return' row in 'signature table' ''', font='Mono 8')], [sg.CB('enable', True, enable_events=True, key='checkbox_enable_filter_tkinter_class_methods')], [sg.ML('SetFocus\nSetTooltip\nUpdate\n__init__\nbind\nexpand\nset_cursor\nset_size', size=(30,10), enable_events=True, key='_filter_tkinter_class_methods_')]], visible=not True) ] ] layout = [[sg.TabGroup([[ sg.Tab('readme logs', make_tab('README')), sg.Tab('Call reference logs', make_tab('CALL_REF')), sg.Tab('General settings', settings_layout) ]])]] # ░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░ # ░▒▒▓▓▓ progress bar ▓▓▓▒▒░ # ░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░ from time import sleep; from math import pi, sin; from itertools import count def next_star(): middle = 100/2 for i in (int(sin(i*pi/middle)*middle + middle) for i in count()): yield i psg_module_path = str(sg).split("' from '")[1][:-2] star_bar = sg.Col([ [sg.ProgressBar(max_value=100, orientation='h', key='_star_bar1_', size=(50,5), bar_color=('blue', 'yellow'))], [sg.ProgressBar(max_value=100, orientation='h', key='_star_bar2_', size=(50,5), bar_color=('yellow', 'blue'))], ]) # guia def empty_line(fontsize=12): return [sg.T('', font=('Mono '+str(fontsize)))] window = sg.Window('We are live! Again! --- ' + 'Completed making {}, {}'.format(os.path.basename(README_OFILENAME), os.path.basename(CALL_REFERENCE_OFILENAME)), [ [sg.T(size=(30,1), key='-compile-time-'), star_bar], empty_line(), [*md2psg(f'The *Bmagenta*PySimpleGUI** module being processed is *Imagenta*"{psg_module_path}"**'), sg.B('< open (__init__.py)', key='open_init_file'), sg.B('< open (psg.py)', key='open_psg_file')], # [sg.T(f'The **PySimpleGUI** module being processed is *"{psg_module_path}"*')], empty_line(), [ sg.B('Run again (F1)', key='-run-') ,sg.Col([ [sg.CB('show time in logs (F2)', False, enable_events=True, key='show_time')], [sg.CB('Logs with Color (F3)', True, enable_events=True, key='use_psg_color')], ]) ,sg.Col([ empty_line(5), [sg.B('open "db folder"', key='-open_db_folder-')], ]) ,sg.Frame('', [[ sg.Col([ [*md2psg('markdown outputFileName *I*FOR** *B*readme **: ') ,sg.I(README_OFILENAME, key='README_OFILE', size=(25, 1)) ,sg.B('open in explorer', key='open in explorer_readme') ,sg.B('open in text editor', key='open file - readme') ] ,[*md2psg('markdown outputFileName *I*FOR** *B*call ref**: ') ,sg.I(CALL_REFERENCE_OFILENAME, key='CALL_REF_OFILE', size=(25, 1)) ,sg.B('open in explorer', key='open in explorer_calref') ,sg.B('open in text editor', key='open file - calref') ] ]) ]], relief=sg.RELIEF_SUNKEN, border_width=4) ] ,*layout ], resizable=True, finalize=True, location=(0,0), return_keyboard_events = True) def update_time_in_GUI(): window['-compile-time-'](datetime.datetime.today().strftime('%Y-%m-%d %H:%M:%S.%f')) def update_compilation_in_psg(values): # # ░▒▒▓▓▓▓▓◘ compile ◘▓▓▓▓▓▒▒░ # result_readme__for_txt_n_listbox, result_call_ref__for_txt_n_listbox = compile_all_stuff( use_psg_color=values['use_psg_color'], show_time=values['show_time']) result_readme_txt, result_readme_listbox_items = result_readme__for_txt_n_listbox result_call_ref_txt, result_call_ref_listbox_items = result_call_ref__for_txt_n_listbox # # ░▒▒▓▓▓▓▓◘ define FILTER functions ◘▓▓▓▓▓▒▒░ # badNames = [ i.strip() for i in values['_filter_tkinter_class_methods_'].split('\n') if i.strip()] badNames = '|'.join(badNames) regex_str1 = rf"fix .:return:. in .({badNames})." badNames = [ i for i in values['_filter_empty_tables_ml_'].split('\n') if i.strip()] badNames = '|'.join(badNames) regex_str2 = rf'empty md_table for .({badNames}).' def is_valid_regex_LogMessage(msg: str): nonlocal regex_str1, regex_str2 # test 1 - filter tkinter class methods error1_found = False if values['checkbox_enable_filter_tkinter_class_methods'] and ':return:' in msg: error1_found = bool(re.search(regex_str1, msg, flags=re.M|re.DOTALL)) # test 2 - filter "special empty tables" error2_found = False if values['checkbox_enable_empty_tables_filter'] and 'empty md_table for' in msg: error2_found = bool(re.search(regex_str2, msg, flags=re.M|re.DOTALL)) return not error1_found and not error2_found def filter_log_messages(messages): if type(messages) is str: return '\n'.join([msg for msg in messages.split('\n') if is_valid_regex_LogMessage(msg)]) raise TypeError # # ▓▓▓ Update GUI ▓▓▓ # # =========== listbox's class ParsingError(object): def __init__(self, log_obj): self.log_obj = log_obj self.text = log_obj['message_text'] def __str__(self): return self.__repr__() def __repr__(self): '''qwe''' # { # 'message_type': 'info', # 'message_text': 'STARTING', # 'message_time': 2, # 'message_metadata': {} # } text = self.log_obj['message_text'] metadata = self.log_obj['message_metadata'] lineno = '' if 'lineno' in metadata.keys(): lineno = "(line:" + str(metadata['lineno']) + ') ' return f'{lineno} {text}' items1 = [i for i in result_readme_listbox_items if is_valid_regex_LogMessage(i['message_text']) ] items2 = [i for i in result_call_ref_listbox_items if is_valid_regex_LogMessage(i['message_text']) ] window['-README-listbox-']([ ParsingError(i) for i in items1]) window['-CALL_REF-listbox-']([ ParsingError(i) for i in items2]) # =========== multitext's def set_it(prefix = 'CALL_REF', messages_obj = result_call_ref_txt): t_error, t_warning, t_info, t_debug = ['\n'.join(i) for i in messages_obj[:4]] t_error = filter_log_messages(t_error) t_warning = filter_log_messages(t_warning) t_info = filter_log_messages(t_info) t_debug = filter_log_messages(t_debug) window[f'-{prefix}-error-'](t_error) window[f'-{prefix}-warning-'](t_warning) window[f'-{prefix}-info-'](t_info) window[f'-{prefix}-debug-'](t_debug) # /// colors warning_info window[f'-{prefix}-warning_info-'].update('') t_warning_info_obj = messages_obj[-1] if values['use_psg_color']: for text, color in t_warning_info_obj: if not is_valid_regex_LogMessage(text): continue window[f'-{prefix}-warning_info-'].print(text, text_color=color) else: window[f'-{prefix}-warning_info-'](t_warning_info_obj) # two calls set_it('README', result_readme_txt) set_it('CALL_REF', result_call_ref_txt) # ~~~~~~~~~~~~ # GUI updating # ~~~~~~~~~~~~ update_time_in_GUI() values = window.read(timeout=0)[1] update_compilation_in_psg(values) p_values = values window['_PyCharm_path_'](APP_CONFIGS['_PyCharm_path_']) window['_text_editor_combo_'].update(set_to_index=APP_CONFIGS['_text_editor_combo_']) # index window['toggle_progressbar'](APP_CONFIGS['toggle_progressbar']) window['checkbox_enable_empty_tables_filter'](APP_CONFIGS['checkbox_enable_empty_tables_filter']) window['_filter_empty_tables_ml_'](APP_CONFIGS['_filter_empty_tables_ml_']) window['checkbox_enable_filter_tkinter_class_methods'](APP_CONFIGS['checkbox_enable_filter_tkinter_class_methods']) window['_filter_tkinter_class_methods_'](APP_CONFIGS['_filter_tkinter_class_methods_']) window['show_time'](APP_CONFIGS['show_time']) window['use_psg_color'](APP_CONFIGS['use_psg_color']) window['README_OFILE'](APP_CONFIGS['README_OFILE']) window['CALL_REF_OFILE'](APP_CONFIGS['CALL_REF_OFILE']) next_val_gen = next_star() my_timeout = None while True: event, values = window(timeout=my_timeout) if event in ('Exit', None): # save to disk # APP_CONFIGS['_PyCharm_path_'] = p_values['_PyCharm_path_'] APP_CONFIGS['_text_editor_combo_'] = 1 if window['_text_editor_combo_'].get() == 'subl' else 0 APP_CONFIGS['toggle_progressbar'] = p_values['toggle_progressbar'] APP_CONFIGS['checkbox_enable_empty_tables_filter'] = p_values['checkbox_enable_empty_tables_filter'] APP_CONFIGS['_filter_empty_tables_ml_'] = p_values['_filter_empty_tables_ml_'] APP_CONFIGS['checkbox_enable_filter_tkinter_class_methods'] = p_values['checkbox_enable_filter_tkinter_class_methods'] APP_CONFIGS['_filter_tkinter_class_methods_'] = p_values['_filter_tkinter_class_methods_'] APP_CONFIGS['show_time'] = p_values['show_time'] APP_CONFIGS['use_psg_color'] = p_values['use_psg_color'] APP_CONFIGS['README_OFILE'] = p_values['README_OFILE'] APP_CONFIGS['CALL_REF_OFILE'] = p_values['CALL_REF_OFILE'] save_configs(APP_CONFIGS) break p_values = values if '__TIMEOUT__' in event: if values['toggle_progressbar']: window['_star_bar1_'].UpdateBar(next(next_val_gen)) window['_star_bar2_'].UpdateBar(next(next_val_gen)) if '__TIMEOUT__' not in event: print('PSG event>', event) if event == 'toggle_progressbar': my_timeout = None if not values['toggle_progressbar'] else 100 if event == '-README-listbox-': metadata = values['-README-listbox-'][0].log_obj['message_metadata'] print(f'metadata = {metadata}') if event == '-CALL_REF-listbox-': ParsingError_obj = values['-CALL_REF-listbox-'][0] metadata = ParsingError_obj.log_obj['message_metadata'] if 'lineno' in metadata.keys(): lineno = metadata['lineno'] texteditor = values['_text_editor_combo_'] psg_module_path_SDK = psg_module_path.replace('__init__.py', 'PySimpleGUI.py') if 'pycharm' == texteditor: texteditor = values['_PyCharm_path_'] subprocess.Popen(f'"{texteditor}" --line {lineno} "{psg_module_path_SDK}"', shell=True) elif 'subl' == texteditor: subprocess.Popen(f'{texteditor} "{psg_module_path_SDK}:{lineno}"', shell=True) # if event == '-CALL_REF-listbox-': # res = values['-CALL_REF-listbox-'][0] # print(f'res = {res}') if event == '-run-' or 'F1' in event: update_compilation_in_psg(values) # folder if event == '-open_db_folder-': opendir(cd) # folder if event == 'open in explorer_readme': opendir(os.path.dirname(os.path.join(cd, values['README_OFILE']))) if event == 'open in explorer_calref': opendir(os.path.dirname(os.path.join(cd, values['CALL_REF_OFILE']))) # file if event == 'open file - readme': openfile(os.path.join(cd, values['README_OFILE'])) if event == 'open file - calref': openfile(os.path.join(cd, values['CALL_REF_OFILE'])) # file if event == 'open_init_file': openfile(psg_module_path) if event == 'open_psg_file': openfile(psg_module_path.replace('__init__.py', 'PySimpleGUI.py')) # hotkeys if 'F2' in event: window['show_time'](not values['show_time']) if 'F3' in event: window['use_psg_color'](not values['use_psg_color']) window.close() if __name__ == '__main__': mini_GUI()