You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
9.9 KiB
247 lines
9.9 KiB
#!/usr/bin/env python3 |
|
# MIT License |
|
# |
|
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com> |
|
# |
|
# 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. |
|
|
|
import re |
|
import json |
|
import argparse |
|
import inspect |
|
from typing import List, Optional, Dict, Tuple, Any |
|
|
|
def read_file_to_lines(file_path: str) -> List[str]: |
|
""" |
|
Reads a file and returns a list of strings, one for each line. |
|
|
|
Args: |
|
file_path (str): The path to the file. |
|
|
|
Returns: |
|
list: A list of strings, where each string is a line from the file. |
|
Returns None if the file cannot be opened. |
|
""" |
|
with open(file_path, 'r') as f: |
|
lines: List[str] = f.readlines() |
|
return lines |
|
raise Exception() |
|
|
|
def write_lines_to_file(lines: List[str], output_path: str) -> bool: |
|
""" |
|
Writes a list of strings to a file, one string per line. |
|
|
|
Args: |
|
lines (list): The list of strings to write. |
|
output_path (str): The path to the output file. |
|
|
|
Returns: |
|
bool: True if the write was successful, False otherwise. |
|
""" |
|
try: |
|
with open(output_path, 'w') as f: |
|
for line in lines: |
|
f.write(line) |
|
return True |
|
except Exception as e: |
|
print(f"Error: An error occurred while writing to the file: {e}") |
|
return False |
|
|
|
def _index_of(_m:str, lines:List[str]) -> int: |
|
for i,l in enumerate(lines): |
|
if _m in l: |
|
return i |
|
raise ValueError(f"End Delimiter '{_m}' not found in the filtered lines") |
|
|
|
marker_start = "#--FORWARD-AUTOGEN-START--#" |
|
marker_end = "#--FORWARD-AUTOGEN-END--#" |
|
|
|
from TermTk.TTkAbstract.abstractscrollarea import _ForwardData |
|
def autogen_methods(data: _ForwardData) -> List[str]: |
|
""" |
|
Generates a list of method signatures and return types for a given class. |
|
|
|
Args: |
|
data (Dict[str, Any]): A dictionary containing the class name and a list of methods. |
|
Example: {'class': 'TTkTexteditView', 'methods': ['clear', 'setText', ...]} |
|
|
|
Returns: |
|
List[str]: A list of strings, where each string is a method signature and return type. |
|
""" |
|
import TermTk as ttk |
|
|
|
class_name = data.forwardClass.__name__ |
|
signatures: List[str] = [] |
|
|
|
for method_name in data.methods: |
|
try: |
|
# Get the method from the class |
|
method = getattr(data.forwardClass, method_name) |
|
sig = inspect.signature(method) |
|
doc = inspect.getdoc(method) |
|
return_type = sig.return_annotation |
|
params = ', '.join([f"{_p}={_p}" for _p in sig.parameters.keys() if _p != 'self']) |
|
|
|
source = inspect.getsource(method) |
|
# Extract the first line, which should be the method definition |
|
lines = source.splitlines() |
|
index_func = _index_of(' def ',lines) |
|
lines = lines[:index_func+1] |
|
doc_indent = " " |
|
lines.extend([ |
|
doc_indent + f"'''", |
|
doc_indent + f".. seealso:: this method is forwarded to :py:meth:`{class_name}.{method_name}`\n", |
|
]) |
|
if doc: |
|
lines.extend([f"{doc_indent}{_l}" if _l else '' for _l in doc.split('\n')]) |
|
lines.append(doc_indent + "'''") |
|
# Format the signature string |
|
signatures.extend([ |
|
*[f"{_l}\n" for _l in lines], |
|
# f" def {method_name}{sig}:", |
|
]) |
|
if '=kwargs' in params and '=args' in params: |
|
signatures.append(f" return {data.instance}.{method_name}(*args, **kwargs)\n") |
|
elif '=kwargs' in params: |
|
signatures.append(f" return {data.instance}.{method_name}(**kwargs)\n") |
|
elif '=args' in params: |
|
signatures.append(f" return {data.instance}.{method_name}(*args)\n") |
|
else: |
|
signatures.append(f" return {data.instance}.{method_name}({params})\n") |
|
|
|
except AttributeError: |
|
print(f"Error: Method '{method_name}' not found in class '{class_name}'.") |
|
except Exception as e: |
|
print(f"Error: An error occurred while processing method '{method_name}': {e}") |
|
|
|
return signatures |
|
|
|
def autogen_signals(data: _ForwardData) -> List[str]: |
|
""" |
|
Generates a list of signals signatures and return types for a given class. |
|
|
|
Args: |
|
data (Dict[str, Any]): A dictionary containing the class name and a list of methods. |
|
Example: {'class': 'TTkTexteditView', 'methods': ['clear', 'setText', ...]} |
|
|
|
Returns: |
|
List[str]: A list of strings, where each string is a signal signature and return type. |
|
""" |
|
import TermTk as ttk |
|
|
|
class_name = data.forwardClass.__name__ |
|
signatures: List[str] = [] |
|
|
|
for signal_name in data.signals: |
|
try: |
|
# Get the method from the class |
|
signal = getattr(data.forwardClass, signal_name) |
|
# sig = inspect.signature(signal) |
|
doc = inspect.getdoc(signal) |
|
# return_type = sig.return_annotation |
|
# params = ', '.join([f"{_p}={_p}" for _p in sig.parameters.keys() if _p != 'self']) |
|
|
|
# source = inspect.getsource(signal) |
|
# Extract the first line, which should be the method definition |
|
# lines = source.splitlines() |
|
# index_func = _index_of(' def ',lines) |
|
# lines = lines[:index_func+1] |
|
doc_lines:List[str] = [] |
|
doc_indent = " " |
|
doc_lines.extend([ |
|
doc_indent + f"'''", |
|
doc_indent + f".. seealso:: this method is forwarded to :py:meth:`{class_name}.{signal_name}`\n", |
|
]) |
|
if doc: |
|
doc_lines.extend([doc_indent + _l for _l in doc.split('\n')]) |
|
doc_lines.append(doc_indent + "'''") |
|
# Format the signature string |
|
signatures.extend([ |
|
" @property\n", |
|
f" def {signal_name}(self) -> pyTTkSignal:\n", |
|
*[f"{_l}\n" for _l in doc_lines], |
|
f" return {data.instance}.{signal_name}\n" |
|
]) |
|
|
|
except AttributeError: |
|
print(f"Error: Method '{signal_name}' not found in class '{class_name}'.") |
|
except Exception as e: |
|
print(f"Error: An error occurred while processing method '{signal_name}': {e}") |
|
|
|
return signatures |
|
|
|
def get_classes_with_source_from_module(module) -> List[Dict[str, Any]]: |
|
classes_with_source: List[Dict[str, Any]] = [] |
|
|
|
for name, obj in inspect.getmembers(module): |
|
if inspect.isclass(obj) and hasattr(obj,'_ttk_forward'): |
|
try: |
|
source = inspect.getsource(obj) |
|
filename = inspect.getfile(obj) |
|
classes_with_source.append({ |
|
'class': obj, |
|
'name': name, |
|
'forward': obj._ttk_forward, |
|
'module': module.__name__, |
|
'filename': filename, |
|
'source': source |
|
}) |
|
except OSError as e: |
|
print(f"Could not get source for class {name}: {e}") |
|
except Exception as e: |
|
print(f"Unexpected error getting source for class {name}: {e}") |
|
|
|
return classes_with_source |
|
|
|
|
|
if __name__ == "__main__": |
|
parser = argparse.ArgumentParser(description="Read a file and write its lines to another file.") |
|
parser.add_argument('--apply', action='store_true', help='Apply the changes') |
|
parser.add_argument('--clear', action='store_true', help='Remove the changes') |
|
args = parser.parse_args() |
|
|
|
import TermTk as ttk |
|
classes = get_classes_with_source_from_module(ttk) |
|
args = parser.parse_args() |
|
if classes: |
|
for class_data in classes: |
|
print(f"Class Name: {class_data['name']}") |
|
print(f" Class: {class_data['class']}") |
|
print(f" Forward: {class_data['forward']}") |
|
print(f" Module: {class_data['module']}") |
|
print(f" Filename: {class_data['filename']}") |
|
# print(f" Source:\n{class_data['source']}") |
|
autogenenerated = autogen_signals(class_data['forward']) |
|
autogenenerated += autogen_methods(class_data['forward']) |
|
if args.apply: |
|
lines = read_file_to_lines(class_data['filename']) |
|
index_start = _index_of(marker_start,lines) |
|
index_end = _index_of(marker_end,lines) |
|
lines[index_start+1:index_end] = autogenenerated |
|
write_lines_to_file(lines,class_data['filename']) |
|
elif args.clear: |
|
lines = read_file_to_lines(class_data['filename']) |
|
index_start = _index_of(marker_start,lines) |
|
index_end = _index_of(marker_end,lines) |
|
lines[index_start+1:index_end] = '' |
|
write_lines_to_file(lines,class_data['filename']) |
|
else: |
|
print(''.join(autogenenerated)) |
|
else: |
|
print("No classes found in the module.") |