mirror of
https://github.com/weechat/weechat.git
synced 2026-06-12 14:14:48 +02:00
ci: switch from bandit/flake8/pylint to ruff in CI for Python scripts
This commit is contained in:
@@ -18,7 +18,6 @@ env:
|
||||
curl
|
||||
devscripts
|
||||
equivs
|
||||
flake8
|
||||
gem2deb
|
||||
guile-3.0-dev
|
||||
lcov
|
||||
@@ -40,8 +39,6 @@ env:
|
||||
php-dev
|
||||
pipx
|
||||
pkgconf
|
||||
pylint
|
||||
python3-bandit
|
||||
python3-dev
|
||||
ruby-pygments.rb
|
||||
shellcheck
|
||||
@@ -158,7 +155,7 @@ jobs:
|
||||
sudo apt-get --yes --no-install-recommends install ${{ env.WEECHAT_DEPS_UBUNTU }}
|
||||
# uninstall php imagick as is causes a crash when loading php plugin (see #2009)
|
||||
sudo apt-get --yes purge php8.3-imagick
|
||||
pipx install msgcheck schemathesis
|
||||
pipx install msgcheck ruff schemathesis
|
||||
|
||||
- name: Check gettext files
|
||||
run: msgcheck po/*.po
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-FileCopyrightText: 2025 Sébastien Helleu <flashcode@flashtux.org>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
line-length = 120
|
||||
|
||||
[lint]
|
||||
select = ["ALL"]
|
||||
ignore = [
|
||||
"D203", # incorrect-blank-line-before-class
|
||||
"D213", # multi-line-summary-second-line
|
||||
]
|
||||
|
||||
[lint.pylint]
|
||||
max-args = 10
|
||||
@@ -7,6 +7,8 @@
|
||||
# DO NOT EDIT BY HAND!
|
||||
#
|
||||
|
||||
# ruff: noqa: A002,D400,D205,D301,D415,E501,PIE790,PYI021,PYI048,UP006,UP007,UP035
|
||||
|
||||
from typing import Dict, Union
|
||||
|
||||
WEECHAT_RC_OK: int = 0
|
||||
@@ -55,8 +57,7 @@ WEECHAT_HOOK_SIGNAL_POINTER: str = "pointer"
|
||||
|
||||
|
||||
def register(name: str, author: str, version: str, license: str, description: str, shutdown_function: str, charset: str) -> int:
|
||||
"""`register in WeeChat plugin API reference <https://weechat.org/doc/weechat/api/#_register>`_
|
||||
"""
|
||||
"""`register in WeeChat plugin API reference <https://weechat.org/doc/weechat/api/#_register>`_"""
|
||||
...
|
||||
|
||||
|
||||
@@ -1981,8 +1982,7 @@ def window_get_integer(window: str, property: str) -> int:
|
||||
|
||||
|
||||
def window_get_string(window: str, property: str) -> str:
|
||||
"""`window_get_string in WeeChat plugin API reference <https://weechat.org/doc/weechat/api/#_window_get_string>`_
|
||||
"""
|
||||
"""`window_get_string in WeeChat plugin API reference <https://weechat.org/doc/weechat/api/#_window_get_string>`_"""
|
||||
...
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,9 +18,9 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Scripts generator for WeeChat: build source of scripts in all languages to
|
||||
test the scripting API.
|
||||
"""Test script generator for WeeChat.
|
||||
|
||||
Build source of scripts in all languages to test the scripting API.
|
||||
|
||||
This script can be run in WeeChat or as a standalone script
|
||||
(during automatic tests, it is loaded as a WeeChat script).
|
||||
@@ -30,36 +30,33 @@ It uses the following scripts:
|
||||
- testapi.py: the WeeChat scripting API tests
|
||||
"""
|
||||
|
||||
# pylint: disable=wrong-import-order,wrong-import-position
|
||||
# pylint: disable=useless-object-inheritance
|
||||
# pylint: disable=consider-using-f-string
|
||||
# pylint: disable=super-with-arguments
|
||||
# pylint: disable=consider-using-with
|
||||
# pylint: disable=unspecified-encoding
|
||||
# ruff: noqa: T201,UP006,UP007,UP035
|
||||
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import ast
|
||||
from datetime import datetime
|
||||
import datetime
|
||||
import inspect
|
||||
from io import StringIO
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Tuple, Type, Union
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(SCRIPT_DIR)
|
||||
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||
sys.path.append(str(SCRIPT_DIR))
|
||||
from unparse import ( # noqa: E402
|
||||
UnparsePython,
|
||||
UnparsePerl,
|
||||
UnparseRuby,
|
||||
UnparseLua,
|
||||
UnparseTcl,
|
||||
Unparse,
|
||||
UnparseGuile,
|
||||
UnparseJavaScript,
|
||||
UnparseLua,
|
||||
UnparsePerl,
|
||||
UnparsePhp,
|
||||
UnparsePython,
|
||||
UnparseRuby,
|
||||
UnparseTcl,
|
||||
)
|
||||
|
||||
RUNNING_IN_WEECHAT = True
|
||||
@@ -69,315 +66,328 @@ except ImportError:
|
||||
RUNNING_IN_WEECHAT = False
|
||||
|
||||
|
||||
SCRIPT_NAME = 'testapigen'
|
||||
SCRIPT_AUTHOR = 'Sébastien Helleu <flashcode@flashtux.org>'
|
||||
SCRIPT_VERSION = '0.1'
|
||||
SCRIPT_LICENSE = 'GPL3'
|
||||
SCRIPT_DESC = 'Generate scripting API test scripts'
|
||||
SCRIPT_NAME = "testapigen"
|
||||
SCRIPT_AUTHOR = "Sébastien Helleu <flashcode@flashtux.org>"
|
||||
SCRIPT_VERSION = "0.1"
|
||||
SCRIPT_LICENSE = "GPL3"
|
||||
SCRIPT_DESC = "Generate scripting API test scripts"
|
||||
|
||||
SCRIPT_COMMAND = 'testapigen'
|
||||
SCRIPT_COMMAND = "testapigen"
|
||||
|
||||
|
||||
class WeechatScript(object): # pylint: disable=too-many-instance-attributes
|
||||
"""
|
||||
A generic WeeChat script.
|
||||
class WeechatScript:
|
||||
"""A generic WeeChat script.
|
||||
|
||||
This class must NOT be instantiated directly, use subclasses instead:
|
||||
PythonScript, PerlScript, ...
|
||||
"""
|
||||
|
||||
def __init__(self, unparse_class, tree, source_script, output_dir,
|
||||
language, extension, comment_char='#',
|
||||
weechat_module='weechat'):
|
||||
# pylint: disable=too-many-arguments
|
||||
def __init__(
|
||||
self,
|
||||
unparse_class: Type[Unparse],
|
||||
tree: ast.AST,
|
||||
source_script: str,
|
||||
output_dir: str,
|
||||
language: str,
|
||||
extension: str,
|
||||
comment_char: str = "#",
|
||||
weechat_module: str = "weechat",
|
||||
) -> None:
|
||||
"""Initialize a WeeChat script generator."""
|
||||
self.unparse_class = unparse_class
|
||||
self.tree = tree
|
||||
self.source_script = os.path.realpath(source_script)
|
||||
self.output_dir = os.path.realpath(output_dir)
|
||||
self.language = language
|
||||
self.extension = extension
|
||||
self.script_name = 'weechat_testapi.%s' % extension
|
||||
self.script_path = os.path.join(self.output_dir, self.script_name)
|
||||
self.script_name = f"weechat_testapi.{extension}"
|
||||
self.script_path = Path(self.output_dir) / self.script_name
|
||||
self.comment_char = comment_char
|
||||
self.weechat_module = weechat_module
|
||||
self.update_tree()
|
||||
|
||||
def comment(self, string):
|
||||
def comment(self, string: str) -> str:
|
||||
"""Get a commented line."""
|
||||
return '%s %s' % (self.comment_char, string)
|
||||
return f"{self.comment_char} {string}"
|
||||
|
||||
def update_tree(self):
|
||||
def update_tree(self) -> None:
|
||||
"""Make changes in AST tree."""
|
||||
functions = {
|
||||
'prnt': 'print',
|
||||
'prnt_date_tags': 'print_date_tags',
|
||||
'prnt_datetime_tags': 'print_datetime_tags',
|
||||
'prnt_y': 'print_y',
|
||||
'prnt_y_date_tags': 'print_y_date_tags',
|
||||
'prnt_y_datetime_tags': 'print_y_datetime_tags',
|
||||
"prnt": "print",
|
||||
"prnt_date_tags": "print_date_tags",
|
||||
"prnt_datetime_tags": "print_datetime_tags",
|
||||
"prnt_y": "print_y",
|
||||
"prnt_y_date_tags": "print_y_date_tags",
|
||||
"prnt_y_datetime_tags": "print_y_datetime_tags",
|
||||
}
|
||||
tests_count = 0
|
||||
for node in ast.walk(self.tree):
|
||||
# rename some API functions
|
||||
if self.language != 'python' and \
|
||||
isinstance(node, ast.Call) and \
|
||||
isinstance(node.func, ast.Attribute) and \
|
||||
node.func.value.id == 'weechat':
|
||||
if (
|
||||
self.language != "python"
|
||||
and isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.value.id == "weechat"
|
||||
):
|
||||
node.func.attr = functions.get(node.func.attr, node.func.attr)
|
||||
# count number of tests
|
||||
if isinstance(node, ast.Call) and \
|
||||
isinstance(node.func, ast.Name) and \
|
||||
node.func.id == 'check':
|
||||
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "check":
|
||||
tests_count += 1
|
||||
|
||||
# replace script variables in string values
|
||||
variables = {
|
||||
'{SCRIPT_SOURCE}': self.source_script,
|
||||
'{SCRIPT_NAME}': self.script_name,
|
||||
'{SCRIPT_PATH}': self.script_path,
|
||||
'{SCRIPT_AUTHOR}': 'Sebastien Helleu',
|
||||
'{SCRIPT_VERSION}': '1.0',
|
||||
'{SCRIPT_LICENSE}': 'GPL3',
|
||||
'{SCRIPT_DESCRIPTION}': ('%s scripting API test' %
|
||||
self.language.capitalize()),
|
||||
'{SCRIPT_LANGUAGE}': self.language,
|
||||
'{SCRIPT_TESTS}': str(tests_count),
|
||||
"{SCRIPT_SOURCE}": self.source_script,
|
||||
"{SCRIPT_NAME}": self.script_name,
|
||||
"{SCRIPT_PATH}": str(self.script_path),
|
||||
"{SCRIPT_AUTHOR}": "Sebastien Helleu",
|
||||
"{SCRIPT_VERSION}": "1.0",
|
||||
"{SCRIPT_LICENSE}": "GPL3",
|
||||
"{SCRIPT_DESCRIPTION}": f"{self.language.capitalize()} scripting API test",
|
||||
"{SCRIPT_LANGUAGE}": self.language,
|
||||
"{SCRIPT_TESTS}": str(tests_count),
|
||||
}
|
||||
# replace variables
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.Constant) and node.value in variables:
|
||||
node.value = variables[node.value]
|
||||
|
||||
def write_header(self, output):
|
||||
def write_header(self, output: io.TextIOBase) -> None:
|
||||
"""Generate script header (just comments by default)."""
|
||||
comments = (
|
||||
'',
|
||||
'%s -- WeeChat %s scripting API testing' % (
|
||||
self.script_name, self.language.capitalize()),
|
||||
'',
|
||||
'WeeChat script automatically generated by testapigen.py.',
|
||||
'DO NOT EDIT BY HAND!',
|
||||
'',
|
||||
'Date: %s' % datetime.now(),
|
||||
'',
|
||||
"",
|
||||
f"{self.script_name} -- WeeChat {self.language.capitalize()} scripting API testing",
|
||||
"",
|
||||
"WeeChat script automatically generated by testapigen.py.",
|
||||
"DO NOT EDIT BY HAND!",
|
||||
"",
|
||||
f"Date: {datetime.datetime.now(tz=datetime.timezone.utc)}",
|
||||
"",
|
||||
)
|
||||
for line in comments:
|
||||
output.write(self.comment(line).rstrip() + '\n')
|
||||
output.writelines(f"{self.comment(line)}\n" for line in comments)
|
||||
|
||||
def write(self):
|
||||
def write(self) -> None:
|
||||
"""Write script on disk."""
|
||||
print('Writing script %s... ' % self.script_path, end='')
|
||||
with open(self.script_path, 'w') as output:
|
||||
print(f"Writing script {self.script_path}... ", end="")
|
||||
with self.script_path.open("w") as output:
|
||||
self.write_header(output)
|
||||
self.unparse_class(output).add(self.tree)
|
||||
output.write('\n')
|
||||
output.write("\n")
|
||||
self.write_footer(output)
|
||||
print('OK')
|
||||
print("OK")
|
||||
|
||||
def write_footer(self, output):
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer (nothing by default)."""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
|
||||
class WeechatPythonScript(WeechatScript):
|
||||
"""A WeeChat script written in Python."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatPythonScript, self).__init__(
|
||||
UnparsePython, tree, source_script, output_dir, 'python', 'py')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Python script."""
|
||||
super().__init__(UnparsePython, tree, source_script, output_dir, "python", "py")
|
||||
|
||||
def write_header(self, output):
|
||||
super(WeechatPythonScript, self).write_header(output)
|
||||
output.write('\n'
|
||||
'import weechat')
|
||||
def write_header(self, output: io.TextIOBase) -> None:
|
||||
"""Write header of Python script."""
|
||||
super().write_header(output)
|
||||
output.write("\nimport weechat")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'\n'
|
||||
'if __name__ == "__main__":\n'
|
||||
' weechat_init()\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of Python script."""
|
||||
super().write_footer(output)
|
||||
output.write('\n\nif __name__ == "__main__":\n weechat_init()\n')
|
||||
|
||||
|
||||
class WeechatPerlScript(WeechatScript):
|
||||
"""A WeeChat script written in Perl."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatPerlScript, self).__init__(
|
||||
UnparsePerl, tree, source_script, output_dir, 'perl', 'pl')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Perl script."""
|
||||
super().__init__(UnparsePerl, tree, source_script, output_dir, "perl", "pl")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'weechat_init();\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of Perl script."""
|
||||
super().write_footer(output)
|
||||
output.write("\nweechat_init();\n")
|
||||
|
||||
|
||||
class WeechatRubyScript(WeechatScript):
|
||||
"""A WeeChat script written in Ruby."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatRubyScript, self).__init__(
|
||||
UnparseRuby, tree, source_script, output_dir, 'ruby', 'rb')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Ruby script."""
|
||||
super().__init__(UnparseRuby, tree, source_script, output_dir, "ruby", "rb")
|
||||
|
||||
def update_tree(self):
|
||||
super(WeechatRubyScript, self).update_tree()
|
||||
def update_tree(self) -> None:
|
||||
"""Make changes in AST tree of Ruby script."""
|
||||
super().update_tree()
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.Attribute) and \
|
||||
node.value.id == 'weechat':
|
||||
node.value.id = 'Weechat'
|
||||
if isinstance(node, ast.Call) \
|
||||
and isinstance(node.func, ast.Attribute) \
|
||||
and node.func.attr == 'config_new_option':
|
||||
if isinstance(node, ast.Attribute) and node.value.id == "weechat":
|
||||
node.value.id = "Weechat"
|
||||
if (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr == "config_new_option"
|
||||
):
|
||||
node.args = [*node.args[:11], ast.List(node.args[11:])]
|
||||
|
||||
|
||||
class WeechatLuaScript(WeechatScript):
|
||||
"""A WeeChat script written in Lua."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatLuaScript, self).__init__(
|
||||
UnparseLua, tree, source_script, output_dir, 'lua', 'lua',
|
||||
comment_char='--')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Lua script."""
|
||||
super().__init__(UnparseLua, tree, source_script, output_dir, "lua", "lua", comment_char="--")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'weechat_init()\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of Lua script."""
|
||||
super().write_footer(output)
|
||||
output.write("\nweechat_init()\n")
|
||||
|
||||
|
||||
class WeechatTclScript(WeechatScript):
|
||||
"""A WeeChat script written in Tcl."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatTclScript, self).__init__(
|
||||
UnparseTcl, tree, source_script, output_dir, 'tcl', 'tcl')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Tcl script."""
|
||||
super().__init__(UnparseTcl, tree, source_script, output_dir, "tcl", "tcl")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'weechat_init\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of Tcl script."""
|
||||
super().write_footer(output)
|
||||
output.write("\nweechat_init\n")
|
||||
|
||||
|
||||
class WeechatGuileScript(WeechatScript):
|
||||
"""A WeeChat script written in Guile (Scheme)."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatGuileScript, self).__init__(
|
||||
UnparseGuile, tree, source_script, output_dir, 'guile', 'scm',
|
||||
comment_char=';')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize Guile script."""
|
||||
super().__init__(UnparseGuile, tree, source_script, output_dir, "guile", "scm", comment_char=";")
|
||||
|
||||
def update_tree(self):
|
||||
super(WeechatGuileScript, self).update_tree()
|
||||
def update_tree(self) -> None:
|
||||
"""Make changes in AST tree of Guile script."""
|
||||
super().update_tree()
|
||||
functions_with_list = (
|
||||
'config_new_section',
|
||||
'config_new_option',
|
||||
"config_new_section",
|
||||
"config_new_option",
|
||||
)
|
||||
for node in ast.walk(self.tree):
|
||||
if isinstance(node, ast.Call) \
|
||||
and isinstance(node.func, ast.Attribute) \
|
||||
and node.func.attr in functions_with_list:
|
||||
node.args = [ast.Call('list', node.args)]
|
||||
if (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Attribute)
|
||||
and node.func.attr in functions_with_list
|
||||
):
|
||||
node.args = [ast.Call("list", node.args)]
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'(weechat_init)\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of Guile script."""
|
||||
super().write_footer(output)
|
||||
output.write("\n(weechat_init)\n")
|
||||
|
||||
|
||||
class WeechatJavaScriptScript(WeechatScript):
|
||||
"""A WeeChat script written in JavaScript."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatJavaScriptScript, self).__init__(
|
||||
UnparseJavaScript, tree, source_script, output_dir,
|
||||
'javascript', 'js', comment_char='//')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize JavaScript script."""
|
||||
super().__init__(UnparseJavaScript, tree, source_script, output_dir, "javascript", "js", comment_char="//")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'weechat_init()\n')
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Writer footer of JavaScript script."""
|
||||
super().write_footer(output)
|
||||
output.write("\nweechat_init()\n")
|
||||
|
||||
|
||||
class WeechatPhpScript(WeechatScript):
|
||||
"""A WeeChat script written in PHP."""
|
||||
|
||||
def __init__(self, tree, source_script, output_dir):
|
||||
super(WeechatPhpScript, self).__init__(
|
||||
UnparsePhp, tree, source_script, output_dir, 'php', 'php',
|
||||
comment_char='//')
|
||||
def __init__(self, tree: ast.AST, source_script: str, output_dir: str) -> None:
|
||||
"""Initialize PHP script."""
|
||||
super().__init__(UnparsePhp, tree, source_script, output_dir, "php", "php", comment_char="//")
|
||||
|
||||
def write_header(self, output):
|
||||
output.write('<?php\n')
|
||||
super(WeechatPhpScript, self).write_header(output)
|
||||
def write_header(self, output: io.TextIOBase) -> None:
|
||||
"""Writer header of PHP script."""
|
||||
output.write("<?php\n")
|
||||
super().write_header(output)
|
||||
|
||||
def write_footer(self, output: io.TextIOBase) -> None:
|
||||
"""Write footer of PHP script."""
|
||||
super().write_footer(output)
|
||||
output.write("\nweechat_init();\n")
|
||||
|
||||
def write_footer(self, output):
|
||||
output.write('\n'
|
||||
'weechat_init();\n')
|
||||
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def update_nodes(tree):
|
||||
"""
|
||||
Update the tests AST tree (in-place):
|
||||
def update_nodes(tree: ast.AST) -> None:
|
||||
"""Update the tests AST tree (in-place).
|
||||
|
||||
Actions performed:
|
||||
1. add a print message in each test_* function
|
||||
2. add arguments in calls to check() function
|
||||
"""
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.FunctionDef) and \
|
||||
node.name.startswith('test_'):
|
||||
if isinstance(node, ast.FunctionDef) and node.name.startswith("test_"):
|
||||
# add a print at the beginning of each test function
|
||||
node.body.insert(
|
||||
0, ast.parse('weechat.prnt("", " > %s");' % node.name))
|
||||
elif isinstance(node, ast.Call) and \
|
||||
isinstance(node.func, ast.Name) and \
|
||||
node.func.id == 'check':
|
||||
node.body.insert(0, ast.parse(f'weechat.prnt("", " > {node.name}");'))
|
||||
elif isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == "check":
|
||||
# add two arguments in the call to "check" function:
|
||||
# 1. the string representation of the test
|
||||
# 2. the line number in source (as string)
|
||||
# for example if this test is on line 50:
|
||||
# check(weechat.test() == 123)
|
||||
# >>> check(weechat.test() == 123)
|
||||
# it becomes:
|
||||
# check(weechat.test() == 123, 'weechat.test() == 123', '50')
|
||||
output = StringIO()
|
||||
# >>> check(weechat.test() == 123, 'weechat.test() == 123', '50')
|
||||
output = io.StringIO()
|
||||
unparsed = UnparsePython(output=output)
|
||||
unparsed.add(node.args[0])
|
||||
node.args.append(ast.Constant(output.getvalue()))
|
||||
node.args.append(ast.Constant(str(node.func.lineno)))
|
||||
|
||||
|
||||
def get_tests(path):
|
||||
def get_tests(path: str) -> ast.AST:
|
||||
"""Parse the source with tests and return the AST node."""
|
||||
test_script = open(path).read()
|
||||
tests = ast.parse(test_script)
|
||||
update_nodes(tests)
|
||||
return tests
|
||||
with Path(path).open() as f:
|
||||
test_script = f.read()
|
||||
tests = ast.parse(test_script)
|
||||
update_nodes(tests)
|
||||
return tests
|
||||
|
||||
|
||||
def generate_scripts(source_script, output_dir):
|
||||
def generate_scripts(source_script: str, output_dir: str) -> Tuple[int, Union[str, None]]:
|
||||
"""Generate scripts in all languages to test the API."""
|
||||
ret_code = 0
|
||||
error = None
|
||||
try:
|
||||
for name, obj in inspect.getmembers(sys.modules[__name__]):
|
||||
if inspect.isclass(obj) and name != 'WeechatScript' and \
|
||||
name.startswith('Weechat') and name.endswith('Script'):
|
||||
if (
|
||||
inspect.isclass(obj)
|
||||
and name != "WeechatScript"
|
||||
and name.startswith("Weechat")
|
||||
and name.endswith("Script")
|
||||
):
|
||||
tests = get_tests(source_script)
|
||||
obj(tests, source_script, output_dir).write()
|
||||
except Exception as exc: # pylint: disable=broad-except
|
||||
except Exception as exc: # noqa: BLE001
|
||||
ret_code = 1
|
||||
error = 'ERROR: %s\n\n%s' % (str(exc), traceback.format_exc())
|
||||
error = f"ERROR: {exc}\n\n{traceback.format_exc()}"
|
||||
return ret_code, error
|
||||
|
||||
|
||||
def testapigen_cmd_cb(data, buf, args):
|
||||
def testapigen_cmd_cb(data: str, buf: str, args: str) -> int: # noqa: ARG001
|
||||
"""Callback for WeeChat command /testapigen."""
|
||||
|
||||
def print_error(msg):
|
||||
def print_error(msg: str) -> None:
|
||||
"""Print an error message on core buffer."""
|
||||
weechat.prnt('', '%s%s' % (weechat.prefix('error'), msg))
|
||||
weechat.prnt("", f"{weechat.prefix('error')}{msg}")
|
||||
|
||||
try:
|
||||
source_script, output_dir = args.split()
|
||||
except ValueError:
|
||||
print_error('ERROR: invalid arguments for /testapigen')
|
||||
print_error("ERROR: invalid arguments for /testapigen")
|
||||
return weechat.WEECHAT_RC_OK
|
||||
if not weechat.mkdir_parents(output_dir, 0o755):
|
||||
print_error('ERROR: invalid directory: %s' % output_dir)
|
||||
print_error("ERROR: invalid directory: {output_dir}")
|
||||
return weechat.WEECHAT_RC_OK
|
||||
ret_code, error = generate_scripts(source_script, output_dir)
|
||||
if error:
|
||||
@@ -385,23 +395,16 @@ def testapigen_cmd_cb(data, buf, args):
|
||||
return weechat.WEECHAT_RC_OK if ret_code == 0 else weechat.WEECHAT_RC_ERROR
|
||||
|
||||
|
||||
def get_parser_args():
|
||||
def get_parser_args() -> argparse.Namespace:
|
||||
"""Get parser arguments."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description=('Generate WeeChat scripts in all languages '
|
||||
'to test the API.'))
|
||||
parser.add_argument(
|
||||
'script',
|
||||
help='the path to Python script with tests')
|
||||
parser.add_argument(
|
||||
'-o', '--output-dir',
|
||||
default='.',
|
||||
help='output directory (defaults to current directory)')
|
||||
parser = argparse.ArgumentParser(description=("Generate WeeChat scripts in all languages to test the API."))
|
||||
parser.add_argument("script", help="the path to Python script with tests")
|
||||
parser.add_argument("-o", "--output-dir", default=".", help="output directory (defaults to current directory)")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
"""Main function (when script is not loaded in WeeChat)."""
|
||||
def main() -> None:
|
||||
"""Generate all scripts (when script is not loaded in WeeChat)."""
|
||||
args = get_parser_args()
|
||||
ret_code, error = generate_scripts(args.script, args.output_dir)
|
||||
if error:
|
||||
@@ -409,17 +412,17 @@ def main():
|
||||
sys.exit(ret_code)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
if RUNNING_IN_WEECHAT:
|
||||
weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION,
|
||||
SCRIPT_LICENSE, SCRIPT_DESC, '', '')
|
||||
weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "")
|
||||
weechat.hook_command(
|
||||
SCRIPT_COMMAND,
|
||||
'Generate scripting API test scripts',
|
||||
'source_script output_dir',
|
||||
'source_script: path to source script (testapi.py)\n'
|
||||
' output_dir: output directory for scripts',
|
||||
'',
|
||||
'testapigen_cmd_cb', '')
|
||||
"Generate scripting API test scripts",
|
||||
"source_script output_dir",
|
||||
"source_script: path to source script (testapi.py)\n output_dir: output directory for scripts",
|
||||
"",
|
||||
"testapigen_cmd_cb",
|
||||
"",
|
||||
)
|
||||
else:
|
||||
main()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+30
-52
@@ -18,7 +18,8 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
"""Check Curl symbols defined in WeeChat source code.
|
||||
|
||||
Check if Curl symbols defined in src/core/core-url.c are matching symbols
|
||||
defined in Curl (introduced/deprecated/last versions), using this file:
|
||||
https://github.com/curl/curl/blob/master/docs/libcurl/symbols-in-versions.
|
||||
@@ -41,16 +42,15 @@ Or with Curl repository cloned locally:
|
||||
This script requires Python 3.7+.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, TextIO, Tuple
|
||||
# ruff: noqa: COM812,T201
|
||||
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
SRC_PATH = (
|
||||
Path(__file__).resolve().parent.parent / "src" / "core" / "core-url.c"
|
||||
)
|
||||
SRC_PATH = Path(__file__).resolve().parent.parent / "src" / "core" / "core-url.c"
|
||||
|
||||
# NOTE: keep version in sync with CMakeLists.txt
|
||||
CURL_MIN_VERSION_STR = "7.68.0"
|
||||
@@ -72,13 +72,10 @@ WEECHAT_CURL_MIN_MAX_VERSION_RE = (
|
||||
r"(?P<str_max_version>[0-9][0-9.]+) \*/"
|
||||
)
|
||||
WEECHAT_ENDIF_RE = r"#endif"
|
||||
WEECHAT_CURL_CONSTANT_RE = (
|
||||
r" URL_DEF_CONST\((?P<prefix>[A-Z0-9_]+), (?P<name>[A-Z0-9_]+)\),"
|
||||
)
|
||||
WEECHAT_CURL_OPTION_RE = (
|
||||
r" URL_DEF_OPTION\((?P<name>[A-Z0-9_]+), .*\),"
|
||||
)
|
||||
WEECHAT_CURL_CONSTANT_RE = r" URL_DEF_CONST\((?P<prefix>[A-Z0-9_]+), (?P<name>[A-Z0-9_]+)\),"
|
||||
WEECHAT_CURL_OPTION_RE = r" URL_DEF_OPTION\((?P<name>[A-Z0-9_]+), .*\),"
|
||||
CURL_SYMBOL_RE = r"[A-Z][A-Z0-9_]"
|
||||
CURL_VERSION_ITEMS = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -92,8 +89,7 @@ class WeechatCurlSymbol:
|
||||
|
||||
|
||||
def curl_version_to_int(version: str) -> int:
|
||||
"""
|
||||
Convert Curl version as string to integer.
|
||||
"""Convert Curl version as string to integer.
|
||||
|
||||
:param version: version as string (eg: "7.87.0")
|
||||
:return: version as integer, eg: 481024 (== 0x075700, 0x57 == 87)
|
||||
@@ -102,7 +98,7 @@ def curl_version_to_int(version: str) -> int:
|
||||
return 0
|
||||
result = 0
|
||||
items = version.split(".")
|
||||
if len(items) < 3:
|
||||
while len(items) < CURL_VERSION_ITEMS:
|
||||
items.append("0")
|
||||
factor = 0
|
||||
for item in reversed(items):
|
||||
@@ -112,8 +108,7 @@ def curl_version_to_int(version: str) -> int:
|
||||
|
||||
|
||||
def curl_version_to_str(version: int) -> str:
|
||||
"""
|
||||
Convert Curl version as integer to string.
|
||||
"""Convert Curl version as integer to string.
|
||||
|
||||
:param version: version as integer, eg: 481024 (0x075700)
|
||||
:return: version as string, eg: "7.87.0"
|
||||
@@ -127,15 +122,14 @@ def curl_version_to_str(version: int) -> str:
|
||||
return result.rstrip(".")
|
||||
|
||||
|
||||
def get_curl_symbols(symbols_file: TextIO) -> Dict[str, Tuple[int, int]]:
|
||||
"""
|
||||
Parse file docs/libcurl/symbols-in-versions from Curl repository.
|
||||
def get_curl_symbols(symbols_file: io.TextIOBase) -> dict[str, tuple[int, int]]:
|
||||
"""Parse file docs/libcurl/symbols-in-versions from Curl repository.
|
||||
|
||||
:param symbols_file: file with Curl symbols
|
||||
:return: Curl symbols as dict: {name: (version_min, version_max)}
|
||||
"""
|
||||
curl_symbol_pattern = re.compile(CURL_SYMBOL_RE)
|
||||
symbols: Dict[str, Tuple[int, int]] = {}
|
||||
symbols: dict[str, tuple[int, int]] = {}
|
||||
if symbols_file.isatty():
|
||||
return symbols
|
||||
for line in symbols_file:
|
||||
@@ -150,10 +144,8 @@ def get_curl_symbols(symbols_file: TextIO) -> Dict[str, Tuple[int, int]]:
|
||||
return symbols
|
||||
|
||||
|
||||
def check_req_symbols(symbols: List[WeechatCurlSymbol]) -> int:
|
||||
"""
|
||||
Checks the symbols' min/max version, relative to minimum Curl version that
|
||||
we require.
|
||||
def check_req_symbols(symbols: list[WeechatCurlSymbol]) -> int:
|
||||
"""Check the symbols' min/max version, relative to min Curl version required.
|
||||
|
||||
:return: errors
|
||||
"""
|
||||
@@ -177,13 +169,11 @@ def check_req_symbols(symbols: List[WeechatCurlSymbol]) -> int:
|
||||
return errors
|
||||
|
||||
|
||||
def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
"""
|
||||
Parse Curl symbols declared in src/core/core-url.c.
|
||||
def get_weechat_curl_symbols() -> tuple[list[WeechatCurlSymbol], int]: # noqa: C901,PLR0915
|
||||
"""Parse Curl symbols declared in src/core/core-url.c.
|
||||
|
||||
:return: tuple (list_symbols, errors)
|
||||
"""
|
||||
# pylint: disable=too-many-locals,too-many-statements
|
||||
min_version_pattern = re.compile(WEECHAT_CURL_MIN_VERSION_RE)
|
||||
max_version_pattern = re.compile(WEECHAT_CURL_MAX_VERSION_RE)
|
||||
min_max_version_pattern = re.compile(WEECHAT_CURL_MIN_MAX_VERSION_RE)
|
||||
@@ -192,10 +182,10 @@ def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
option_pattern = re.compile(WEECHAT_CURL_OPTION_RE)
|
||||
v_min: int = 0
|
||||
v_max: int = 0
|
||||
symbols: List[WeechatCurlSymbol] = []
|
||||
symbols: list[WeechatCurlSymbol] = []
|
||||
errors: int = 0
|
||||
line_no: int = 0
|
||||
with open(SRC_PATH, encoding="utf-8") as src_file:
|
||||
with Path(SRC_PATH).open(encoding="utf-8") as src_file:
|
||||
for line in src_file:
|
||||
line_no += 1
|
||||
# min Curl version
|
||||
@@ -207,9 +197,7 @@ def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
comment_min = curl_version_to_int(str_min_vers)
|
||||
if v_min != comment_min:
|
||||
print(
|
||||
f"{SRC_PATH}:{line_no}: min version not matching "
|
||||
f"the comment: "
|
||||
f"{hex_min_vers} != {str_min_vers}"
|
||||
f"{SRC_PATH}:{line_no}: min version not matching the comment: {hex_min_vers} != {str_min_vers}"
|
||||
)
|
||||
errors += 1
|
||||
continue
|
||||
@@ -222,9 +210,7 @@ def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
comment_max = curl_version_to_int(str_max_vers)
|
||||
if v_max != comment_max:
|
||||
print(
|
||||
f"{SRC_PATH}:{line_no}: max version not matching "
|
||||
f"the comment: "
|
||||
f"{hex_max_vers} != {str_max_vers}"
|
||||
f"{SRC_PATH}:{line_no}: max version not matching the comment: {hex_max_vers} != {str_max_vers}"
|
||||
)
|
||||
errors += 1
|
||||
continue
|
||||
@@ -240,16 +226,12 @@ def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
comment_max = curl_version_to_int(str_max_vers)
|
||||
if v_min != comment_min:
|
||||
print(
|
||||
f"{SRC_PATH}:{line_no}: min version not matching "
|
||||
f"the comment: "
|
||||
f"{hex_min_vers} != {str_min_vers}"
|
||||
f"{SRC_PATH}:{line_no}: min version not matching the comment: {hex_min_vers} != {str_min_vers}"
|
||||
)
|
||||
errors += 1
|
||||
if v_max != comment_max:
|
||||
print(
|
||||
f"{SRC_PATH}:{line_no}: max version not matching "
|
||||
f"the comment: "
|
||||
f"{hex_max_vers} != {str_max_vers}"
|
||||
f"{SRC_PATH}:{line_no}: max version not matching the comment: {hex_max_vers} != {str_max_vers}"
|
||||
)
|
||||
errors += 1
|
||||
continue
|
||||
@@ -276,11 +258,10 @@ def get_weechat_curl_symbols() -> Tuple[List[WeechatCurlSymbol], int]:
|
||||
|
||||
|
||||
def check_symbols(
|
||||
weechat_curl_symbols: List[WeechatCurlSymbol],
|
||||
curl_symbols: Dict[str, Tuple[int, int]],
|
||||
weechat_curl_symbols: list[WeechatCurlSymbol],
|
||||
curl_symbols: dict[str, tuple[int, int]],
|
||||
) -> int:
|
||||
"""
|
||||
Check that symbols declared are matching Curl symbols.
|
||||
"""Check that symbols declared are matching Curl symbols.
|
||||
|
||||
:param weechat_curl_symbols: list of Curl symbols in WeeChat
|
||||
:param curl_symbols: list of all Curl symbols
|
||||
@@ -291,10 +272,7 @@ def check_symbols(
|
||||
for symbol in weechat_curl_symbols:
|
||||
curl_symbol = curl_symbols.get(symbol.name)
|
||||
if not curl_symbol:
|
||||
print(
|
||||
f"{SRC_PATH}:{symbol.line_no}: symbol {symbol.name} "
|
||||
f"not found in Curl"
|
||||
)
|
||||
print(f"{SRC_PATH}:{symbol.line_no}: symbol {symbol.name} not found in Curl")
|
||||
errors += 1
|
||||
continue
|
||||
if curl_symbol[0] > req_curl and symbol.min_curl != curl_symbol[0]:
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#
|
||||
# Check shell and Python scripts in WeeChat git repository using these tools:
|
||||
# - shell scripts: shellcheck
|
||||
# - Python scripts: flake8 + pylint + bandit
|
||||
# - Python scripts: ruff
|
||||
#
|
||||
|
||||
set -o errexit
|
||||
@@ -45,7 +45,5 @@ done
|
||||
|
||||
# check Python scripts
|
||||
for script in ${python_scripts}; do
|
||||
flake8 --max-line-length=100 --builtins=_ "${root_dir}/$script"
|
||||
pylint --additional-builtins=_ "${root_dir}/$script"
|
||||
bandit "${root_dir}/$script"
|
||||
ruff check "${root_dir}/$script"
|
||||
done
|
||||
|
||||
@@ -19,16 +19,16 @@
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""
|
||||
Generate Python stub: API constants and functions, with type annotations.
|
||||
"""Generate Python stub with API constants and functions.
|
||||
|
||||
This script requires Python 3.6+.
|
||||
This script requires Python 3.10+.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from textwrap import indent
|
||||
# ruff: noqa: T201
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from textwrap import indent
|
||||
|
||||
DOC_DIR = Path(__file__).resolve().parent.parent / "doc" / "en"
|
||||
SRC_DIR = Path(__file__).resolve().parent.parent / "src"
|
||||
@@ -43,12 +43,12 @@ STUB_HEADER = """\
|
||||
# DO NOT EDIT BY HAND!
|
||||
#
|
||||
|
||||
# ruff: noqa: A002,D400,D205,D301,D415,E501,PIE790,PYI021,PYI048,UP006,UP007,UP035
|
||||
|
||||
from typing import Dict, Union
|
||||
"""
|
||||
|
||||
CONSTANT_RE = (
|
||||
r"WEECHAT_SCRIPT_CONST_(?P<type>(INT|STR))\((?P<constant>WEECHAT_[A-Z0-9_]+)\)"
|
||||
)
|
||||
CONSTANT_RE = r"WEECHAT_SCRIPT_CONST_(?P<type>(INT|STR))\((?P<constant>WEECHAT_[A-Z0-9_]+)\)"
|
||||
|
||||
FUNCTION_RE = r"""\[source,python\]
|
||||
----
|
||||
@@ -60,40 +60,34 @@ def (?P<function>\w+)(?P<args>[^)]*)(?P<return>\) -> [^:]+:) \.\.\.(?P<example>.
|
||||
def print_stub_constants() -> None:
|
||||
"""Print constants, extracted from the plugin-script.c."""
|
||||
constant_pattern = re.compile(CONSTANT_RE)
|
||||
with open(
|
||||
SRC_DIR / "plugins" / "plugin-script.c", encoding="utf-8"
|
||||
) as plugin_script_file, open(
|
||||
SRC_DIR / "plugins" / "weechat-plugin.h", encoding="utf-8"
|
||||
) as plugin_public_header_file:
|
||||
plugin_script_c = Path(SRC_DIR / "plugins" / "plugin-script.c")
|
||||
weechat_plugin_h = Path(SRC_DIR / "plugins" / "weechat-plugin.h")
|
||||
with (
|
||||
plugin_script_c.open(encoding="utf-8") as plugin_script_file,
|
||||
weechat_plugin_h.open(encoding="utf-8") as plugin_public_header_file,
|
||||
):
|
||||
plugin_script = plugin_script_file.read()
|
||||
plugin_public_header = plugin_public_header_file.read()
|
||||
for match in constant_pattern.finditer(plugin_script):
|
||||
value_re = rf'^#define {match["constant"]} +(?P<value>[\w"-]+)$'
|
||||
value_match = re.search(value_re, plugin_public_header, re.MULTILINE)
|
||||
value = f' = {value_match["value"]}' if value_match else ""
|
||||
print(f'{match["constant"]}: {match["type"].lower()}{value}')
|
||||
value = f" = {value_match['value']}" if value_match else ""
|
||||
print(f"{match['constant']}: {match['type'].lower()}{value}")
|
||||
|
||||
|
||||
def print_stub_functions() -> None:
|
||||
"""Print function prototypes, extracted from the Plugin API reference."""
|
||||
function_pattern = re.compile(FUNCTION_RE, re.DOTALL)
|
||||
with open(DOC_DIR / "weechat_plugin_api.en.adoc",
|
||||
encoding="utf-8") as api_doc_file:
|
||||
with Path(DOC_DIR / "weechat_plugin_api.en.adoc").open(encoding="utf-8") as api_doc_file:
|
||||
api_doc = api_doc_file.read()
|
||||
for match in function_pattern.finditer(api_doc):
|
||||
url = f'https://weechat.org/doc/weechat/api/#_{match["function"]}'
|
||||
example = (
|
||||
f'\n ::\n\n{indent(match["example"].lstrip(), " " * 8)}'
|
||||
if match["example"]
|
||||
else ""
|
||||
)
|
||||
print(
|
||||
f"""\n
|
||||
url = f"https://weechat.org/doc/weechat/api/#_{match['function']}"
|
||||
example = f"\n ::\n\n{indent(match['example'].lstrip(), ' ' * 8)}\n " if match["example"] else ""
|
||||
str_func = f"""\n
|
||||
def {match["function"]}{match["args"]}{match["return"]}
|
||||
\"""`{match["function"]} in WeeChat plugin API reference <{url}>`_{example}
|
||||
\"""
|
||||
\"""`{match["function"]} in WeeChat plugin API reference <{url}>`_{example}\"""
|
||||
..."""
|
||||
)
|
||||
print(str_func)
|
||||
|
||||
|
||||
def stub_api() -> None:
|
||||
|
||||
Reference in New Issue
Block a user