2025-07-01
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
'''
|
||||
Copyright (C) 2023 CG Cookie
|
||||
http://cgcookie.com
|
||||
hello@cgcookie.com
|
||||
|
||||
Created by Jonathan Denning, Jonathan Williamson, Patrick Moore
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
__all__ = ['deepdebug']
|
||||
@@ -0,0 +1,127 @@
|
||||
'''
|
||||
Copyright (C) 2023 CG Cookie
|
||||
http://cgcookie.com
|
||||
hello@cgcookie.com
|
||||
|
||||
Created by Jonathan Denning
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
from pathlib import Path
|
||||
|
||||
from . import term_printer
|
||||
|
||||
|
||||
# DEEP DEBUGGING: if debug.txt file exists in addon root folder, redirect **ALL** stdout and stderr to that file!
|
||||
class DeepDebug:
|
||||
_fn_debug = 'debug.txt'
|
||||
_path_debug = None
|
||||
_path_debug_backup = None
|
||||
_needs_restart = False
|
||||
|
||||
@staticmethod
|
||||
def can_be_enabled():
|
||||
# we were having problems with windows, but we might have a better approach.
|
||||
# see init below.
|
||||
# if platform.system() == 'Windows': return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def path_debug():
|
||||
if DeepDebug._path_debug is None:
|
||||
DeepDebug._path_debug = Path(__file__).parent.parent.parent / DeepDebug._fn_debug
|
||||
return DeepDebug._path_debug
|
||||
|
||||
@staticmethod
|
||||
def path_debug_backup():
|
||||
if DeepDebug._path_debug_backup is None:
|
||||
path_debug = DeepDebug.path_debug()
|
||||
DeepDebug._path_debug_backup = path_debug.parent / f'{path_debug.name}.bkp'
|
||||
return DeepDebug._path_debug_backup
|
||||
|
||||
@staticmethod
|
||||
def is_enabled():
|
||||
return DeepDebug.path_debug().exists()
|
||||
|
||||
@staticmethod
|
||||
def has_been_debugged():
|
||||
path_debug = DeepDebug.path_debug()
|
||||
if not path_debug.exists(): return False
|
||||
with path_debug.open() as f:
|
||||
return len(f.read()) != 0
|
||||
|
||||
@staticmethod
|
||||
def needs_restart():
|
||||
return DeepDebug._needs_restart
|
||||
|
||||
@staticmethod
|
||||
def enable():
|
||||
if DeepDebug.is_enabled(): return
|
||||
DeepDebug.path_debug().touch()
|
||||
DeepDebug._needs_restart = True
|
||||
|
||||
@staticmethod
|
||||
def disable():
|
||||
if not DeepDebug.is_enabled(): return
|
||||
DeepDebug.path_debug().unlink()
|
||||
DeepDebug._needs_restart = True
|
||||
|
||||
@staticmethod
|
||||
def init(*, fn_debug=None, clear=True, enable_only_once=True):
|
||||
if DeepDebug._path_debug:
|
||||
print(f'Addon Common: DeepDebug should be initialized only once')
|
||||
return
|
||||
|
||||
if fn_debug:
|
||||
DeepDebug._fn_debug = fn_debug
|
||||
|
||||
# assuming this file is two folders under the addon root folder
|
||||
if not DeepDebug.is_enabled(): return
|
||||
|
||||
path_debug = DeepDebug.path_debug()
|
||||
path_backup = DeepDebug.path_debug_backup()
|
||||
|
||||
# disable deep debugging if it should be run only once and it has already been run
|
||||
if enable_only_once and DeepDebug.has_been_debugged():
|
||||
if path_backup.exists(): path_backup.unlink()
|
||||
path_debug.rename(path_backup)
|
||||
return
|
||||
|
||||
term_printer.boxed(
|
||||
f'Redirecting ALL STDOUT and STDERR',
|
||||
f'path: {path_debug}',
|
||||
title='Deep Debugging', margin=' ', sides='double', color='black', highlight='blue',
|
||||
)
|
||||
sys.stdout.flush()
|
||||
if clear: path_debug.unlink() # delete it to reset session recording
|
||||
|
||||
# WARNING: closing STDOUT does _NOT_ work on Windows!
|
||||
# on windows, using a different approach, but it does not capture everything :(
|
||||
if platform.system() != 'Windows':
|
||||
# https://stackoverflow.com/questions/4675728/redirect-stdout-to-a-file-in-python/11632982#11632982
|
||||
# in C++, see https://stackoverflow.com/a/13888242 and https://cplusplus.com/reference/cstdio/freopen/
|
||||
os.close(1) ; os.open(path_debug, os.O_WRONLY | os.O_CREAT)
|
||||
else:
|
||||
# https://github.com/ipython/ipython/issues/10847
|
||||
sys.stdout = open(path_debug, 'wt', buffering=1)
|
||||
|
||||
@staticmethod
|
||||
def read():
|
||||
if not DeepDebug.is_enabled(): return None
|
||||
return DeepDebug.path_debug().read_text()
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
'''
|
||||
Copyright (C) 2023 CG Cookie
|
||||
http://cgcookie.com
|
||||
hello@cgcookie.com
|
||||
|
||||
Created by Jonathan Denning
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
'''
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any, Iterable
|
||||
from contextlib import contextmanager
|
||||
from ..ext import termcolor
|
||||
import random
|
||||
import time
|
||||
|
||||
def colored(
|
||||
text: str,
|
||||
color: str | None = None,
|
||||
*,
|
||||
highlight: str | None = None,
|
||||
attributes: Iterable[str] | None = None,
|
||||
no_color: bool | None = None,
|
||||
force_color: bool | None = None,
|
||||
) -> str:
|
||||
return termcolor.colored(
|
||||
text,
|
||||
color=color,
|
||||
on_color=f'on_{highlight}' if highlight else None,
|
||||
attrs=attributes,
|
||||
no_color=no_color,
|
||||
force_color=force_color,
|
||||
)
|
||||
|
||||
def cprint(
|
||||
text: str,
|
||||
*,
|
||||
color: str | None = None,
|
||||
highlight: str | None = None,
|
||||
attributes: Iterable[str] | None = None,
|
||||
no_color: bool | None = None,
|
||||
force_color: bool | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
print(
|
||||
colored(
|
||||
text,
|
||||
color=color,
|
||||
highlight=highlight,
|
||||
attributes=attributes,
|
||||
no_color=no_color,
|
||||
force_color=force_color,
|
||||
),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def boxed(*lines, title=None, prefix='', margin='', pad=' ', sides='single', color=None, highlight=None, attributes=None, wrap=120, indent=4):
|
||||
lines = [line for lines_ in lines for line in lines_.splitlines()]
|
||||
# https://www.w3.org/TR/xml-entity-names/025.html
|
||||
tl,tm,tr,lm,rm,bl,bm,br,lt,rt = {
|
||||
'single': '┌─┐││└─┘┤├',
|
||||
'double': '╔═╗║║╚═╝╡╞',
|
||||
}[sides]
|
||||
if title:
|
||||
title = f'{tm}{lt} {title} {rt}{tm}'
|
||||
title_width = len(title)
|
||||
else:
|
||||
title_width = 0
|
||||
pad_width = len(pad) * 2
|
||||
width = max(max(len(line) for line in lines), title_width)
|
||||
if wrap and width > wrap:
|
||||
width = wrap
|
||||
wrapped_lines = []
|
||||
for line in lines:
|
||||
cur_indent = len(line) - len(line.lstrip()) + indent
|
||||
first = True
|
||||
while True:
|
||||
if first: first = False
|
||||
else: line = (' '*cur_indent) + line
|
||||
wrapped_lines.append(line[:wrap])
|
||||
line = line[wrap:]
|
||||
if not line: break
|
||||
lines = wrapped_lines
|
||||
if prefix: print(prefix, end='')
|
||||
if title:
|
||||
cprint(f'{margin}{tl}{title}{tm*(width+pad_width-len(title))}{tr}{margin}', color=color, highlight=highlight, attributes=attributes)
|
||||
else:
|
||||
cprint(f'{margin}{tl}{tm*(width+pad_width)}{tr}{margin}', color=color, highlight=highlight, attributes=attributes)
|
||||
for line in lines:
|
||||
if prefix: print(prefix, end='')
|
||||
cprint(f'{margin}{lm}{pad}{line}{" "*(width - len(line))}{pad}{rm}{margin}', color=color, highlight=highlight, attributes=attributes)
|
||||
if prefix: print(prefix, end='')
|
||||
cprint(f'{margin}{bl}{bm*(width+pad_width)}{br}{margin}', color=color, highlight=highlight, attributes=attributes)
|
||||
|
||||
sprint_data = {
|
||||
'width': 5,
|
||||
'index': -1,
|
||||
'time': None,
|
||||
}
|
||||
def sprint(*args):
|
||||
global sprint_data
|
||||
sprint_data['index'] = (sprint_data['index']+1) % sprint_data['width']
|
||||
d = time.time() - sprint_data['time'] if sprint_data['time'] else 0
|
||||
sprint_data['time'] = time.time()
|
||||
m = ' '*sprint_data['index'] + '*' + ' '*(sprint_data['width']-sprint_data['index']-1)
|
||||
s = " ".join(f'{arg}' for arg in args)
|
||||
print(f'[{m}] {d:0.4f}s | {s}')
|
||||
Reference in New Issue
Block a user