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

#!/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.")