Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3.8 backport and minor code cleanup #1

Merged
merged 7 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -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<<EOF" >> "$GITHUB_ENV"
echo "OS:${{matrix.os}}" >> "$GITHUB_ENV"
echo "$timer" >> "$GITHUB_ENV"
cat python.log >> "$GITHUB_ENV"
echo 'EOF' >> "$GITHUB_ENV"

exit $test_succ
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
3 changes: 0 additions & 3 deletions libbash/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
from .api import ast_to_json, bash_to_ast, ast_to_bash
from .test import run_tests


49 changes: 28 additions & 21 deletions libbash/api.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
from __future__ import annotations

from .bash_command import *
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")
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")

Expand All @@ -22,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 = []
Expand Down Expand Up @@ -56,13 +62,14 @@ 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]]:

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.
Expand All @@ -71,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[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.
Expand All @@ -90,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: 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")

Expand All @@ -108,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')
global_command: ctypes._Pointer[c_bash.command] = ctypes.POINTER(
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
Expand All @@ -136,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
return command_list
2 changes: 1 addition & 1 deletion libbash/bash-5.2
Loading