Skip to content

Commit cabf8b7

Browse files
committed
begin
1 parent 34a92ed commit cabf8b7

10 files changed

Lines changed: 410 additions & 0 deletions

File tree

.github/workflows/publish.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Publish
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*.*.*"
7+
8+
jobs:
9+
build-and-publish:
10+
name: Build and Publish
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- name: Set up Python
17+
uses: actions/setup-python@v4
18+
with:
19+
python-version: "3.12"
20+
21+
- name: Install dependencies
22+
run: python -m pip install --upgrade pip build twine
23+
24+
- name: Build the package
25+
run: python -m build
26+
27+
- name: Publish to PyPI
28+
env:
29+
TWINE_USERNAME: __token__
30+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
31+
run: twine upload --verbose dist/*

.github/workflows/tests.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, devel]
6+
pull_request:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up Python
15+
uses: actions/setup-python@v4
16+
with:
17+
python-version: "3.12"
18+
19+
- name: Install dependencies
20+
run: |
21+
python -m pip install --upgrade pip
22+
pip install ".[dev,test]"
23+
24+
- name: Lint with flake8
25+
run: |
26+
# stop the build if there are Python syntax errors or undefined names
27+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
28+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
29+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
30+
31+
- name: Run tests
32+
run: sh tests/run_tests_with_coverage.sh
33+
34+
- name: Test with mypy
35+
run: |
36+
mypy . || true

.gitignore

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Byte-compiled / optimized / DLL files
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
6+
# C extensions
7+
*.so
8+
9+
# Distribution / packaging
10+
.Python
11+
build/
12+
develop-eggs/
13+
dist/
14+
downloads/
15+
eggs/
16+
.eggs/
17+
lib/
18+
lib64/
19+
parts/
20+
sdist/
21+
var/
22+
wheels/
23+
share/python-wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
MANIFEST
28+
29+
# PyInstaller
30+
# Usually these files are written by a python script from a template
31+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
32+
*.manifest
33+
*.spec
34+
35+
# Installer logs
36+
pip-log.txt
37+
pip-delete-this-directory.txt
38+
39+
# Unit test / coverage reports
40+
htmlcov/
41+
.tox/
42+
.nox/
43+
.coverage
44+
.coverage.*
45+
.cache
46+
nosetests.xml
47+
coverage.xml
48+
*.cover
49+
*.py,cover
50+
.hypothesis/
51+
.pytest_cache/
52+
cover/
53+
54+
# Translations
55+
*.mo
56+
*.pot
57+
58+
# Django stuff:
59+
*.log
60+
local_settings.py
61+
db.sqlite3
62+
db.sqlite3-journal
63+
64+
# Flask stuff:
65+
instance/
66+
.webassets-cache
67+
68+
# Scrapy stuff:
69+
.scrapy
70+
71+
# Sphinx documentation
72+
docs/_build/
73+
74+
# PyBuilder
75+
.pybuilder/
76+
target/
77+
78+
# Jupyter Notebook
79+
.ipynb_checkpoints
80+
81+
# IPython
82+
profile_default/
83+
ipython_config.py
84+
85+
# pyenv
86+
# For a library or package, you might want to ignore these files since the code is
87+
# intended to run in multiple environments; otherwise, check them in:
88+
# .python-version
89+
90+
# pipenv
91+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
93+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
94+
# install all needed dependencies.
95+
#Pipfile.lock
96+
97+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
98+
__pypackages__/
99+
100+
# Celery stuff
101+
celerybeat-schedule
102+
celerybeat.pid
103+
104+
# SageMath parsed files
105+
*.sage.py
106+
107+
# Environments
108+
.env
109+
.venv
110+
env/
111+
venv/
112+
ENV/
113+
env.bak/
114+
venv.bak/
115+
116+
# Spyder project settings
117+
.spyderproject
118+
.spyproject
119+
120+
# Rope project settings
121+
.ropeproject
122+
123+
# mkdocs documentation
124+
/site
125+
126+
# mypy
127+
.mypy_cache/
128+
.dmypy.json
129+
dmypy.json
130+
131+
# Pyre type checker
132+
.pyre/
133+
134+
# pytype static type analyzer
135+
.pytype/
136+
137+
# Cython debug symbols
138+
cython_debug/
139+
140+
.pytest_cache/
141+
.vscode/
142+
*.tar.*
143+
*.zip

pyproject.toml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
[build-system]
2+
requires = ["setuptools>=61.0", "wheel"]
3+
build-backend = "setuptools.build_meta"
4+
5+
[tool.setuptools.dynamic]
6+
version = {attr = "ra2csf.__init__.__version__"}
7+
8+
[project]
9+
name = "ra2csf"
10+
dynamic = ["version"]
11+
description = "Read, write, update Command & Conquer: Red Alert 2 .csf files"
12+
readme = "README.md"
13+
authors = [
14+
{name = "jet-logic"}
15+
]
16+
license = {file = "LICENSE"}
17+
classifiers = [
18+
"Development Status :: 3 - Alpha",
19+
"Intended Audience :: Developers",
20+
"Programming Language :: Python :: 3",
21+
"Programming Language :: Python :: 3.8",
22+
]
23+
requires-python = ">=3.8"
24+
dependencies = [
25+
"PyYAML>=5.1",
26+
]
27+
28+
[project.optional-dependencies]
29+
dev = [
30+
"pytest>=7.0",
31+
"pytest-cov>=4.0",
32+
"black>=22.0",
33+
"flake8>=5.0",
34+
"mypy>=1.0",
35+
]
36+
37+
[project.urls]
38+
Homepage = "https://github.com/jet-logic/ra2csf"
39+
40+
[project.scripts]
41+
ghrapt = "ra2csf.__main__:main"
42+
43+
[tool.setuptools.packages.find]
44+
where = ["."]
45+
include = ["ra2csf*"]

ra2csf/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "0.0.1"

ra2csf/read.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
from typing import IO
2+
from struct import unpack
3+
4+
5+
def read_str(fh: IO[bytes]):
6+
(n,) = unpack("<I", fh.read(4))
7+
return fh.read(n).decode("latin1")
8+
9+
10+
def read_wstr(fh: IO[bytes]):
11+
(n,) = unpack("<I", fh.read(4))
12+
return bytes([0xFF - b for b in fh.read(n * 2)]).decode("UTF-16-LE")
13+
14+
15+
def read_int(fh: IO[bytes]) -> int:
16+
(n,) = unpack("<I", fh.read(4))
17+
return n
18+
19+
20+
def read_entry(fh: IO[bytes], sig=b""):
21+
if not sig:
22+
sig = fh.read(4)
23+
assert sig == b" LBL", f"sig = {sig}"
24+
flag = read_int(fh)
25+
key = read_str(fh)
26+
kind = fh.read(4)
27+
value = read_wstr(fh)
28+
if kind == b"WRTS":
29+
extra = read_str(fh)
30+
else:
31+
assert kind == b" RTS"
32+
extra = None
33+
34+
return key, value, flag, extra
35+
36+
37+
def read_head(fh: IO[bytes]) -> bytes:
38+
def fx(
39+
identifier: int,
40+
version: int,
41+
labels: int,
42+
extra_values: int,
43+
flags: int,
44+
language: int,
45+
):
46+
return dict(locals())
47+
48+
return fx(*unpack("<IIIIII", fh.read(6 * 4)))
49+
50+
51+
def read_entries(fh: IO[bytes]):
52+
b = fh.read(4)
53+
while b == b" LBL":
54+
key, value, flag, extra = read_entry(fh, b)
55+
yield key, value, flag, extra
56+
b = fh.read(4)
57+
58+
59+
def parse(fh: IO[bytes]):
60+
head: tuple[int] = unpack("<IIIIII", fh.read(6 * 4))
61+
strings = tuple(read_entries(fh))
62+
return head, strings
63+
64+
65+
def parse_file(csf_file=""):
66+
with open(csf_file, "br") as rh:
67+
return parse(rh)

ra2csf/write.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import IO
2+
from struct import pack
3+
4+
5+
def write_str(fh: IO[bytes], s=""):
6+
fh.write(pack("<I", len(s)))
7+
fh.write(s.encode("latin1"))
8+
9+
10+
def write_wstr(fh: IO[bytes], s=""):
11+
e = s.encode("UTF-16-LE")
12+
fh.write(pack("<I", len(e) // 2))
13+
fh.write(bytes([(0xFF - b) for b in e]))
14+
15+
16+
def write_entry(fh: IO[bytes], key, value="", flag=1, extra=None):
17+
fh.write(b" LBL")
18+
fh.write(pack("<I", flag))
19+
write_str(fh, key)
20+
fh.write(b"WRTS" if extra else b" RTS")
21+
write_wstr(fh, value)
22+
if extra:
23+
write_str(fh, extra)
24+
25+
26+
def write(
27+
fh: IO[bytes], head=list[int], strings=list[tuple[str, str, int, str | None]]
28+
):
29+
fh.write(pack("<IIIIII", *head))
30+
for v in strings:
31+
write_entry(fh, *v)

tests/ra2.csf

474 KB
Binary file not shown.

tests/run_tests_with_coverage.sh

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
# Clean previous coverage data
3+
NAME=$(basename $(realpath .))
4+
DOCS=/tmp/"$NAME"_coverage
5+
export COVERAGE_FILE=/tmp/."$NAME"_coverage
6+
echo [$NAME] $DOCS
7+
rm -f "$COVERAGE_FILE"*
8+
rm -rf $DOCS
9+
10+
# Run tests with coverage
11+
python -m pytest -s tests/ \
12+
--cov=$NAME \
13+
--cov-append \
14+
--cov-report=term-missing || exit 1
15+
16+
# Combine all coverage data
17+
python -m coverage combine
18+
19+
# Generate HTML report in /tmp
20+
python -m coverage html \
21+
--directory="$DOCS" \
22+
--title="$NAME Coverage Report"
23+
24+
echo "Coverage report generated at: $DOCS/index.html"

0 commit comments

Comments
 (0)