Skip to content

Commit

Permalink
Skeleton of python implementation (#248)
Browse files Browse the repository at this point in the history
  • Loading branch information
nedtwigg authored Feb 27, 2024
2 parents d3a6d5c + 780befb commit f90bfaf
Show file tree
Hide file tree
Showing 11 changed files with 324 additions and 1 deletion.
32 changes: 32 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
on:
push:
branches: [main]
pull_request:
paths:
- 'python/**'
defaults:
run:
working-directory: python/selfie-lib
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v4
- run: pipx install poetry
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version-file: 'python/selfie-lib/pyproject.toml'
cache: 'poetry'
- run: poetry install
- run: poetry run pytest -vv
- run: poetry run pyright
- run: poetry run ruff check
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
.gradle/
build/
bin/
.DS_Store
.DS_Store
__pycache__/
1 change: 1 addition & 0 deletions python/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
8 changes: 8 additions & 0 deletions python/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"python.testing.pytestArgs": [
"selfie-lib",
"-vv"
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
16 changes: 16 additions & 0 deletions python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
The python implementation is under construction. It makes use of PEP 695, so you must use Python 3.12 or later.

Dependencies are managed using poetry, which you can install here.
- https://python-poetry.org/docs/#installing-with-the-official-installer
- then cd into `selfie-lib` and run `poetry install`

Our CI server runs three checks in the `selfie-lib` directory.

- `poetry run pytest -vv` this runs the tests (`-vv` makes nice output)
- `poetry run pyright` this does type checking
- `poetry run ruff check` this checks formatting

For the IDE we use VSCode. Make sure to open the `python` directory, not the parent `selfie`. Receommended VSCode plugins:

- https://marketplace.visualstudio.com/items?itemName=ms-python.python
- https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff
148 changes: 148 additions & 0 deletions python/selfie-lib/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions python/selfie-lib/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[tool.poetry]
name = "selfie-lib"
version = "0.1.0"
description = "Infrastructure for creating selfie-compatible test runner plugins."
authors = ["Ned Twigg <[email protected]>"]
license = "Apache-2.0"
readme = "../README.md"

[tool.poetry.dependencies]
python = "^3.12"

[tool.poetry.group.dev.dependencies]
ruff = "^0.2.1"
pyright = "^1.1.350"
pytest = "^8.0.0"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
84 changes: 84 additions & 0 deletions python/selfie-lib/selfie_lib/Slice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
from typing import Optional
from typing import Union
from collections import Counter

class Slice:
"""Represents a slice of a base string from startIndex to endIndex."""

def __init__(self, base: str, startIndex: int = 0, endIndex: Optional[int] = None) -> None:
self.base = base
self.base = base
self.startIndex = startIndex
self.endIndex = endIndex if endIndex is not None else len(base)

assert 0 <= self.startIndex <= self.endIndex <= len(base), "Invalid start or end index"

def __len__(self) -> int:
return self.endIndex - self.startIndex

def __getitem__(self, index: int) -> str:
if not (0 <= index < len(self)):
raise IndexError("Index out of range")
return self.base[self.startIndex + index]

def subSequence(self, start: int, end: int) -> 'Slice':
return Slice(self.base, self.startIndex + start, self.startIndex + end)

def trim(self) -> 'Slice':
start, end = 0, len(self)
while start < end and self[start].isspace():
start += 1
while start < end and self[end - 1].isspace():
end -= 1
return self.subSequence(start, end) if start > 0 or end < len(self) else self

def __str__(self) -> str:
return self.base[self.startIndex:self.endIndex]

def sameAs(self, other: Union['Slice', str]) -> bool:
if isinstance(other, Slice):
return str(self) == str(other)
elif isinstance(other, str):
if len(self) != len(other):
return False
for i in range(len(self)):
if self[i] != other[i]:
return False
return True
return False

def indexOf(self, lookingFor: str, startOffset: int = 0) -> int:
result = self.base.find(lookingFor, self.startIndex + startOffset, self.endIndex)
return -1 if result == -1 else result - self.startIndex

def unixLine(self, count: int) -> 'Slice':
assert count > 0, "Count must be positive"
lineStart = 0
for i in range(1, count):
lineStart = self.indexOf('\n', lineStart)
assert lineStart >= 0, f"This string has only {i - 1} lines, not {count}"
lineStart += 1
lineEnd = self.indexOf('\n', lineStart)
return Slice(self.base, self.startIndex + lineStart, self.endIndex if lineEnd == -1 else self.startIndex + lineEnd)

def __eq__(self, other: object) -> bool:
if self is other:
return True
if isinstance(other, Slice):
return self.sameAs(other)
return False

def __hash__(self) -> int:
h = 0
for i in range(len(self)):
h = 31 * h + ord(self[i])
return h

def replaceSelfWith(self, s: str) -> str:
return self.base[:self.startIndex] + s + self.base[self.endIndex:]

def count(self, char: str) -> int:
return Counter(self.base[self.startIndex:self.endIndex])[char]

def baseLineAtOffset(self, index: int) -> int:
return 1 + Slice(self.base, 0, index).count('\n')
1 change: 1 addition & 0 deletions python/selfie-lib/selfie_lib/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .Slice import Slice as Slice
13 changes: 13 additions & 0 deletions python/selfie-lib/tests/Slice_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from selfie_lib import Slice

def test_unixLine():
slice_1 = Slice("A single line")
assert str(slice_1.unixLine(1)) == "A single line"

one_two_three = Slice("\nI am the first\nI, the second\n\nFOURTH\n")
assert str(one_two_three.unixLine(1)) == ""
assert str(one_two_three.unixLine(2)) == "I am the first"
assert str(one_two_three.unixLine(3)) == "I, the second"
assert str(one_two_three.unixLine(4)) == ""
assert str(one_two_three.unixLine(5)) == "FOURTH"
assert str(one_two_three.unixLine(6)) == ""
Empty file.

0 comments on commit f90bfaf

Please sign in to comment.