Skip to content
This repository was archived by the owner on Apr 4, 2024. It is now read-only.

Commit f90bfaf

Browse files
authored
Skeleton of python implementation (diffplug#248)
2 parents d3a6d5c + 780befb commit f90bfaf

File tree

11 files changed

+324
-1
lines changed

11 files changed

+324
-1
lines changed

.github/workflows/python-ci.yml

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
on:
2+
push:
3+
branches: [main]
4+
pull_request:
5+
paths:
6+
- 'python/**'
7+
defaults:
8+
run:
9+
working-directory: python/selfie-lib
10+
concurrency:
11+
group: ${{ github.workflow }}-${{ github.ref }}
12+
cancel-in-progress: true
13+
jobs:
14+
build:
15+
strategy:
16+
fail-fast: false
17+
matrix:
18+
os: [ubuntu-latest, windows-latest]
19+
runs-on: ${{ matrix.os }}
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v4
23+
- run: pipx install poetry
24+
- name: Set up Python
25+
uses: actions/setup-python@v5
26+
with:
27+
python-version-file: 'python/selfie-lib/pyproject.toml'
28+
cache: 'poetry'
29+
- run: poetry install
30+
- run: poetry run pytest -vv
31+
- run: poetry run pyright
32+
- run: poetry run ruff check

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
.gradle/
33
build/
44
bin/
5-
.DS_Store
5+
.DS_Store
6+
__pycache__/

python/.python-version

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.12

python/.vscode/settings.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"selfie-lib",
4+
"-vv"
5+
],
6+
"python.testing.unittestEnabled": false,
7+
"python.testing.pytestEnabled": true
8+
}

python/README.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
The python implementation is under construction. It makes use of PEP 695, so you must use Python 3.12 or later.
2+
3+
Dependencies are managed using poetry, which you can install here.
4+
- https://python-poetry.org/docs/#installing-with-the-official-installer
5+
- then cd into `selfie-lib` and run `poetry install`
6+
7+
Our CI server runs three checks in the `selfie-lib` directory.
8+
9+
- `poetry run pytest -vv` this runs the tests (`-vv` makes nice output)
10+
- `poetry run pyright` this does type checking
11+
- `poetry run ruff check` this checks formatting
12+
13+
For the IDE we use VSCode. Make sure to open the `python` directory, not the parent `selfie`. Receommended VSCode plugins:
14+
15+
- https://marketplace.visualstudio.com/items?itemName=ms-python.python
16+
- https://marketplace.visualstudio.com/items?itemName=charliermarsh.ruff

python/selfie-lib/poetry.lock

+148
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

python/selfie-lib/pyproject.toml

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[tool.poetry]
2+
name = "selfie-lib"
3+
version = "0.1.0"
4+
description = "Infrastructure for creating selfie-compatible test runner plugins."
5+
authors = ["Ned Twigg <[email protected]>"]
6+
license = "Apache-2.0"
7+
readme = "../README.md"
8+
9+
[tool.poetry.dependencies]
10+
python = "^3.12"
11+
12+
[tool.poetry.group.dev.dependencies]
13+
ruff = "^0.2.1"
14+
pyright = "^1.1.350"
15+
pytest = "^8.0.0"
16+
17+
[build-system]
18+
requires = ["poetry-core"]
19+
build-backend = "poetry.core.masonry.api"

python/selfie-lib/selfie_lib/Slice.py

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from typing import Optional
2+
from typing import Union
3+
from collections import Counter
4+
5+
class Slice:
6+
"""Represents a slice of a base string from startIndex to endIndex."""
7+
8+
def __init__(self, base: str, startIndex: int = 0, endIndex: Optional[int] = None) -> None:
9+
self.base = base
10+
self.base = base
11+
self.startIndex = startIndex
12+
self.endIndex = endIndex if endIndex is not None else len(base)
13+
14+
assert 0 <= self.startIndex <= self.endIndex <= len(base), "Invalid start or end index"
15+
16+
def __len__(self) -> int:
17+
return self.endIndex - self.startIndex
18+
19+
def __getitem__(self, index: int) -> str:
20+
if not (0 <= index < len(self)):
21+
raise IndexError("Index out of range")
22+
return self.base[self.startIndex + index]
23+
24+
def subSequence(self, start: int, end: int) -> 'Slice':
25+
return Slice(self.base, self.startIndex + start, self.startIndex + end)
26+
27+
def trim(self) -> 'Slice':
28+
start, end = 0, len(self)
29+
while start < end and self[start].isspace():
30+
start += 1
31+
while start < end and self[end - 1].isspace():
32+
end -= 1
33+
return self.subSequence(start, end) if start > 0 or end < len(self) else self
34+
35+
def __str__(self) -> str:
36+
return self.base[self.startIndex:self.endIndex]
37+
38+
def sameAs(self, other: Union['Slice', str]) -> bool:
39+
if isinstance(other, Slice):
40+
return str(self) == str(other)
41+
elif isinstance(other, str):
42+
if len(self) != len(other):
43+
return False
44+
for i in range(len(self)):
45+
if self[i] != other[i]:
46+
return False
47+
return True
48+
return False
49+
50+
def indexOf(self, lookingFor: str, startOffset: int = 0) -> int:
51+
result = self.base.find(lookingFor, self.startIndex + startOffset, self.endIndex)
52+
return -1 if result == -1 else result - self.startIndex
53+
54+
def unixLine(self, count: int) -> 'Slice':
55+
assert count > 0, "Count must be positive"
56+
lineStart = 0
57+
for i in range(1, count):
58+
lineStart = self.indexOf('\n', lineStart)
59+
assert lineStart >= 0, f"This string has only {i - 1} lines, not {count}"
60+
lineStart += 1
61+
lineEnd = self.indexOf('\n', lineStart)
62+
return Slice(self.base, self.startIndex + lineStart, self.endIndex if lineEnd == -1 else self.startIndex + lineEnd)
63+
64+
def __eq__(self, other: object) -> bool:
65+
if self is other:
66+
return True
67+
if isinstance(other, Slice):
68+
return self.sameAs(other)
69+
return False
70+
71+
def __hash__(self) -> int:
72+
h = 0
73+
for i in range(len(self)):
74+
h = 31 * h + ord(self[i])
75+
return h
76+
77+
def replaceSelfWith(self, s: str) -> str:
78+
return self.base[:self.startIndex] + s + self.base[self.endIndex:]
79+
80+
def count(self, char: str) -> int:
81+
return Counter(self.base[self.startIndex:self.endIndex])[char]
82+
83+
def baseLineAtOffset(self, index: int) -> int:
84+
return 1 + Slice(self.base, 0, index).count('\n')
+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .Slice import Slice as Slice

python/selfie-lib/tests/Slice_test.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from selfie_lib import Slice
2+
3+
def test_unixLine():
4+
slice_1 = Slice("A single line")
5+
assert str(slice_1.unixLine(1)) == "A single line"
6+
7+
one_two_three = Slice("\nI am the first\nI, the second\n\nFOURTH\n")
8+
assert str(one_two_three.unixLine(1)) == ""
9+
assert str(one_two_three.unixLine(2)) == "I am the first"
10+
assert str(one_two_three.unixLine(3)) == "I, the second"
11+
assert str(one_two_three.unixLine(4)) == ""
12+
assert str(one_two_three.unixLine(5)) == "FOURTH"
13+
assert str(one_two_three.unixLine(6)) == ""

python/selfie-lib/tests/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)