From ffaed0f0cf2ddf339a6dc21b004da0fa52301290 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Fri, 6 Dec 2024 03:47:05 +0000 Subject: [PATCH 1/8] Backport to Python 3.8 Only requires adding a few `from __future__ import annotations` Signed-off-by: Bolun Thompson --- libbash/api.py | 2 ++ libbash/bash_command/command.py | 2 ++ libbash/bash_command/flags.py | 2 ++ libbash/test.py | 2 ++ pyproject.toml | 2 +- 5 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libbash/api.py b/libbash/api.py index 9048108..609ce72 100644 --- a/libbash/api.py +++ b/libbash/api.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .bash_command import * import ctypes import os diff --git a/libbash/bash_command/command.py b/libbash/bash_command/command.py index b72aefd..1a18d08 100644 --- a/libbash/bash_command/command.py +++ b/libbash/bash_command/command.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Union, Optional from .. import ctypes_bash_command as c_bash import ctypes diff --git a/libbash/bash_command/flags.py b/libbash/bash_command/flags.py index a6ae782..f490163 100644 --- a/libbash/bash_command/flags.py +++ b/libbash/bash_command/flags.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import Enum diff --git a/libbash/test.py b/libbash/test.py index 95ebb66..b742d7f 100644 --- a/libbash/test.py +++ b/libbash/test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from libbash.api import bash_to_ast, ast_to_bash, ast_to_json diff --git a/pyproject.toml b/pyproject.toml index 22894bf..de7e076 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ authors = [ ] description = "A library for parsing bash scripts." readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.8" license = {text = "GPL-3.0"} dependencies = [ ] From 40e0bb8b14292c8741396a75478b7dd531e66f82 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Fri, 6 Dec 2024 03:48:19 +0000 Subject: [PATCH 2/8] Add runnable tests Signed-off-by: Bolun Thompson --- libbash/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) mode change 100644 => 100755 libbash/test.py diff --git a/libbash/test.py b/libbash/test.py old mode 100644 new mode 100755 index b742d7f..5480eef --- a/libbash/test.py +++ b/libbash/test.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + from __future__ import annotations import sys @@ -181,4 +183,6 @@ def run_tests(): print("Running tests...") test_bash_and_ast_consistency() print("All tests passed!") - + +if __name__ == "__main__": + run_tests() From 1e2e735519bcbc3414db58b6049c9aef05e3c118 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Fri, 6 Dec 2024 04:19:32 +0000 Subject: [PATCH 3/8] Add github action for tests Signed-off-by: Bolun Thompson --- .github/workflows/test.yml | 42 ++++++++++++++++++++++++++++++++++++++ libbash/__init__.py | 1 - setup_test.sh | 5 +++++ libbash/test.py => test.py | 10 ++++++--- 4 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100755 setup_test.sh rename libbash/test.py => test.py (96%) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..1370732 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,42 @@ +name: Test +on: + pull_request_target: + types: [assigned, opened, synchronize, reopened, ready_for_review] + paths: + - libbash/** + push: + branches: + - main + paths: + - libbash/** +jobs: + LibBash-Test: + strategy: + fail-fast: false + matrix: + os: + - ubuntu-latest + runs-on: ${{ matrix.os }} + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + - name: Running Tests + run: | + python3 -m venv venv + . venv/bin/activate + ./setup_test.sh + + ./test.py | tee python.log + test_succ=$? + + # TODO: Is this working? + timer="$(LANG=en_us_88591; date)" + echo "VERSION<> "$GITHUB_ENV" + echo "OS:${{matrix.os}}" >> "$GITHUB_ENV" + echo "$timer" >> "$GITHUB_ENV" + cat python.log >> "$GITHUB_ENV" + echo 'EOF' >> "$GITHUB_ENV" + + exit $test_succ diff --git a/libbash/__init__.py b/libbash/__init__.py index 93deb0f..c46d66c 100644 --- a/libbash/__init__.py +++ b/libbash/__init__.py @@ -1,4 +1,3 @@ from .api import ast_to_json, bash_to_ast, ast_to_bash -from .test import run_tests diff --git a/setup_test.sh b/setup_test.sh new file mode 100755 index 0000000..bebe749 --- /dev/null +++ b/setup_test.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +git submodule update --init --recursive + +pip install -e . diff --git a/libbash/test.py b/test.py similarity index 96% rename from libbash/test.py rename to test.py index 5480eef..e4bfac2 100755 --- a/libbash/test.py +++ b/test.py @@ -11,11 +11,11 @@ # The file path to the bash.so file BASH_FILE_PATH = os.path.join(os.path.dirname( - __file__), "bash-5.2", "bash.so") + __file__), "libbash", "bash-5.2", "bash.so") # The file path to the bash-5.2/tests directory BASH_TESTS_DIR = os.path.join(os.path.dirname( - __file__), "bash-5.2", "tests") + __file__), "libbash", "bash-5.2", "tests") def get_test_files() -> list[str]: @@ -181,7 +181,11 @@ def run_tests(): Runs all the tests in this file """ print("Running tests...") - test_bash_and_ast_consistency() + try: + test_bash_and_ast_consistency() + except AssertionError: + print("Test failed!") + sys.exit(1) print("All tests passed!") if __name__ == "__main__": From 39bbb3c5a0cf1a366cde63e3578d7b353f1cac19 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Mon, 9 Dec 2024 00:53:14 +0000 Subject: [PATCH 4/8] Update submodule to use pash libbash Signed-off-by: Bolun Thompson --- .gitmodules | 2 +- libbash/bash-5.2 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index fce3308..6216fc5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "bash-5.2"] path = libbash/bash-5.2 - url = https://github.com/sethsabar/bash-for-libbash.git + url = https://github.com/binpash/bash-for-libbash diff --git a/libbash/bash-5.2 b/libbash/bash-5.2 index c8f14b3..9a8849a 160000 --- a/libbash/bash-5.2 +++ b/libbash/bash-5.2 @@ -1 +1 @@ -Subproject commit c8f14b301c8d2be7f28ffd49f8042b45e7f8f6fa +Subproject commit 9a8849a106f59a23e8740f7237833fb1bf9a60ec From 6f3a499ccd3b45c2448f802e26d7d7c057f77a70 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Wed, 11 Dec 2024 04:09:24 +0000 Subject: [PATCH 5/8] Fix simple type-hint errors Signed-off-by: Bolun Thompson --- libbash/api.py | 10 +++++---- libbash/bash_command/command.py | 40 ++++++++++++++++----------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/libbash/api.py b/libbash/api.py index 609ce72..39b2d6b 100644 --- a/libbash/api.py +++ b/libbash/api.py @@ -4,6 +4,8 @@ import ctypes import os +from typing import Any + # current location + ../../bash-5.2/bash.so BASH_FILE_PATH = os.path.join(os.path.dirname( __file__), "bash-5.2", "bash.so") @@ -64,7 +66,7 @@ def ast_to_bash(ast: list[Command], write_to: str): # don't decode the bytes, just write them to the file f.write(bash_str) -def ast_to_json(ast: list[Command]) -> list[dict[str, any]]: +def ast_to_json(ast: list[Command]) -> list[dict[str, Any]]: """ Converts the AST to a JSON style object. :param ast: The AST, a list of Command objects. @@ -74,7 +76,7 @@ def ast_to_json(ast: list[Command]) -> list[dict[str, any]]: def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ - [list[Command], list[Command, bytes, int, int]]: + list[Command] | list[tuple[Command, bytes, int, int]]: """ Extracts the AST from the bash source code. Uses ctypes to call an injected bash function that returns the AST. @@ -92,7 +94,7 @@ def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ bash.set_bash_file.restype = ctypes.c_int # call the function - set_result: ctypes.c_int = bash.set_bash_file(bash_file.encode('utf-8')) + set_result: int = bash.set_bash_file(bash_file.encode('utf-8')) if set_result < 0: raise IOError("Setting bash file failed") @@ -119,7 +121,7 @@ def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ "Bash read command failed, shell script may be invalid") # read the global_command variable - global_command: ctypes.POINTER(c_bash.command) = ctypes.POINTER( + global_command: ctypes._Pointer[c_bash.command] = ctypes.POINTER( c_bash.command).in_dll(bash, 'global_command') # global_command is null diff --git a/libbash/bash_command/command.py b/libbash/bash_command/command.py index 1a18d08..0ab1b01 100644 --- a/libbash/bash_command/command.py +++ b/libbash/bash_command/command.py @@ -54,7 +54,7 @@ def _to_ctypes(self) -> c_bash.word_desc: return c_word_desc -def word_desc_list_from_word_list(word_list: ctypes.POINTER(c_bash.word_list)) -> list[WordDesc]: +def word_desc_list_from_word_list(word_list: ctypes._Pointer[c_bash.word_list]) -> list[WordDesc]: """ :param word_list: the word list :return: a list of word descriptions @@ -66,7 +66,7 @@ def word_desc_list_from_word_list(word_list: ctypes.POINTER(c_bash.word_list)) - return word_desc_list -def c_word_list_from_word_desc_list(word_desc_list: list[WordDesc]) -> ctypes.POINTER(c_bash.word_list): +def c_word_list_from_word_desc_list(word_desc_list: list[WordDesc]) -> ctypes._Pointer[c_bash.word_list]: """ :param word_desc_list: the list of word descriptions :return: a pointer to the first word description in the list @@ -196,7 +196,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict, list]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: """ :return: a dictionary representation of the redirect struct """ @@ -206,7 +206,7 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: 'flags': [x._to_json() for x in self.flags], 'instruction': self.instruction._to_json(), 'redirectee': self.redirectee._to_json(), - 'here_doc_eof': self.here_doc_eof if self.here_doc_eof is not None else None + 'here_doc_eof': self.here_doc_eof } def _to_ctypes(self) -> c_bash.redirect: @@ -237,7 +237,7 @@ def _to_ctypes(self) -> c_bash.redirect: return c_redirect -def redirect_list_from_redirect(redirect: ctypes.POINTER(c_bash.redirect)) -> list[Redirect]: +def redirect_list_from_redirect(redirect: ctypes._Pointer[c_bash.redirect]) -> list[Redirect]: """ :param redirect: the redirect list :return: a list of redirects @@ -249,7 +249,7 @@ def redirect_list_from_redirect(redirect: ctypes.POINTER(c_bash.redirect)) -> li return redirect_list -def c_redirect_list_from_redirect_list(redirect_list: list[Redirect]) -> ctypes.POINTER(c_bash.redirect): +def c_redirect_list_from_redirect_list(redirect_list: list[Redirect]) -> ctypes._Pointer[c_bash.redirect]: """ :param redirect_list: the list of redirects :return: a pointer to the first redirect in the list @@ -261,7 +261,7 @@ def c_redirect_list_from_redirect_list(redirect_list: list[Redirect]) -> ctypes. return ctypes.POINTER(c_bash.redirect)(c_redirect) -def c_redirect_from_redirect_list(redirect_list: list[Redirect]) -> ctypes.POINTER(c_bash.redirect): +def c_redirect_from_redirect_list(redirect_list: list[Redirect]) -> ctypes._Pointer[c_bash.redirect]: """ :param redirect_list: the list of redirects :return: a pointer to the first redirect in the list @@ -369,7 +369,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, dict, list]]: + def _to_json(self) -> dict[str, Union[int, dict, list, None]]: """ :return: a dictionary representation of the pattern """ @@ -391,7 +391,7 @@ def _to_ctypes(self) -> c_bash.pattern_list: return c_pattern -def pattern_list_from_pattern_list(pattern: ctypes.POINTER(c_bash.pattern_list)) -> list[Pattern]: +def pattern_list_from_pattern_list(pattern: ctypes._Pointer[c_bash.pattern_list]) -> list[Pattern]: """ :param pattern: the pattern list, as they are represented in c :return: a list of patterns as they are represented in python @@ -403,7 +403,7 @@ def pattern_list_from_pattern_list(pattern: ctypes.POINTER(c_bash.pattern_list)) return pattern_list -def c_pattern_list_from_pattern_list(pattern_list: list[Pattern]) -> ctypes.POINTER(c_bash.pattern_list): +def c_pattern_list_from_pattern_list(pattern_list: list[Pattern]) -> ctypes._Pointer[c_bash.pattern_list]: """ :param pattern_list: the list of patterns, as they are represented in python :return: a pointer to the first pattern in the list, as they are represented in c @@ -504,7 +504,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list]]: """ :return: a dictionary representation of the while command """ @@ -560,7 +560,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: """ :return: a dictionary representation of the if command """ @@ -621,7 +621,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: """ :return: a dictionary representation of the connection """ @@ -742,7 +742,7 @@ def __eq__(self, other: object) -> bool: # return False return True - def _to_json(self) -> dict[str, Union[int, str, dict, None]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: """ :return: a dictionary representation of the function definition """ @@ -797,7 +797,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list]]: """ :return: a dictionary representation of the group command """ @@ -910,7 +910,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list]]: """ :return: a dictionary representation of the arith command """ @@ -975,7 +975,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: """ :return: a dictionary representation of the cond command """ @@ -1103,7 +1103,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list]]: """ :return: a dictionary representation of the subshell command """ @@ -1158,7 +1158,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, Union[int, str, dict]]: + def _to_json(self) -> dict[str, Union[int, str, dict, list]]: """ :return: a dictionary representation of the coproc command """ @@ -1292,7 +1292,7 @@ def __eq__(self, other: object) -> bool: return False return True - def _to_json(self) -> dict[str, dict]: + def _to_json(self) -> dict: """ :return: a dictionary representation of the value union """ From f3b5a836779c0ddf5752d7df3ada482dd6e12479 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Fri, 6 Dec 2024 04:21:59 +0000 Subject: [PATCH 6/8] Format with black Signed-off-by: Bolun Thompson --- README.md | 6 +- libbash/__init__.py | 2 - libbash/api.py | 41 +-- libbash/bash_command/command.py | 437 ++++++++++++++++++-------------- libbash/bash_command/flags.py | 161 ++++++------ libbash/bash_command/util.py | 7 +- libbash/ctypes_bash_command.py | 61 ++--- setup.py | 2 +- test.py | 2 +- 9 files changed, 387 insertions(+), 332 deletions(-) diff --git a/README.md b/README.md index c82750e..4284f42 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **NOTE: This project is mostly functional, however there are a few minor bugs with the Bash source code causing issues with our API. Take a look at `test.py` to see which tests we are currently not testing because they will fail!** -`libbash` can be installed via pip: https://pypi.org/project/libbash/ +`libbash` can be installed via pip: https://pypi.org/project/libbash/ ## API @@ -44,8 +44,8 @@ while [ $counter -le 5 ]; do done ``` -Whether `while` is aliased or not depends on the time of day that the script is run, and this affects the functionality of the `while` loop. This is because alias expansion is done -*before* parsing in Bash. As this example shows, determining alias expansions is not possible without executing a Bash script. Therefore, one can not expect any uses of `alias` or +Whether `while` is aliased or not depends on the time of day that the script is run, and this affects the functionality of the `while` loop. This is because alias expansion is done +*before* parsing in Bash. As this example shows, determining alias expansions is not possible without executing a Bash script. Therefore, one can not expect any uses of `alias` or other programs that change the script before parse-time to be reflected. ## Additional Documents diff --git a/libbash/__init__.py b/libbash/__init__.py index c46d66c..42e5a60 100644 --- a/libbash/__init__.py +++ b/libbash/__init__.py @@ -1,3 +1 @@ from .api import ast_to_json, bash_to_ast, ast_to_bash - - diff --git a/libbash/api.py b/libbash/api.py index 39b2d6b..6461233 100644 --- a/libbash/api.py +++ b/libbash/api.py @@ -7,16 +7,19 @@ from typing import Any # current location + ../../bash-5.2/bash.so -BASH_FILE_PATH = os.path.join(os.path.dirname( - __file__), "bash-5.2", "bash.so") +BASH_FILE_PATH = os.path.join(os.path.dirname(__file__), "bash-5.2", "bash.so") + def _setup_bash() -> ctypes.CDLL: if not os.path.isfile(BASH_FILE_PATH): # run configure and make clean all # this will compile the bash source code into a shared object file # that can be called from python using ctypes - result = os.system("cd " + os.path.dirname(BASH_FILE_PATH) + - " && ./configure && make clean all") + result = os.system( + "cd " + + os.path.dirname(BASH_FILE_PATH) + + " && ./configure && make clean all" + ) if result != 0: raise Exception("Bash compilation failed") @@ -26,8 +29,7 @@ def _setup_bash() -> ctypes.CDLL: try: bash = ctypes.CDLL(BASH_FILE_PATH) except OSError: - raise Exception( - "Bash shared object file not found at path: " + BASH_FILE_PATH) + raise Exception("Bash shared object file not found at path: " + BASH_FILE_PATH) # tell python arg types and return type of the initialize_shell_libbash bash.initialize_shell_libbash.argtypes = [] @@ -60,12 +62,13 @@ def ast_to_bash(ast: list[Command], write_to: str): for comm in ast: command_string = bash.make_command_string(comm._to_ctypes()) bash_str += command_string - bash_str += "\n".encode('utf-8') + bash_str += "\n".encode("utf-8") with open(write_to, "wb") as f: # don't decode the bytes, just write them to the file f.write(bash_str) + def ast_to_json(ast: list[Command]) -> list[dict[str, Any]]: """ Converts the AST to a JSON style object. @@ -75,8 +78,9 @@ def ast_to_json(ast: list[Command]) -> list[dict[str, Any]]: return [command._to_json() for command in ast] -def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ - list[Command] | list[tuple[Command, bytes, int, int]]: +def bash_to_ast( + bash_file: str, with_linno_info: bool = False +) -> list[Command] | list[tuple[Command, bytes, int, int]]: """ Extracts the AST from the bash source code. Uses ctypes to call an injected bash function that returns the AST. @@ -94,7 +98,7 @@ def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ bash.set_bash_file.restype = ctypes.c_int # call the function - set_result: int = bash.set_bash_file(bash_file.encode('utf-8')) + set_result: int = bash.set_bash_file(bash_file.encode("utf-8")) if set_result < 0: raise IOError("Setting bash file failed") @@ -112,22 +116,21 @@ def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ while True: # call the function - linno_before: int = ctypes.c_int.in_dll(bash, 'line_number').value + linno_before: int = ctypes.c_int.in_dll(bash, "line_number").value read_result: ctypes.c_int = bash.read_command_safe() - linno_after: int = ctypes.c_int.in_dll(bash, 'line_number').value + linno_after: int = ctypes.c_int.in_dll(bash, "line_number").value if read_result != 0: bash.unset_bash_input(0) - raise RuntimeError( - "Bash read command failed, shell script may be invalid") + raise RuntimeError("Bash read command failed, shell script may be invalid") # read the global_command variable global_command: ctypes._Pointer[c_bash.command] = ctypes.POINTER( - c_bash.command).in_dll(bash, 'global_command') + c_bash.command + ).in_dll(bash, "global_command") # global_command is null if not global_command: - eof_reached: ctypes.c_int = ctypes.c_int.in_dll( - bash, 'EOF_Reached') + eof_reached: ctypes.c_int = ctypes.c_int.in_dll(bash, "EOF_Reached") if eof_reached: bash.unset_bash_input(0) break @@ -140,9 +143,9 @@ def bash_to_ast(bash_file: str, with_linno_info: bool=False) -> \ # add the command to the list if with_linno_info: - command_string = b''.join(lines[linno_before:linno_after]) + command_string = b"".join(lines[linno_before:linno_after]) command_list.append((command, command_string, linno_before, linno_after)) else: command_list.append(command) - return command_list \ No newline at end of file + return command_list diff --git a/libbash/bash_command/command.py b/libbash/bash_command/command.py index 0ab1b01..35e31d2 100644 --- a/libbash/bash_command/command.py +++ b/libbash/bash_command/command.py @@ -11,7 +11,8 @@ class WordDesc: """ describes a word """ - word: bytes # the word + + word: bytes # the word flags: list[WordDescFlag] def __init__(self, word: c_bash.word_desc): @@ -40,8 +41,8 @@ def _to_json(self) -> dict[str, Union[int, str]]: :return: a dictionary representation of the word description """ return { - 'word': self.word.decode('utf-8', errors='replace'), - 'flags': [x._to_json() for x in self.flags] + "word": self.word.decode("utf-8", errors="replace"), + "flags": [x._to_json() for x in self.flags], } def _to_ctypes(self) -> c_bash.word_desc: @@ -54,7 +55,9 @@ def _to_ctypes(self) -> c_bash.word_desc: return c_word_desc -def word_desc_list_from_word_list(word_list: ctypes._Pointer[c_bash.word_list]) -> list[WordDesc]: +def word_desc_list_from_word_list( + word_list: ctypes._Pointer[c_bash.word_list], +) -> list[WordDesc]: """ :param word_list: the word list :return: a list of word descriptions @@ -66,7 +69,9 @@ def word_desc_list_from_word_list(word_list: ctypes._Pointer[c_bash.word_list]) return word_desc_list -def c_word_list_from_word_desc_list(word_desc_list: list[WordDesc]) -> ctypes._Pointer[c_bash.word_list]: +def c_word_list_from_word_desc_list( + word_desc_list: list[WordDesc], +) -> ctypes._Pointer[c_bash.word_list]: """ :param word_desc_list: the list of word descriptions :return: a pointer to the first word description in the list @@ -74,8 +79,7 @@ def c_word_list_from_word_desc_list(word_desc_list: list[WordDesc]) -> ctypes._P if len(word_desc_list) == 0: return ctypes.POINTER(c_bash.word_list)() c_word_list = c_bash.word_list() - c_word_list.word = ctypes.POINTER(c_bash.word_desc)( - word_desc_list[0]._to_ctypes()) + c_word_list.word = ctypes.POINTER(c_bash.word_desc)(word_desc_list[0]._to_ctypes()) c_word_list.next = c_word_list_from_word_desc_list(word_desc_list[1:]) return ctypes.POINTER(c_bash.word_list)(c_word_list) @@ -84,6 +88,7 @@ class RedirecteeUnion: """ a redirectee, either a file descriptor or a file name """ + # use only if R_DUPLICATING_INPUT or R_DUPLICATING_OUTPUT dest: Optional[int] # use otherwise @@ -115,15 +120,11 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the redirectee union """ if self.dest is not None: - return { - 'dest': self.dest - } + return {"dest": self.dest} elif self.filename is not None: - return { - 'filename': self.filename._to_json() - } + return {"filename": self.filename._to_json()} else: - raise Exception('invalid redirectee') + raise Exception("invalid redirectee") def _to_ctypes(self) -> c_bash.REDIRECTEE: """ @@ -135,7 +136,7 @@ def _to_ctypes(self) -> c_bash.REDIRECTEE: elif self.filename is not None: c_redirectee.filename = self.filename._to_ctypes() else: - raise Exception('invalid redirectee') + raise Exception("invalid redirectee") return c_redirectee @@ -143,6 +144,7 @@ class Redirect: """ describes a redirection such as >, >>, <, << """ + redirector: RedirecteeUnion # the thing being redirected rflags: list[RedirectFlag] # flags for redirection flags: list[OFlag] @@ -157,22 +159,29 @@ def __init__(self, redirect: c_bash.redirect): self.rflags = redirect_flag_list_from_rflags(redirect.rflags) self.flags = oflag_list_from_int(redirect.flags) self.instruction = RInstruction(redirect.instruction) - self.here_doc_eof = redirect.here_doc_eof.decode( - 'utf-8') if redirect.here_doc_eof is not None else None + self.here_doc_eof = ( + redirect.here_doc_eof.decode("utf-8") + if redirect.here_doc_eof is not None + else None + ) if RedirectFlag.REDIR_VARASSIGN in self.rflags: self.redirector = RedirecteeUnion( - None, redirect.redirector.filename.contents) + None, redirect.redirector.filename.contents + ) else: self.redirector = RedirecteeUnion(redirect.redirector.dest, None) - if self.instruction == RInstruction.R_DUPLICATING_INPUT or \ - self.instruction == RInstruction.R_DUPLICATING_OUTPUT or \ - self.instruction == RInstruction.R_CLOSE_THIS or \ - self.instruction == RInstruction.R_MOVE_INPUT or \ - self.instruction == RInstruction.R_MOVE_OUTPUT: + if ( + self.instruction == RInstruction.R_DUPLICATING_INPUT + or self.instruction == RInstruction.R_DUPLICATING_OUTPUT + or self.instruction == RInstruction.R_CLOSE_THIS + or self.instruction == RInstruction.R_MOVE_INPUT + or self.instruction == RInstruction.R_MOVE_OUTPUT + ): self.redirectee = RedirecteeUnion(redirect.redirectee.dest, None) else: self.redirectee = RedirecteeUnion( - None, redirect.redirectee.filename.contents) + None, redirect.redirectee.filename.contents + ) def __eq__(self, other: object) -> bool: """ @@ -201,12 +210,12 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: :return: a dictionary representation of the redirect struct """ return { - 'redirector': self.redirector._to_json(), - 'rflags': [x._to_json() for x in self.rflags], - 'flags': [x._to_json() for x in self.flags], - 'instruction': self.instruction._to_json(), - 'redirectee': self.redirectee._to_json(), - 'here_doc_eof': self.here_doc_eof + "redirector": self.redirector._to_json(), + "rflags": [x._to_json() for x in self.rflags], + "flags": [x._to_json() for x in self.flags], + "instruction": self.instruction._to_json(), + "redirectee": self.redirectee._to_json(), + "here_doc_eof": self.here_doc_eof, } def _to_ctypes(self) -> c_bash.redirect: @@ -218,9 +227,11 @@ def _to_ctypes(self) -> c_bash.redirect: if self.redirector.dest is not None: c_redirect.redirector.dest = self.redirector.dest elif self.redirector.filename is not None: - c_redirect.redirector.filename = ctypes.POINTER(c_bash.word_desc)(self.redirector.filename._to_ctypes()) + c_redirect.redirector.filename = ctypes.POINTER(c_bash.word_desc)( + self.redirector.filename._to_ctypes() + ) else: - raise Exception('invalid redirector') + raise Exception("invalid redirector") c_redirect.rflags = int_from_redirect_flag_list(self.rflags) c_redirect.flags = int_from_oflag_list(self.flags) c_redirect.instruction = self.instruction.value @@ -228,16 +239,20 @@ def _to_ctypes(self) -> c_bash.redirect: if self.redirectee.dest is not None: c_redirect.redirectee.dest = self.redirectee.dest elif self.redirectee.filename is not None: - c_redirect.redirectee.filename = ctypes.POINTER( - c_bash.word_desc)(self.redirectee.filename._to_ctypes()) + c_redirect.redirectee.filename = ctypes.POINTER(c_bash.word_desc)( + self.redirectee.filename._to_ctypes() + ) else: - raise Exception('invalid redirectee') - c_redirect.here_doc_eof = self.here_doc_eof.encode( - 'utf-8') if self.here_doc_eof is not None else None + raise Exception("invalid redirectee") + c_redirect.here_doc_eof = ( + self.here_doc_eof.encode("utf-8") if self.here_doc_eof is not None else None + ) return c_redirect -def redirect_list_from_redirect(redirect: ctypes._Pointer[c_bash.redirect]) -> list[Redirect]: +def redirect_list_from_redirect( + redirect: ctypes._Pointer[c_bash.redirect], +) -> list[Redirect]: """ :param redirect: the redirect list :return: a list of redirects @@ -249,7 +264,9 @@ def redirect_list_from_redirect(redirect: ctypes._Pointer[c_bash.redirect]) -> l return redirect_list -def c_redirect_list_from_redirect_list(redirect_list: list[Redirect]) -> ctypes._Pointer[c_bash.redirect]: +def c_redirect_list_from_redirect_list( + redirect_list: list[Redirect], +) -> ctypes._Pointer[c_bash.redirect]: """ :param redirect_list: the list of redirects :return: a pointer to the first redirect in the list @@ -261,7 +278,9 @@ def c_redirect_list_from_redirect_list(redirect_list: list[Redirect]) -> ctypes. return ctypes.POINTER(c_bash.redirect)(c_redirect) -def c_redirect_from_redirect_list(redirect_list: list[Redirect]) -> ctypes._Pointer[c_bash.redirect]: +def c_redirect_from_redirect_list( + redirect_list: list[Redirect], +) -> ctypes._Pointer[c_bash.redirect]: """ :param redirect_list: the list of redirects :return: a pointer to the first redirect in the list @@ -277,11 +296,12 @@ class ForCom: """ a for command class """ + flags: list[CommandFlag] line: int # line number the command is on? name: WordDesc # the variable name to get mapped over? map_list: list[WordDesc] # the list of words to map over - action: 'Command' # the action to take for each word in the map list + action: "Command" # the action to take for each word in the map list def __init__(self, for_c: c_bash.for_com): """ @@ -317,11 +337,11 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the for command """ return { - 'flags': self.flags, - 'line': self.line, - 'name': self.name._to_json(), - 'map_list': [x._to_json() for x in self.map_list], - 'action': self.action._to_json() + "flags": self.flags, + "line": self.line, + "name": self.name._to_json(), + "map_list": [x._to_json() for x in self.map_list], + "action": self.action._to_json(), } def _to_ctypes(self) -> c_bash.for_com: @@ -333,7 +353,7 @@ def _to_ctypes(self) -> c_bash.for_com: c_for.line = self.line c_for.name = ctypes.POINTER(c_bash.word_desc)(self.name._to_ctypes()) c_for.map_list = c_word_list_from_word_desc_list(self.map_list) - c_for.action = ctypes.POINTER(c_bash.command)(self.action._to_ctypes()) + c_for.action = ctypes.POINTER(c_bash.command)(self.action._to_ctypes()) return c_for @@ -341,8 +361,9 @@ class Pattern: """ represents a pattern in a case command """ + patterns: list[WordDesc] # the list of patterns to match against - action: Optional['Command'] # the action to take if the pattern matches + action: Optional["Command"] # the action to take if the pattern matches flags: list[PatternFlag] def __init__(self, pattern: c_bash.pattern_list): @@ -374,9 +395,9 @@ def _to_json(self) -> dict[str, Union[int, dict, list, None]]: :return: a dictionary representation of the pattern """ return { - 'patterns': [x._to_json() for x in self.patterns], - 'action': self.action._to_json() if self.action is not None else None, - 'flags': self.flags + "patterns": [x._to_json() for x in self.patterns], + "action": self.action._to_json() if self.action is not None else None, + "flags": self.flags, } def _to_ctypes(self) -> c_bash.pattern_list: @@ -385,13 +406,18 @@ def _to_ctypes(self) -> c_bash.pattern_list: """ c_pattern = c_bash.pattern_list() c_pattern.patterns = c_word_list_from_word_desc_list(self.patterns) - c_pattern.action = ctypes.POINTER( - c_bash.command)(self.action._to_ctypes()) if self.action is not None else None + c_pattern.action = ( + ctypes.POINTER(c_bash.command)(self.action._to_ctypes()) + if self.action is not None + else None + ) c_pattern.flags = int_from_pattern_flag_list(self.flags) return c_pattern -def pattern_list_from_pattern_list(pattern: ctypes._Pointer[c_bash.pattern_list]) -> list[Pattern]: +def pattern_list_from_pattern_list( + pattern: ctypes._Pointer[c_bash.pattern_list], +) -> list[Pattern]: """ :param pattern: the pattern list, as they are represented in c :return: a list of patterns as they are represented in python @@ -403,7 +429,9 @@ def pattern_list_from_pattern_list(pattern: ctypes._Pointer[c_bash.pattern_list] return pattern_list -def c_pattern_list_from_pattern_list(pattern_list: list[Pattern]) -> ctypes._Pointer[c_bash.pattern_list]: +def c_pattern_list_from_pattern_list( + pattern_list: list[Pattern], +) -> ctypes._Pointer[c_bash.pattern_list]: """ :param pattern_list: the list of patterns, as they are represented in python :return: a pointer to the first pattern in the list, as they are represented in c @@ -419,6 +447,7 @@ class CaseCom: """ a case command class """ + flags: list[CommandFlag] line: int # line number the command is on? word: WordDesc # the thing to match against @@ -454,10 +483,10 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the case command """ return { - 'flags': self.flags, - 'line': self.line, - 'word': self.word._to_json(), - 'clauses': [x._to_json() for x in self.clauses] + "flags": self.flags, + "line": self.line, + "word": self.word._to_json(), + "clauses": [x._to_json() for x in self.clauses], } def _to_ctypes(self) -> c_bash.case_com: @@ -476,9 +505,10 @@ class WhileCom: """ a while command class """ + flags: list[CommandFlag] - test: 'Command' # the thing to test - action: 'Command' # the action to take while the test is true + test: "Command" # the thing to test + action: "Command" # the action to take while the test is true def __init__(self, while_c: c_bash.while_com): """ @@ -509,9 +539,9 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the while command """ return { - 'flags': self.flags, - 'test': self.test._to_json(), - 'action': self.action._to_json() + "flags": self.flags, + "test": self.test._to_json(), + "action": self.action._to_json(), } def _to_ctypes(self) -> c_bash.while_com: @@ -529,10 +559,11 @@ class IfCom: """ an if command class """ + flags: list[CommandFlag] - test: 'Command' # the thing to test - true_case: 'Command' # the action to take if the test is true - false_case: Optional['Command'] # the action to take if the test is false + test: "Command" # the thing to test + true_case: "Command" # the action to take if the test is true + false_case: Optional["Command"] # the action to take if the test is false def __init__(self, if_c: c_bash.if_com): """ @@ -541,8 +572,7 @@ def __init__(self, if_c: c_bash.if_com): self.flags = command_flag_list_from_int(if_c.flags) self.test = Command(if_c.test.contents) self.true_case = Command(if_c.true_case.contents) - self.false_case = Command( - if_c.false_case.contents) if if_c.false_case else None + self.false_case = Command(if_c.false_case.contents) if if_c.false_case else None def __eq__(self, other: object) -> bool: """ @@ -565,10 +595,12 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: :return: a dictionary representation of the if command """ return { - 'flags': self.flags, - 'test': self.test._to_json(), - 'true_case': self.true_case._to_json(), - 'false_case': self.false_case._to_json() if self.false_case is not None else None + "flags": self.flags, + "test": self.test._to_json(), + "true_case": self.true_case._to_json(), + "false_case": ( + self.false_case._to_json() if self.false_case is not None else None + ), } def _to_ctypes(self) -> c_bash.if_com: @@ -578,10 +610,12 @@ def _to_ctypes(self) -> c_bash.if_com: c_if = c_bash.if_com() c_if.flags = int_from_command_flag_list(self.flags) c_if.test = ctypes.POINTER(c_bash.command)(self.test._to_ctypes()) - c_if.true_case = ctypes.POINTER( - c_bash.command)(self.true_case._to_ctypes()) - c_if.false_case = ctypes.POINTER(c_bash.command)(self.false_case._to_ctypes( - )) if self.false_case is not None else None + c_if.true_case = ctypes.POINTER(c_bash.command)(self.true_case._to_ctypes()) + c_if.false_case = ( + ctypes.POINTER(c_bash.command)(self.false_case._to_ctypes()) + if self.false_case is not None + else None + ) return c_if @@ -589,9 +623,10 @@ class Connection: """ represents connections """ + flags: list[CommandFlag] - first: 'Command' # the first command to run - second: Optional['Command'] # the second command to run + first: "Command" # the first command to run + second: Optional["Command"] # the second command to run connector: ConnectionType # the type of connection def __init__(self, connection: c_bash.connection): @@ -600,8 +635,7 @@ def __init__(self, connection: c_bash.connection): """ self.flags = command_flag_list_from_int(connection.ignore) self.first = Command(connection.first.contents) - self.second = Command( - connection.second.contents) if connection.second else None + self.second = Command(connection.second.contents) if connection.second else None self.connector = ConnectionType(connection.connector) def __eq__(self, other: object) -> bool: @@ -626,10 +660,10 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: :return: a dictionary representation of the connection """ return { - 'flags': [x._to_json() for x in self.flags], - 'first': self.first._to_json(), - 'second': self.second._to_json() if self.second is not None else None, - 'connector': self.connector._to_json() # todo: figure out what this int means + "flags": [x._to_json() for x in self.flags], + "first": self.first._to_json(), + "second": self.second._to_json() if self.second is not None else None, + "connector": self.connector._to_json(), # todo: figure out what this int means } def _to_ctypes(self) -> c_bash.connection: @@ -638,10 +672,12 @@ def _to_ctypes(self) -> c_bash.connection: """ c_connection = c_bash.connection() c_connection.ignore = int_from_command_flag_list(self.flags) - c_connection.first = ctypes.POINTER( - c_bash.command)(self.first._to_ctypes()) - c_connection.second = ctypes.POINTER( - c_bash.command)(self.second._to_ctypes()) if self.second is not None else None + c_connection.first = ctypes.POINTER(c_bash.command)(self.first._to_ctypes()) + c_connection.second = ( + ctypes.POINTER(c_bash.command)(self.second._to_ctypes()) + if self.second is not None + else None + ) c_connection.connector = self.connector.value return c_connection @@ -650,6 +686,7 @@ class SimpleCom: """ a simple command class """ + flags: list[CommandFlag] line: int # line number the command is on words: list[WordDesc] # program name, arguments, variable assignments, etc @@ -684,10 +721,10 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the simple command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'words': [x._to_json() for x in self.words], - 'redirects': [x._to_json() for x in self.redirects] + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "words": [x._to_json() for x in self.words], + "redirects": [x._to_json() for x in self.redirects], } def _to_ctypes(self) -> c_bash.simple_com: @@ -706,10 +743,11 @@ class FunctionDef: """ for function definitions """ + flags: list[CommandFlag] line: int # line number the command is on name: WordDesc # the name of the function - command: 'Command' # the execution tree for the function + command: "Command" # the execution tree for the function source_file: Optional[str] # the file the function was defined in, if any def __init__(self, function: c_bash.function_def): @@ -720,8 +758,9 @@ def __init__(self, function: c_bash.function_def): self.line = function.line self.name = WordDesc(function.name.contents) self.command = Command(function.command.contents) - self.source_file = function.source_file.decode( - 'utf-8') if function.source_file else None + self.source_file = ( + function.source_file.decode("utf-8") if function.source_file else None + ) def __eq__(self, other: object) -> bool: """ @@ -747,11 +786,11 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: :return: a dictionary representation of the function definition """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'name': self.name._to_json(), - 'command': self.command._to_json(), - 'source_file': self.source_file if self.source_file is not None else None + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "name": self.name._to_json(), + "command": self.command._to_json(), + "source_file": self.source_file if self.source_file is not None else None, } def _to_ctypes(self) -> c_bash.function_def: @@ -761,12 +800,11 @@ def _to_ctypes(self) -> c_bash.function_def: c_function = c_bash.function_def() c_function.flags = int_from_command_flag_list(self.flags) c_function.line = self.line - c_function.name = ctypes.POINTER( - c_bash.word_desc)(self.name._to_ctypes()) - c_function.command = ctypes.POINTER( - c_bash.command)(self.command._to_ctypes()) - c_function.source_file = self.source_file.encode( - 'utf-8') if self.source_file is not None else None + c_function.name = ctypes.POINTER(c_bash.word_desc)(self.name._to_ctypes()) + c_function.command = ctypes.POINTER(c_bash.command)(self.command._to_ctypes()) + c_function.source_file = ( + self.source_file.encode("utf-8") if self.source_file is not None else None + ) return c_function @@ -774,8 +812,9 @@ class GroupCom: """ group commands allow pipes and redirections to be applied to a group of commands """ + flags: list[CommandFlag] - command: 'Command' # the command to run + command: "Command" # the command to run def __init__(self, group: c_bash.group_com): """ @@ -802,8 +841,8 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the group command """ return { - 'line': [x._to_json() for x in self.flags], - 'command': self.command._to_json() + "line": [x._to_json() for x in self.flags], + "command": self.command._to_json(), } def _to_ctypes(self) -> c_bash.group_com: @@ -812,8 +851,7 @@ def _to_ctypes(self) -> c_bash.group_com: """ c_group = c_bash.group_com() c_group.ignore = int_from_command_flag_list(self.flags) - c_group.command = ctypes.POINTER( - c_bash.command)(self.command._to_ctypes()) + c_group.command = ctypes.POINTER(c_bash.command)(self.command._to_ctypes()) return c_group @@ -821,11 +859,12 @@ class SelectCom: """ the select command is like a for loop but with a menu """ + flags: list[CommandFlag] line: int # line number the command is on name: WordDesc # the name of the variable? map_list: list[WordDesc] # the list of words to map over - action: 'Command' # the action to take for each word in the map list, during execution name is bound to member of map_list + action: "Command" # the action to take for each word in the map list, during execution name is bound to member of map_list def __init__(self, select: c_bash.select_com): """ @@ -860,11 +899,11 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the select command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'name': self.name._to_json(), - 'map_list': [x._to_json() for x in self.map_list], - 'action': self.action._to_json() + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "name": self.name._to_json(), + "map_list": [x._to_json() for x in self.map_list], + "action": self.action._to_json(), } def _to_ctypes(self) -> c_bash.select_com: @@ -884,6 +923,7 @@ class ArithCom: """ arithmetic expression ((...)) """ + flags: list[CommandFlag] line: int # line number the command is on exp: list[WordDesc] # the expression to evaluate @@ -915,9 +955,9 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the arith command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'exp': [x._to_json() for x in self.exp] + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "exp": [x._to_json() for x in self.exp], } def _to_ctypes(self) -> c_bash.arith_com: @@ -935,12 +975,13 @@ class CondCom: """ conditional expression [[...]] """ + flags: list[CommandFlag] line: int # line number the command is on type: CondTypeEnum # the type of conditional expression op: Optional[WordDesc] # binary tree vibe? - left: Optional['CondCom'] # the left side of the expression - right: Optional['CondCom'] # the right side of the expression + left: Optional["CondCom"] # the left side of the expression + right: Optional["CondCom"] # the right side of the expression def __init__(self, cond: c_bash.cond_com): """ @@ -950,10 +991,8 @@ def __init__(self, cond: c_bash.cond_com): self.line = cond.line self.type = CondTypeEnum(cond.type) self.op = WordDesc(cond.op.contents) if cond.op else None - self.left = CondCom( - cond.left.contents) if cond.left else None - self.right = CondCom( - cond.right.contents) if cond.right else None + self.left = CondCom(cond.left.contents) if cond.left else None + self.right = CondCom(cond.right.contents) if cond.right else None def __eq__(self, other: object) -> bool: """ @@ -980,12 +1019,12 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list, None]]: :return: a dictionary representation of the cond command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'cond_type': self.type._to_json(), - 'op': self.op._to_json() if self.op is not None else None, - 'left': self.left._to_json() if self.left is not None else None, - 'right': self.right._to_json() if self.right is not None else None + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "cond_type": self.type._to_json(), + "op": self.op._to_json() if self.op is not None else None, + "left": self.left._to_json() if self.left is not None else None, + "right": self.right._to_json() if self.right is not None else None, } def _to_ctypes(self) -> c_bash.cond_com: @@ -996,11 +1035,21 @@ def _to_ctypes(self) -> c_bash.cond_com: c_cond.flags = int_from_command_flag_list(self.flags) c_cond.line = self.line c_cond.type = self.type.value - c_cond.op = ctypes.POINTER(c_bash.word_desc)(self.op._to_ctypes()) if self.op is not None else None - c_cond.left = ctypes.POINTER(c_bash.cond_com)( - self.left._to_ctypes()) if self.left is not None else None - c_cond.right = ctypes.POINTER(c_bash.cond_com)( - self.right._to_ctypes()) if self.right is not None else None + c_cond.op = ( + ctypes.POINTER(c_bash.word_desc)(self.op._to_ctypes()) + if self.op is not None + else None + ) + c_cond.left = ( + ctypes.POINTER(c_bash.cond_com)(self.left._to_ctypes()) + if self.left is not None + else None + ) + c_cond.right = ( + ctypes.POINTER(c_bash.cond_com)(self.right._to_ctypes()) + if self.right is not None + else None + ) return c_cond @@ -1008,12 +1057,13 @@ class ArithForCom: """ a c-style for loop ((init; test; step)) action """ + flags: list[CommandFlag] line: int # line number the command is on init: list[WordDesc] # the initial values of the variables test: list[WordDesc] # the test to perform step: list[WordDesc] # the step to take - action: 'Command' # the action to take for each iteration + action: "Command" # the action to take for each iteration def __init__(self, arith_for: c_bash.arith_for_com): """ @@ -1051,12 +1101,12 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the arith_for command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'init': [x._to_json() for x in self.init], - 'test': [x._to_json() for x in self.test], - 'step': [x._to_json() for x in self.step], - 'action': self.action._to_json() + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "init": [x._to_json() for x in self.init], + "test": [x._to_json() for x in self.test], + "step": [x._to_json() for x in self.step], + "action": self.action._to_json(), } def _to_ctypes(self) -> c_bash.arith_for_com: @@ -1069,8 +1119,7 @@ def _to_ctypes(self) -> c_bash.arith_for_com: c_arith_for.init = c_word_list_from_word_desc_list(self.init) c_arith_for.test = c_word_list_from_word_desc_list(self.test) c_arith_for.step = c_word_list_from_word_desc_list(self.step) - c_arith_for.action = ctypes.POINTER( - c_bash.command)(self.action._to_ctypes()) + c_arith_for.action = ctypes.POINTER(c_bash.command)(self.action._to_ctypes()) return c_arith_for @@ -1078,9 +1127,10 @@ class SubshellCom: """ a subshell command """ + flags: list[CommandFlag] # unclear flag type line: int # line number the command is on - command: 'Command' # the command to run in the subshell + command: "Command" # the command to run in the subshell def __init__(self, subshell: c_bash.subshell_com): """ @@ -1108,9 +1158,9 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the subshell command """ return { - 'flags': [x._to_json() for x in self.flags], - 'line': self.line, - 'command': self.command._to_json() + "flags": [x._to_json() for x in self.flags], + "line": self.line, + "command": self.command._to_json(), } def _to_ctypes(self) -> c_bash.subshell_com: @@ -1120,8 +1170,7 @@ def _to_ctypes(self) -> c_bash.subshell_com: c_subshell = c_bash.subshell_com() c_subshell.flags = int_from_command_flag_list(self.flags) c_subshell.line = self.line - c_subshell.command = ctypes.POINTER( - c_bash.command)(self.command._to_ctypes()) + c_subshell.command = ctypes.POINTER(c_bash.command)(self.command._to_ctypes()) return c_subshell @@ -1129,9 +1178,10 @@ class CoprocCom: """ a coprocess command """ + flags: list[CommandFlag] # unclear flag type name: str # the name of the coprocess - command: 'Command' # the command to run in the coprocess + command: "Command" # the command to run in the coprocess def __init__(self, coproc: c_bash.coproc_com): """ @@ -1139,7 +1189,7 @@ def __init__(self, coproc: c_bash.coproc_com): """ self.flags = command_flag_list_from_int(coproc.flags) # c_char_p is a bytes object so we need to decode it - self.name = coproc.name.decode('utf-8') + self.name = coproc.name.decode("utf-8") self.command = Command(coproc.command.contents) def __eq__(self, other: object) -> bool: @@ -1163,9 +1213,9 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the coproc command """ return { - 'flags': [x._to_json() for x in self.flags], - 'name': self.name, - 'command': self.command._to_json() + "flags": [x._to_json() for x in self.flags], + "name": self.name, + "command": self.command._to_json(), } def _to_ctypes(self) -> c_bash.coproc_com: @@ -1174,7 +1224,7 @@ def _to_ctypes(self) -> c_bash.coproc_com: """ c_coproc = c_bash.coproc_com() c_coproc.flags = int_from_command_flag_list(self.flags) - c_coproc.name = self.name.encode('utf-8') + c_coproc.name = self.name.encode("utf-8") c_coproc.command = ctypes.POINTER(c_bash.command)(self.command._to_ctypes()) return c_coproc @@ -1184,6 +1234,7 @@ class ValueUnion: a union of all the possible command types exactly one of these will be non-null """ + for_com: Optional[ForCom] case_com: Optional[CaseCom] while_com: Optional[WhileCom] @@ -1199,10 +1250,7 @@ class ValueUnion: subshell_com: Optional[SubshellCom] coproc_com: Optional[CoprocCom] - def __init__( - self, - command_type: CommandType, - value: c_bash.value): + def __init__(self, command_type: CommandType, value: c_bash.value): """ :param command_type: the type of command :param value: the value union struct @@ -1253,7 +1301,7 @@ def __init__( elif command_type == CommandType.CM_COPROC: self.coproc_com = CoprocCom(value.Coproc.contents) else: - raise Exception('Unknown command type provided.') + raise Exception("Unknown command type provided.") def __eq__(self, other: object) -> bool: """ @@ -1325,7 +1373,7 @@ def _to_json(self) -> dict: elif self.coproc_com is not None: return self.coproc_com._to_json() else: - raise Exception('invalid value union') + raise Exception("invalid value union") def _to_ctypes(self) -> c_bash.value: """ @@ -1333,49 +1381,55 @@ def _to_ctypes(self) -> c_bash.value: """ c_value = c_bash.value() if self.for_com is not None: - c_value.For = ctypes.POINTER(c_bash.for_com)( - self.for_com._to_ctypes()) + c_value.For = ctypes.POINTER(c_bash.for_com)(self.for_com._to_ctypes()) elif self.case_com is not None: - c_value.Case = ctypes.POINTER(c_bash.case_com)( - self.case_com._to_ctypes()) + c_value.Case = ctypes.POINTER(c_bash.case_com)(self.case_com._to_ctypes()) elif self.while_com is not None: c_value.While = ctypes.POINTER(c_bash.while_com)( - self.while_com._to_ctypes()) + self.while_com._to_ctypes() + ) elif self.if_com is not None: - c_value.If = ctypes.POINTER(c_bash.if_com)( - self.if_com._to_ctypes()) + c_value.If = ctypes.POINTER(c_bash.if_com)(self.if_com._to_ctypes()) elif self.connection is not None: - c_value.Connection = ctypes.POINTER( - c_bash.connection)(self.connection._to_ctypes()) + c_value.Connection = ctypes.POINTER(c_bash.connection)( + self.connection._to_ctypes() + ) elif self.simple_com is not None: c_value.Simple = ctypes.POINTER(c_bash.simple_com)( - self.simple_com._to_ctypes()) + self.simple_com._to_ctypes() + ) elif self.function_def is not None: - c_value.Function_def = ctypes.POINTER( - c_bash.function_def)(self.function_def._to_ctypes()) + c_value.Function_def = ctypes.POINTER(c_bash.function_def)( + self.function_def._to_ctypes() + ) elif self.group_com is not None: c_value.Group = ctypes.POINTER(c_bash.group_com)( - self.group_com._to_ctypes()) + self.group_com._to_ctypes() + ) elif self.select_com is not None: c_value.Select = ctypes.POINTER(c_bash.select_com)( - self.select_com._to_ctypes()) + self.select_com._to_ctypes() + ) elif self.arith_com is not None: - c_value.Arith = ctypes.POINTER( - c_bash.arith_com)(self.arith_com._to_ctypes()) + c_value.Arith = ctypes.POINTER(c_bash.arith_com)( + self.arith_com._to_ctypes() + ) elif self.cond_com is not None: - c_value.Cond = ctypes.POINTER(c_bash.cond_com)( - self.cond_com._to_ctypes()) + c_value.Cond = ctypes.POINTER(c_bash.cond_com)(self.cond_com._to_ctypes()) elif self.arith_for_com is not None: c_value.ArithFor = ctypes.POINTER(c_bash.arith_for_com)( - self.arith_for_com._to_ctypes()) + self.arith_for_com._to_ctypes() + ) elif self.subshell_com is not None: c_value.Subshell = ctypes.POINTER(c_bash.subshell_com)( - self.subshell_com._to_ctypes()) + self.subshell_com._to_ctypes() + ) elif self.coproc_com is not None: c_value.Coproc = ctypes.POINTER(c_bash.coproc_com)( - self.coproc_com._to_ctypes()) + self.coproc_com._to_ctypes() + ) else: - raise Exception('invalid value union') + raise Exception("invalid value union") return c_value @@ -1384,6 +1438,7 @@ class Command: a mirror of the bash command struct defined here: https://git.savannah.gnu.org/cgit/bash.git/tree/command.h """ + type: CommandType # command type flags: list[CommandFlag] # command flags # line: int # line number the command is on - seems to be unused @@ -1423,11 +1478,11 @@ def _to_json(self) -> dict[str, Union[int, str, dict, list]]: :return: a dictionary representation of the command """ return { - 'type': self.type._to_json(), - 'flags': self.flags, + "type": self.type._to_json(), + "flags": self.flags, # 'line': self.line, - 'redirects': [x._to_json() for x in self.redirects], - 'value': self.value._to_json() + "redirects": [x._to_json() for x in self.redirects], + "value": self.value._to_json(), } def _to_ctypes(self) -> c_bash.command: diff --git a/libbash/bash_command/flags.py b/libbash/bash_command/flags.py index f490163..1c7da6c 100644 --- a/libbash/bash_command/flags.py +++ b/libbash/bash_command/flags.py @@ -7,6 +7,7 @@ class OFlag(Enum): """ represents open flags present in the OpenFlag class """ + O_RDONLY = 0 O_WRONLY = 1 << 0 O_RDWR = 1 << 1 @@ -31,19 +32,19 @@ def _to_json(self) -> str: :return: the string representation of the open flag """ if self == OFlag.O_RDONLY: - return 'read_only' + return "read_only" elif self == OFlag.O_WRONLY: - return 'write_only' + return "write_only" elif self == OFlag.O_RDWR: - return 'read_write' + return "read_write" elif self == OFlag.O_APPEND: - return 'append' + return "append" elif self == OFlag.O_CREAT: - return 'create' + return "create" elif self == OFlag.O_TRUNC: - return 'truncate' + return "truncate" else: - raise Exception('invalid open flag') + raise Exception("invalid open flag") def oflag_list_from_int(oflag_int: int) -> list[OFlag]: @@ -73,38 +74,39 @@ class WordDescFlag(Enum): """ represents word description flags present in the WordDesc class """ - W_HASDOLLAR = 1 << 0 # dollar sign present - W_QUOTED = 1 << 1 # some form of quote character is present - W_ASSIGNMENT = 1 << 2 # this word is a variable assignment - W_SPLITSPACE = 1 << 3 # split this word on " " regardless of IFS + + W_HASDOLLAR = 1 << 0 # dollar sign present + W_QUOTED = 1 << 1 # some form of quote character is present + W_ASSIGNMENT = 1 << 2 # this word is a variable assignment + W_SPLITSPACE = 1 << 3 # split this word on " " regardless of IFS # do not perform word splitting on this word because IFS is empty string W_NOSPLIT = 1 << 4 - W_NOGLOB = 1 << 5 # do not perform globbing on this word + W_NOGLOB = 1 << 5 # do not perform globbing on this word # don't split word except for $@ expansion (using spaces) because context does not allow it W_NOSPLIT2 = 1 << 6 - W_TILDEEXP = 1 << 7 # tilde expand this assignment word - W_DOLLARAT = 1 << 8 # UNUSED - $@ and its special handling - W_ARRAYREF = 1 << 9 # word is a valid array reference - W_NOCOMSUB = 1 << 10 # don't perform command substitution on this word - W_ASSIGNRHS = 1 << 11 # word is rhs of an assignment statement - W_NOTILDE = 1 << 12 # don't perform tilde expansion on this word - W_NOASSNTILDE = 1 << 13 # don't do tilde expansion like an assignment statement - W_EXPANDRHS = 1 << 14 # expanding word in ${paramOPword} - W_COMPASSIGN = 1 << 15 # compound assignment - W_ASSNBLTIN = 1 << 16 # word is a builtin command that takes assignments - W_ASSIGNARG = 1 << 17 # word is assignment argument to command - W_HASQUOTEDNULL = 1 << 18 # word contains a quoted null character - W_DQUOTE = 1 << 19 # UNUSED - word should be treated as if double-quoted - W_NOPROCSUB = 1 << 20 # don't perform process substitution - W_SAWQUOTEDNULL = 1 << 21 # word contained a quoted null that was removed - W_ASSIGNASSOC = 1 << 22 # word looks like associative array assignment - W_ASSIGNARRAY = 1 << 23 # word looks like a compound indexed array assignment - W_ARRAYIND = 1 << 24 # word is an array index being expanded + W_TILDEEXP = 1 << 7 # tilde expand this assignment word + W_DOLLARAT = 1 << 8 # UNUSED - $@ and its special handling + W_ARRAYREF = 1 << 9 # word is a valid array reference + W_NOCOMSUB = 1 << 10 # don't perform command substitution on this word + W_ASSIGNRHS = 1 << 11 # word is rhs of an assignment statement + W_NOTILDE = 1 << 12 # don't perform tilde expansion on this word + W_NOASSNTILDE = 1 << 13 # don't do tilde expansion like an assignment statement + W_EXPANDRHS = 1 << 14 # expanding word in ${paramOPword} + W_COMPASSIGN = 1 << 15 # compound assignment + W_ASSNBLTIN = 1 << 16 # word is a builtin command that takes assignments + W_ASSIGNARG = 1 << 17 # word is assignment argument to command + W_HASQUOTEDNULL = 1 << 18 # word contains a quoted null character + W_DQUOTE = 1 << 19 # UNUSED - word should be treated as if double-quoted + W_NOPROCSUB = 1 << 20 # don't perform process substitution + W_SAWQUOTEDNULL = 1 << 21 # word contained a quoted null that was removed + W_ASSIGNASSOC = 1 << 22 # word looks like associative array assignment + W_ASSIGNARRAY = 1 << 23 # word looks like a compound indexed array assignment + W_ARRAYIND = 1 << 24 # word is an array index being expanded # word is a global assignment to declare (declare/typeset -g) W_ASSNGLOBAL = 1 << 25 - W_NOBRACE = 1 << 26 # don't perform brace expansion - W_COMPLETE = 1 << 27 # word is being expanded for completion - W_CHKLOCAL = 1 << 28 # check for local vars on assignment + W_NOBRACE = 1 << 26 # don't perform brace expansion + W_COMPLETE = 1 << 27 # word is being expanded for completion + W_CHKLOCAL = 1 << 28 # check for local vars on assignment # force assignments to be to local variables, non-fatal on assignment errors W_FORCELOCAL = 1 << 29 @@ -185,7 +187,7 @@ def _to_json(self) -> str: elif self == WordDescFlag.W_FORCELOCAL: return "force_local" else: - raise Exception('invalid word description flag') + raise Exception("invalid word description flag") def word_desc_flag_list_from_int(flag_int: int) -> list[WordDescFlag]: @@ -215,6 +217,7 @@ class CommandFlag(Enum): """ represents command flags present in several command types """ + CMD_WANT_SUBSHELL = 1 << 0 # user wants subshell CMD_FORCE_SUBSHELL = 1 << 1 # shell needs to force subshell CMD_INVERT_RETURN = 1 << 2 # invert the exit value @@ -281,7 +284,7 @@ def _to_json(self) -> str: elif self == CommandFlag.CMD_TRY_OPTIMIZING: return "try_optimizing" else: - raise Exception('invalid command flag') + raise Exception("invalid command flag") def command_flag_list_from_int(flag_int: int) -> list[CommandFlag]: @@ -311,6 +314,7 @@ class CommandType(Enum): """ a command type enum """ + CM_FOR = 0 # for loop CM_CASE = 1 # switch case CM_WHILE = 2 # while loop @@ -374,13 +378,14 @@ def _to_json(self) -> str: elif self == CommandType.CM_COPROC: return "coproc" else: - raise Exception('invalid command type') + raise Exception("invalid command type") class RInstruction(Enum): """ a redirection instruction enum """ + R_OUTPUT_DIRECTION = 0 # >foo R_INPUT_DIRECTION = 1 # str: :return: the string representation of the redirection type """ if self == RInstruction.R_OUTPUT_DIRECTION: - return '>' + return ">" elif self == RInstruction.R_INPUT_DIRECTION: - return '<' + return "<" elif self == RInstruction.R_INPUTA_DIRECTION: - return '&' # ? + return "&" # ? elif self == RInstruction.R_APPENDING_TO: - return '>>' + return ">>" elif self == RInstruction.R_READING_UNTIL: - return '<<' + return "<<" elif self == RInstruction.R_READING_STRING: - return '<<<' + return "<<<" elif self == RInstruction.R_DUPLICATING_INPUT: - return '<&' + return "<&" elif self == RInstruction.R_DUPLICATING_OUTPUT: - return '>&' + return ">&" elif self == RInstruction.R_DEBLANK_READING_UNTIL: - return '<<-' + return "<<-" elif self == RInstruction.R_CLOSE_THIS: - return '<&-' + return "<&-" elif self == RInstruction.R_ERR_AND_OUT: - return '&>' + return "&>" elif self == RInstruction.R_INPUT_OUTPUT: - return '<>' + return "<>" elif self == RInstruction.R_OUTPUT_FORCE: - return '>|' + return ">|" elif self == RInstruction.R_DUPLICATING_INPUT_WORD: - return '<&$' # todo figure out if $ is needed + return "<&$" # todo figure out if $ is needed elif self == RInstruction.R_DUPLICATING_OUTPUT_WORD: - return '>&$' # todo figure out if $ is needed + return ">&$" # todo figure out if $ is needed elif self == RInstruction.R_MOVE_INPUT: - return '<&-' + return "<&-" elif self == RInstruction.R_MOVE_OUTPUT: - return '>&-' + return ">&-" elif self == RInstruction.R_MOVE_INPUT_WORD: - return '<&$-' # todo figure out if $ is needed + return "<&$-" # todo figure out if $ is needed elif self == RInstruction.R_MOVE_OUTPUT_WORD: - return '>&$-' # todo figure out if $ is needed + return ">&$-" # todo figure out if $ is needed elif self == RInstruction.R_APPEND_ERR_AND_OUT: - return '&>>' + return "&>>" else: - raise Exception('invalid redirect instruction') + raise Exception("invalid redirect instruction") class CondTypeEnum(Enum): """ a conditional expression type enum """ + COND_AND = 1 COND_OR = 2 COND_UNARY = 3 @@ -486,19 +492,19 @@ def _to_json(self) -> str: :return: the string representation of the conditional expression type """ if self == CondTypeEnum.COND_AND: - return 'and' + return "and" elif self == CondTypeEnum.COND_OR: - return 'or' + return "or" elif self == CondTypeEnum.COND_UNARY: - return 'unary' + return "unary" elif self == CondTypeEnum.COND_BINARY: - return 'binary' + return "binary" elif self == CondTypeEnum.COND_TERM: - return 'term' + return "term" elif self == CondTypeEnum.COND_EXPR: - return 'expression' + return "expression" else: - raise Exception('invalid conditional expression type') + raise Exception("invalid conditional expression type") class ConnectionType(Enum): @@ -507,6 +513,7 @@ class ConnectionType(Enum): in the bash source code for more information, pretty funny approach to this """ + AMPERSAND = 38 SEMICOLON = 59 NEWLINE = 10 @@ -531,25 +538,26 @@ def _to_json(self) -> str: :return: the string representation of the connection type """ if self == ConnectionType.AMPERSAND: - return '&' + return "&" elif self == ConnectionType.SEMICOLON: - return ';' + return ";" elif self == ConnectionType.NEWLINE: - return '\n' + return "\n" elif self == ConnectionType.PIPE: - return '|' + return "|" elif self == ConnectionType.AND_AND: - return '&&' + return "&&" elif self == ConnectionType.OR_OR: - return '||' + return "||" else: - raise Exception('invalid connection type') + raise Exception("invalid connection type") class RedirectFlag(Enum): """ a redirect flag enum """ + REDIR_VARASSIGN = 1 << 0 def __eq__(self, other: object) -> bool: @@ -565,9 +573,9 @@ def _to_json(self) -> str: :return: the string representation of the redirect flag """ if self == RedirectFlag.REDIR_VARASSIGN: - return 'var_assign' + return "var_assign" else: - raise Exception('invalid redirect flag') + raise Exception("invalid redirect flag") def redirect_flag_list_from_rflags(rflags: int) -> list[RedirectFlag]: @@ -596,6 +604,7 @@ class PatternFlag(Enum): """ a pattern flag enum, present in the CasePattern class """ + CASEPAT_FALLTHROUGH = 1 << 0 # fall through to next pattern CASEPAT_TESTNEXT = 1 << 1 # test next pattern @@ -615,11 +624,11 @@ def _to_json(self) -> str: :return: the string representation of the pattern flag """ if self == PatternFlag.CASEPAT_FALLTHROUGH: - return 'fallthrough' + return "fallthrough" elif self == PatternFlag.CASEPAT_TESTNEXT: - return 'test_next' + return "test_next" else: - raise Exception('invalid pattern flag') + raise Exception("invalid pattern flag") def pattern_flag_list_from_int(flag_int: int) -> list[PatternFlag]: diff --git a/libbash/bash_command/util.py b/libbash/bash_command/util.py index b0cf642..ced455a 100644 --- a/libbash/bash_command/util.py +++ b/libbash/bash_command/util.py @@ -1,4 +1,3 @@ - def list_same_elements(l1: list, l2: list) -> bool: """ Checks if two lists are equal (order doesn't matter) @@ -11,7 +10,7 @@ def list_same_elements(l1: list, l2: list) -> bool: if len(l1) != len(l2): return False - + for i in l1: found_match = False for j in l2: @@ -21,5 +20,5 @@ def list_same_elements(l1: list, l2: list) -> bool: break if not found_match: return False - - return True \ No newline at end of file + + return True diff --git a/libbash/ctypes_bash_command.py b/libbash/ctypes_bash_command.py index 8a16847..8363523 100644 --- a/libbash/ctypes_bash_command.py +++ b/libbash/ctypes_bash_command.py @@ -5,20 +5,16 @@ class word_desc(Structure): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n131 """ - _fields_ = [ - ("word", c_char_p), - ("flags", c_int) - ] + + _fields_ = [("word", c_char_p), ("flags", c_int)] class REDIRECTEE(Union): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n154 """ - _fields_ = [ - ("dest", c_int), - ("filename", POINTER(word_desc)) - ] + + _fields_ = [("dest", c_int), ("filename", POINTER(word_desc))] class redirect(Structure): @@ -35,7 +31,7 @@ class redirect(Structure): ("flags", c_int), ("instruction", c_int), ("redirectee", REDIRECTEE), - ("here_doc_eof", c_char_p) + ("here_doc_eof", c_char_p), ] @@ -46,10 +42,7 @@ class word_list(Structure): # we do this because we need to reference word_list in word_list -word_list._fields_ = [ - ("next", POINTER(word_list)), - ("word", POINTER(word_desc)) -] +word_list._fields_ = [("next", POINTER(word_list)), ("word", POINTER(word_desc))] class for_com(Structure): @@ -68,11 +61,12 @@ class case_com(Structure): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n251 """ + _fields_ = [ ("flags", c_int), ("line", c_int), ("word", POINTER(word_desc)), - ("clauses", POINTER(pattern_list)) + ("clauses", POINTER(pattern_list)), ] @@ -116,11 +110,8 @@ class arith_com(Structure): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n364 """ - _fields_ = [ - ("flags", c_int), - ("line", c_int), - ("exp", POINTER(word_list)) - ] + + _fields_ = [("flags", c_int), ("line", c_int), ("exp", POINTER(word_list))] class cond_com(Structure): @@ -162,11 +153,12 @@ class simple_com(Structure): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n337 """ + _fields_ = [ ("flags", c_int), ("line", c_int), ("words", POINTER(word_list)), - ("redirects", POINTER(redirect)) + ("redirects", POINTER(redirect)), ] @@ -174,6 +166,7 @@ class value(Union): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n202 """ + _fields_ = [ ("For", POINTER(for_com)), ("Case", POINTER(case_com)), @@ -196,12 +189,13 @@ class command(Structure): """ https://git.savannah.gnu.org/cgit/bash.git/tree/command.h#n197 """ + _fields_ = [ ("type", c_int), ("flags", c_int), ("line", c_int), ("redirects", POINTER(redirect)), - ("value", value) + ("value", value), ] @@ -211,7 +205,7 @@ class command(Structure): ("line", c_int), ("name", POINTER(word_desc)), ("map_list", POINTER(word_list)), - ("action", POINTER(command)) + ("action", POINTER(command)), ] # we do this because we need to reference pattern_list and command in pattern_list @@ -219,14 +213,14 @@ class command(Structure): ("next", POINTER(pattern_list)), ("patterns", POINTER(word_list)), ("action", POINTER(command)), - ("flags", c_int) + ("flags", c_int), ] # we do this because we need to reference command in while_com while_com._fields_ = [ ("flags", c_int), ("test", POINTER(command)), - ("action", POINTER(command)) + ("action", POINTER(command)), ] # we do this because we need to reference command in if_com @@ -234,7 +228,7 @@ class command(Structure): ("flags", c_int), ("test", POINTER(command)), ("true_case", POINTER(command)), - ("false_case", POINTER(command)) + ("false_case", POINTER(command)), ] # we do this because we need to reference command in connection @@ -242,15 +236,12 @@ class command(Structure): ("ignore", c_int), ("first", POINTER(command)), ("second", POINTER(command)), - ("connector", c_int) + ("connector", c_int), ] # we do this because we need to reference command in group_com -group_com._fields_ = [ - ("ignore", c_int), - ("command", POINTER(command)) -] +group_com._fields_ = [("ignore", c_int), ("command", POINTER(command))] # we do this because we need to reference command in function_def function_def._fields_ = [ @@ -258,7 +249,7 @@ class command(Structure): ("line", c_int), ("name", POINTER(word_desc)), ("command", POINTER(command)), - ("source_file", c_char_p) + ("source_file", c_char_p), ] # we do this because we need to reference command in select_com @@ -267,7 +258,7 @@ class command(Structure): ("line", c_int), ("name", POINTER(word_desc)), ("map_list", POINTER(word_list)), - ("action", POINTER(command)) + ("action", POINTER(command)), ] # we do this because we need to reference command in arith_for_com @@ -277,19 +268,19 @@ class command(Structure): ("init", POINTER(word_list)), ("test", POINTER(word_list)), ("step", POINTER(word_list)), - ("action", POINTER(command)) + ("action", POINTER(command)), ] # we do this because we need to reference command in subshell_com subshell_com._fields_ = [ ("flags", c_int), ("line", c_int), - ("command", POINTER(command)) + ("command", POINTER(command)), ] # we do this because we need to reference command in coproc_com coproc_com._fields_ = [ ("flags", c_int), ("name", c_char_p), - ("command", POINTER(command)) + ("command", POINTER(command)), ] diff --git a/setup.py b/setup.py index 7c7a3e1..ec025ae 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ def run(self): with enter_dir('libbash/bash-5.2'): try_exec('./configure') try_exec('make', 'clean', 'all') - + shutil.copy2('libbash/bash-5.2/bash.so', os.path.join(self.build_lib, 'libbash/bash-5.2/bash.so')) diff --git a/test.py b/test.py index e4bfac2..f49c653 100755 --- a/test.py +++ b/test.py @@ -115,7 +115,7 @@ def read_from_file(file: str) -> bytes: def test_bash_and_ast_consistency(): """ - This test runs bash_to_ast and ast_to_bash on every test file in the bash-5.2/tests directory + This test runs bash_to_ast and ast_to_bash on every test file in the bash-5.2/tests directory back and forth NUM_ITERATIONS times. On each iteration it makes sure that the AST is the same as the previous iteration. It also makes sure that the bash file is the same as the previous iteration excluding the first iteration. Finally if getting the AST fails, it will make sure that it fails consistently. From c574a0ef83ab5cdd6fe473c98aab39dccf089194 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Thu, 12 Dec 2024 04:45:30 +0000 Subject: [PATCH 7/8] Remove editable --- setup_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup_test.sh b/setup_test.sh index bebe749..f846b42 100755 --- a/setup_test.sh +++ b/setup_test.sh @@ -2,4 +2,4 @@ git submodule update --init --recursive -pip install -e . +pip install . From e887f85f52baa5a7bb48f937c8fd8c7d9bc124d9 Mon Sep 17 00:00:00 2001 From: Bolun Thompson Date: Tue, 17 Dec 2024 04:16:42 +0000 Subject: [PATCH 8/8] Increment version Signed-off-by: Bolun Thompson --- pyproject.toml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index de7e076..09fadc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "libbash" -version = "0.1.13" +version = "0.1.14" authors = [ { name="Seth Sabar", email="sethsabar@gmail.com" }, ] diff --git a/setup.py b/setup.py index ec025ae..3e46e36 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ def run(self): cmdclass={'build_py': build_libbash}, package_data={'': ['libbash/bash-5.2']}, include_package_data=True, - version='0.1.13', + version='0.1.14', description="A Python library for parsing Bash scripts into an AST", long_description=open('README.md').read(), long_description_content_type='text/markdown',