dev: promote DOSBox-X and remove QEMU support

This commit is contained in:
2026-08-16 22:14:50 +09:00
parent 4ad3e3097b
commit 594704a07d
26 changed files with 176 additions and 2481 deletions
+2 -19
View File
@@ -1,23 +1,9 @@
# QEMU runtime state, downloaded media, and generated captures # Legacy local QEMU state is no longer used, but may contain large user-owned images.
.qemu/* .qemu/
!.qemu/*.ps1
!.qemu/*.mjs
!.qemu/*.txt
!.qemu/share/
!.qemu/share/*.c
.qemu/share/fec/
# Reproducible DOSBox-X/Open Watcom development cache and ephemeral runs # Reproducible DOSBox-X/Open Watcom development cache and ephemeral runs
.dosboxx/ .dosboxx/
.qemu/*.qcow2
.qemu/*.img
.qemu/*.iso
.qemu/*.zip
.qemu/*.png
.qemu/*.ppm
.qemu/*.tmp
# Windows reserved-device artifact: `> nul` under Git Bash creates a real file. # Windows reserved-device artifact: `> nul` under Git Bash creates a real file.
# Committing it breaks checkout on Windows. # Committing it breaks checkout on Windows.
nul nul
@@ -39,6 +25,3 @@ __pycache__/
node_modules/ node_modules/
.npm/ .npm/
.cache/ .cache/
# Temporary DOS build sources
.qemu/share/dosag*.c
-28
View File
@@ -1,28 +0,0 @@
param()
$ErrorActionPreference = 'Stop'
$qemu = Get-Command qemu-system-i386.exe -ErrorAction Stop
$disk = Join-Path $PSScriptRoot 'freedos.qcow2'
$iso = Join-Path $PSScriptRoot 'FD14LIVE.iso'
if (-not (Test-Path -LiteralPath $disk)) {
throw "Missing $disk. Create it first with: qemu-img create -f qcow2 .qemu\\freedos.qcow2 2G"
}
if (-not (Test-Path -LiteralPath $iso)) {
throw "Missing $iso. Run .qemu\\setup.ps1 first."
}
if (Get-NetTCPConnection -State Listen -LocalPort 4444 -ErrorAction SilentlyContinue) {
throw 'QEMU monitor port 4444 is already in use. Stop the existing VM first.'
}
& $qemu.Source `
-machine pc,usb=on `
-cpu pentium3 `
-m 64 `
-drive "file=$disk,format=qcow2,if=ide,index=0,media=disk" `
-drive "file=$iso,media=cdrom,readonly=on" `
-nic user,model=ne2k_isa `
-monitor "tcp:127.0.0.1:4444,server=on,wait=off" `
-serial null `
-boot order=d `
-display default
-144
View File
@@ -1,144 +0,0 @@
###############################################################################
FreeDOS 1.4 ("FreeDOS 1.4")
###############################################################################
-------------------------------------------------------------------------------
General system requirements:
-------------------------------------------------------------------------------
* DOS-compatible system (Intel + BIOS, or UEFI with Legacy support)
* At least 20MB free disk space:
20MB Plain DOS system
30MB Plain DOS system, with sources
275MB Full installation including applications and games
450MB Full installation with sources
-------------------------------------------------------------------------------
What's in all those zip files?
-------------------------------------------------------------------------------
FD14-LiveCD.zip
* FD14BOOT.IMG - Basic FreeDOS installation boot floppy image.
If your computer has a CD-ROM drive, but you cannot boot from the Live CD
or Legacy CD. Use this diskette image to boot the system. Then insert the
install CD. The FreeDOS installer should do the rest. This diskette
image is for installation purposes only and does not provide a Live
Environment.
* FD14LIVE.ISO - The FreeDOS 1.4 installer. Most users should
use this image to install FreeDOS.
Depending on your computer system and hardware configuration, you
can also use the LiveCD to boot and run FreeDOS directly from the
CD-ROM without installation to your hard drive.
FD14-LegacyCD.zip
* FD14BOOT.IMG - This zip archive also contains a copy of the basic
CD-ROM installation boot floppy.
* FD14LGCY.ISO - A bootable CD image designed for older hardware. If
you cannot boot the LiveCD to install FreeDOS, try this disc image.
This disc image uses the older El Torito boot CD format. Some newer
computers and virtual machines cannot use this older format. Unless
you have a computer that requires this type of bootable CD, we
recommend using the LiveCD instead.
FD14-BonusCD.zip
* FD14BNS.ISO - A non-bootable CD image that contains some FreeDOS
packages that are not installed as part of either the LiveCD or
the Legacy CD.
FD14-LiteUSB.zip
* FD14LITE.IMG - A minimal FreeDOS installer, as a USB fob drive
image. This does not contain all of the packages from either the
LiveCD or the LegacyCD, and instead only contains a basic set of
FreeDOS packages.
* FD14LITE.VMDK - A virtual machine disk file, compatible with a
variety of virtual machine software including VirtualBox, VMware,
and other systems.
Using a VMDK file can simplify installing FreeDOS. Just attach the
VMDK image to your virtual machine software as a hard drive, and
boot it. (Please note that you will still need to create a virtual
hard disk to install FreeDOS)
FD14-FullUSB.zip
* FD14FULL.IMG - Plain DOS system and Full install USB stick image.
* FD14FULL.VMDK - A virtual machine disk file, compatible with a
variety of virtual machine software. Just attach the VMDK image to
your virtual machine as a hard drive, and boot it.
VERIFY.TXT
* Contains MD5, SHA256 and SHA512 hashes for all of the different
release files. You can verify your copy of FreeDOS with these.
README.TXT
* The "before you choose and install" document. (All of the zip
files listed above also have a copy of the README file.)
-------------------------------------------------------------------------------
FreeDOS Floppy-Only Edition (FD14-x86)
-------------------------------------------------------------------------------
FreeDOS 1.4 includes a Floppy-Only Edition! This edition should run on
any hardware that can run FreeDOS and has EGA or better graphics:
* Are you running a '286 or another classic system without a CD-ROM
drive? Install from these floppies to install FreeDOS.
* Do you have just one hard drive and no CD or floppy drive? Just
copy the contents of the floppies to a temporary directory and run
the installer from there.
* Want to perform a "headless" install to a different DOS directory?
It's easy with the command line options.
The Floppy-Only Edition uses a completely different installer than
the CD-ROM or USB installers. The Floppy-Only Edition does not use
any of those other media to install.
The Floppy-Only Edition contains a limited set of FreeDOS programs
that are more useful on classic PC hardware.
The FreeDOS Floppy-Only Edition is distributed as single zip archive that
contains several pre-made floppy diskette images:
These zip archives contain image files for several common floppy
diskette media under separate directories:
* 720k - 3.5" 720k diskette images
* 144m - 3.5" 1.44mb diskette images
* 120m - 5.25" 1.2mb diskette images
Each of those sets contain a number of pre-made disk images:
* x86BOOT.img - A floppy boot disk image with the x86 installer.
* x86DSK??.img - Several floppy diskette images that contain the
core FreeDOS operating system files. The number of floppy images
and amount of files on each varies depending on the diskette
capacity.
To conserve space, the FreeDOS Floppy-Only Edition does not contain
the source code for the FreeDOS packages. You can find the source code
via the FreeDOS website (https://www.freedos.org/download/) or from
the other release media, like the USB or CD-ROM installer.
-60
View File
@@ -1,60 +0,0 @@
param(
[Parameter(Mandatory)]
[string] $Text,
[ValidateRange(0, 1000)]
[int] $DelayMilliseconds = 25,
[switch] $NoEnter
)
$ErrorActionPreference = 'Stop'
$map = @{
' ' = 'spc'; ':' = 'shift-semicolon'; ';' = 'semicolon'
'\' = 'backslash'; '|' = 'shift-backslash'; '/' = 'slash'; '?' = 'shift-slash'
'.' = 'dot'; '>' = 'shift-dot'; ',' = 'comma'; '<' = 'shift-comma'
'-' = 'minus'; '_' = 'shift-minus'; '=' = 'equal'; '+' = 'shift-equal'
'[' = 'bracket_left'; '{' = 'shift-bracket_left'
']' = 'bracket_right'; '}' = 'shift-bracket_right'
"'" = 'apostrophe'; '"' = 'shift-apostrophe'; '`' = 'grave_accent'; '~' = 'shift-grave_accent'
'!' = 'shift-1'; '@' = 'shift-2'; '#' = 'shift-3'; '$' = 'shift-4'; '%' = 'shift-5'
'^' = 'shift-6'; '&' = 'shift-7'; '*' = 'shift-8'; '(' = 'shift-9'; ')' = 'shift-0'
}
function ConvertTo-QemuKey([char] $Character) {
$text = [string] $Character
if ($map.ContainsKey($text)) { return $map[$text] }
if ([char]::IsLetter($Character)) {
$letter = [char]::ToLowerInvariant($Character)
if ([char]::IsUpper($Character)) { return "shift-$letter" }
return [string] $letter
}
if ([char]::IsDigit($Character)) { return $text }
throw "Unsupported QEMU key character: '$Character'"
}
$client = [System.Net.Sockets.TcpClient]::new([System.Net.Sockets.AddressFamily]::InterNetwork)
try {
$client.Connect([System.Net.IPAddress]::Parse('127.0.0.1'), 4444)
$stream = $client.GetStream()
$writer = [System.IO.StreamWriter]::new($stream, [System.Text.Encoding]::ASCII, 1024, $true)
try {
$writer.NewLine = "`r`n"
$writer.AutoFlush = $true
Start-Sleep -Milliseconds 100
foreach ($char in $Text.ToCharArray()) {
$writer.WriteLine("sendkey $(ConvertTo-QemuKey $char)")
Start-Sleep -Milliseconds $DelayMilliseconds
}
if (-not $NoEnter) { $writer.WriteLine('sendkey ret') }
Start-Sleep -Milliseconds $DelayMilliseconds
}
finally {
$writer.Dispose()
}
}
finally {
$client.Dispose()
}
-26
View File
@@ -1,26 +0,0 @@
param()
$ErrorActionPreference = 'Stop'
$qemuImg = Get-Command qemu-img.exe -ErrorAction Stop
$disk = Join-Path $PSScriptRoot 'freedos.qcow2'
$archive = Join-Path $PSScriptRoot 'FD14-LiveCD.zip'
$iso = Join-Path $PSScriptRoot 'FD14LIVE.iso'
$url = 'https://www.ibiblio.org/pub/micro/pc-stuff/freedos/files/distributions/1.4/FD14-LiveCD.zip'
if (-not (Test-Path -LiteralPath $disk)) {
& $qemuImg.Source create -f qcow2 $disk 2G
if ($LASTEXITCODE -ne 0) { throw "qemu-img failed with exit code $LASTEXITCODE." }
}
if (-not (Test-Path -LiteralPath $iso)) {
if (-not (Test-Path -LiteralPath $archive)) {
Invoke-WebRequest -Uri $url -OutFile $archive
}
Expand-Archive -LiteralPath $archive -DestinationPath $PSScriptRoot -Force
}
if (-not (Test-Path -LiteralPath $iso)) {
throw 'FreeDOS ISO extraction failed.'
}
Get-Item -LiteralPath $disk,$iso | Select-Object Name,Length,LastWriteTime
+24 -41
View File
@@ -10,53 +10,39 @@ DOS용 시스템 프로그래밍 언어 Ferro와 그 컴파일러 `fec`. 규범
| `SPEC.md` | 언어 명세 + 구현 지시서. 유일한 규범 문서 | | `SPEC.md` | 언어 명세 + 구현 지시서. 유일한 규범 문서 |
| `SPEC.AUDIT.md` | 명세 변경의 문제·결정·근거·구현 영향 누적 로그 | | `SPEC.AUDIT.md` | 명세 변경의 문제·결정·근거·구현 영향 누적 로그 |
| `tools/README.md` | 호스트 요구사항, 최초 셋업, 자동화 구조 | | `tools/README.md` | 호스트 요구사항, 최초 셋업, 자동화 구조 |
| `tools/tcpagent/README.md` | DOS 내부 TCP 에이전트 프로토콜과 빌드 |
VM 자동화 **명령 목록플래그는 문서가 아니라 CLI가 규범**이다. 문서에 복제하면 개발 환경과 테스트 명령 목록·플래그는 CLI로 확인한다.
반드시 드리프트하므로 아래로 확인한다.
```powershell ```powershell
uv run ferro-vm --help uv run ferro-dos --help
uv run ferro-vm <command> --help uv run ferro-dos <command> --help
uv run ferro-test --help uv run ferro-test --help
``` ```
## 검증 규칙 ## 검증 규칙
- 개발 중 빠른 회귀 검사는 `uv run ferro-test run --through <milestone>` - 컴파일러와 생성 C는 DOSBox-X 내부의 고정된 Open Watcom으로 컴파일한다.
DOSBox-X에서 수행한다. 컴파일러와 bits16은 `WCL`, bits32 생성 C는 `WCL386`을 쓴다.
이 경로도 컴파일러 A를 DOS 내부 Open Watcom으로 매번 새로 빌드한다. - 호스트 C 컴파일러 결과는 검증으로 인정하지 않는다. 호스트는 편집, Git, 다운로드,
- **마일스톤의 최종 공식 검증은 QEMU FreeDOS 내부에서다.** DOSBox-X 결과는 격리 작업공간 준비에다.
개발용 smoke test이며 완료 게이트를 대체하지 않는다. - 실행마다 만들어지는 authoritative workspace는 `C:\FEC`다. 호스트에서는
- 컴파일러 A와 생성 C 모두 VM 안의 Open Watcom으로 컴파일한다. `.dosboxx/runs/<run>/FEC`에 대응한다.
컴파일러 A와 bits16은 `WCL`, bits32 생성 C는 `WCL386`. - 완료하려는 기능을 직접 검사하는 pytest case가 통과해야 한다. 테스트가 증명하지
- **호스트에서 컴파일하지 않는다.** WSL이나 호스트 C 컴파일러 결과는 정식 검증으로 않는 기능은 완료로 처리하지 않는다.
인정하지 않는다. 호스트는 편집, diff, Git, 파일 전송에만 쓴다. - VGA 데모처럼 수동 검증이 필요한 항목은 자동 완료 게이트에서 제외한다.
- authoritative workspace는 VM의 `C:\FEC`다.
- 마일스톤 완료 기준은 `C:\FEC\BUILD.OK`, `C:\FEC\TEST.OK`, `TEST-DOS.BAT` exit 0
세 가지를 모두 확인하는 것이다. 테스트가 증명하지 않는 기능은 완료로 처리하지
않는다.
- VGA 데모처럼 멀티모달 수동 검증이 필요한 항목은 완료 게이트에서 제외한다.
## 빌드 함정 ## 빌드 함정
VM 안에서 반복해서 물렸던 것들. 어기면 원인 찾기 어려운 실패가 난다. - 컴파일러는 16비트 large model로 빌드한다. small model은 메모리 부족으로 실패한다.
- 링크는 `*.obj` 와일드카드로 한다. DOS 명령줄 길이 제한 때문에 오브젝트를 나열할
- **컴파일러 A는 16비트 large model로 빌드한다.** small model은 메모리 부족으로 수 없다. `fec/build-dos.bat`는 먼저 stale object를 지운다.
실패한다. - M4 Watcom 테스트는 `-wx -wcd=202`를 쓴다. 생성 C의 보수적 미사용 helper 때문에
- **링크는 `*.obj` 와일드카드로 한다.** DOS 명령줄 길이 제한 때문에 오브젝트를
나열할 수 없다. 그래서 `fec/build-dos.bat`는 먼저 `C:\FEC\*.obj`를 지워
stale 32비트 test object가 섞이지 않게 한다.
- **M4 Watcom 테스트는 `-wx -wcd=202`를 쓴다.** 생성 C의 보수적 미사용 helper 때문에
W202만 끄고 나머지 경고는 오류로 유지한다. W202만 끄고 나머지 경고는 오류로 유지한다.
- **fixture는 짧은 이름으로 전송한다.** DOS 8.3 파일명 때문에 긴 이름은 - fixture는 DOS 8.3 이름으로 실행한다. 긴 이름은 registry에서 명시적으로 줄인다.
`BAD-ARI.FE`, `TRY-FPR.FE`처럼 명시적으로 줄여야 한다. - `R:`은 읽기 전용 저장소, `W:`은 읽기 전용 Watcom이다. 빌드 산출물은 반드시
- **`D:`에서 빌드하지 않는다.** QEMU의 vvfat 뷰는 교환용이며, 과거 `D:`에서 빌드하다 임시 `C:\FEC`에 쓴다.
rename 처리 오류로 QEMU가 종료된 적이 있다. 호스트에서 편집한 파일은 `put`으로 - 실패 분석이 필요하면 `ferro-test --keep-failed` 또는 `ferro-dos --keep`으로
`C:`에 올린 뒤 컴파일한다. 임시 작업공간을 보존한다.
- 긴 DOS 배치가 멈추면 QEMU를 재시작하기 전에 Ctrl+C 주입을 먼저 시도한다.
`Terminate batch file ... (Yes/No/All)?`가 뜨면 `y`, `ret`을 보내고 `ping` 복구를
확인한다.
## 작업 흐름 ## 작업 흐름
@@ -64,12 +50,9 @@ VM 안에서 반복해서 물렸던 것들. 어기면 원인 찾기 어려운
구현이 명세와 다르면 둘 중 하나가 틀린 것이므로 그 자리에서 결론을 낸다. 구현이 명세와 다르면 둘 중 하나가 틀린 것이므로 그 자리에서 결론을 낸다.
- 검증된 마일스톤마다 커밋하고 항상 `origin`에 푸시한다. - 검증된 마일스톤마다 커밋하고 항상 `origin`에 푸시한다.
- primary 브랜치는 `master`다. - primary 브랜치는 `master`다.
- `.qemu/*.png`, `.qemu/*.ppm`은 진단용이며 커밋하지 않는다. - `.dosboxx/`의 다운로드, 실행 작업공간, 로그는 커밋하지 않는다.
## 현재 상태 ## 현재 상태
- M1~M5 완료 및 QEMU/Open Watcom 검증됨. 현재 구현 상태와 다음 마일스톤은 `SPEC.md`와 테스트 registry를 기준으로 판단한다.
- 다음은 M6(R1~R8 대여 검사)다. 착수할 때 소유권 로직을 `check.c`/`emit_c.c`에서 과거 VM 이미지나 호스트에 남은 바이너리를 근거로 완료 처리하지 않는다.
`own.c/h`로 분리한다 (`SPEC.md` §11.3).
- v0.1.6에서 R8(파생 반환), R6(마지막 사용까지 대여), R10(전역 대여 금지)이
바뀌었다. 셋 다 own.c의 상태 기계를 건드리므로 분리 이후에 함께 구현한다.
-27
View File
@@ -1,27 +0,0 @@
# TODO
## Done
- [x] Add a non-reboot abort path for a hung DOS command: inject `Ctrl+C` through
QEMU's monitor and wait for the agent to recover.
- [x] Apply a configurable timeout to `exec` and invoke the non-reboot abort path
on timeout.
- [x] Expose `abort` for an immediate user-requested command interruption.
The three above were first built for the COM1 serial agent, lost in the rewrite
to the resident TCP agent, and rebuilt on the QEMU monitor in `ferro-vm exec`.
The TCP version supervises with guest disk liveness (`info blockstats`) rather
than a fixed stopwatch, so a slow compile is no longer mistaken for a hang.
## Open
- [ ] Consider `BREAK=ON` in `C:\FDCONFIG.SYS`. Ctrl+C only takes effect at a DOS
break check, and with the FreeDOS default of `BREAK=OFF` a compute-bound
child whose output is redirected to a file may never reach one, so `abort`
cannot always stop it. `BREAK=ON` checks on every DOS call and makes the
abort reliable, at a small cost to every DOS call. Needs a VM reboot; back
up `FDCONFIG.SYS` first.
- [ ] `TCPAGENT.EXE` connects from a fixed source port (`LOCAL_PORT 2058`). After
the host end closes, a reconnect reuses the same 4-tuple and can flap until
the old state ages out. Observed as a ~10s connect/disconnect cycle after
the daemon is killed mid-connection.
-201
View File
@@ -1,201 +0,0 @@
@echo off
rem D: is the read-only exchange volume. Stage everything before running DOS tools.
if not exist C:\FEC md C:\FEC
if not exist C:\FEC\SRC md C:\FEC\SRC
if not exist C:\FEC\STD md C:\FEC\STD
if not exist C:\FEC\TESTS md C:\FEC\TESTS
if not exist C:\FEC\TESTS\PASS md C:\FEC\TESTS\PASS
if not exist C:\FEC\TESTS\FAIL md C:\FEC\TESTS\FAIL
if not exist C:\FEC\TESTS\M2 md C:\FEC\TESTS\M2
if not exist C:\FEC\TESTS\M3 md C:\FEC\TESTS\M3
if not exist C:\FEC\TESTS\M4 md C:\FEC\TESTS\M4
if not exist C:\FEC\TESTS\M5 md C:\FEC\TESTS\M5
if exist C:\FEC\VM.FAIL del C:\FEC\VM.FAIL
if exist C:\FEC\STAGE.FAIL del C:\FEC\STAGE.FAIL
copy D:\FEC\BUILD-~1.BAT C:\FEC\BUILD.BAT > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TEST-DOS.BAT C:\FEC\TEST-DOS.BAT > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\ARENA.C C:\FEC\SRC\ARENA.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\ARENA.H C:\FEC\SRC\ARENA.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\DIAG.C C:\FEC\SRC\DIAG.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\DIAG.H C:\FEC\SRC\DIAG.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\LEXER.C C:\FEC\SRC\LEXER.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\LEXER.H C:\FEC\SRC\LEXER.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\AST.C C:\FEC\SRC\AST.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\AST.H C:\FEC\SRC\AST.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\PARSER.C C:\FEC\SRC\PARSER.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\PARSER.H C:\FEC\SRC\PARSER.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\DRIVER.C C:\FEC\SRC\DRIVER.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\TYPES.C C:\FEC\SRC\TYPES.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\TYPES.H C:\FEC\SRC\TYPES.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\CHECK.C C:\FEC\SRC\CHECK.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\CHECK.H C:\FEC\SRC\CHECK.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\EMIT_C.C C:\FEC\SRC\EMIT_C.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\SRC\EMIT_C.H C:\FEC\SRC\EMIT_C.H > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\CORE.FE C:\FEC\STD\CORE.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\FMT.FE C:\FEC\STD\FMT.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\IO.FE C:\FEC\STD\IO.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\LIST.FE C:\FEC\STD\LIST.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\MAP.FE C:\FEC\STD\MAP.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\MEM.FE C:\FEC\STD\MEM.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\STR.FE C:\FEC\STD\STR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\STD\SYS.FE C:\FEC\STD\SYS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\PASS\BASIC.FE C:\FEC\TESTS\PASS\BASIC.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\PASS\LITERALS.FE C:\FEC\TESTS\PASS\LITERALS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\PASS\KEYWOR~1.FE C:\FEC\TESTS\PASS\KEYWOR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\PASS\V012-F~1.FE C:\FEC\TESTS\PASS\V012-F.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\FAIL\MISSIN~1.FE C:\FEC\TESTS\FAIL\MISSIN.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\FAIL\UNCLOS~1.FE C:\FEC\TESTS\FAIL\UNCLOS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\FAIL\LOGICA~1.FE C:\FEC\TESTS\FAIL\LOGICA.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\HELLO.FE C:\FEC\TESTS\M2\HELLO.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\SCOPES.FE C:\FEC\TESTS\M2\SCOPES.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-CO~1.FE C:\FEC\TESTS\M2\BAD-CO.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-CAST.FE C:\FEC\TESTS\M2\BAD-CA.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-ASSI~1.FE C:\FEC\TESTS\M2\BAD-AS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-UN~1.FE C:\FEC\TESTS\M2\BAD-UN.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-UN~2.FE C:\FEC\TESTS\M2\BAD-UI.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-AR~1.FE C:\FEC\TESTS\M2\BAD-AR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-TY~1.FE C:\FEC\TESTS\M2\BAD-TY.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-RE~1.FE C:\FEC\TESTS\M2\BAD-RE.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\BAD-VOID.FE C:\FEC\TESTS\M2\BAD-VO.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M2\CAST-W~1.FE C:\FEC\TESTS\M2\CAST-W.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\STRUCT.FE C:\FEC\TESTS\M3\STRUCT.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\ENUM.FE C:\FEC\TESTS\M3\ENUM.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\ARRAY.FE C:\FEC\TESTS\M3\ARRAY.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\STR.FE C:\FEC\TESTS\M3\STR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\FOR.FE C:\FEC\TESTS\M3\FOR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\NESTED.FE C:\FEC\TESTS\M3\NESTED.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\CHAR.FE C:\FEC\TESTS\M3\CHAR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\ARRAYCTX.FE C:\FEC\TESTS\M3\ARRAYCTX.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BOUNDS.FE C:\FEC\TESTS\M3\BOUNDS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADFLD.FE C:\FEC\TESTS\M3\BADFLD.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADMAT.FE C:\FEC\TESTS\M3\BADMAT.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADARR.FE C:\FEC\TESTS\M3\BADARR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADCYCLE.FE C:\FEC\TESTS\M3\BADCYCLE.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADSTR.FE C:\FEC\TESTS\M3\BADSTR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADCHAR.FE C:\FEC\TESTS\M3\BADCHAR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADFIELD.FE C:\FEC\TESTS\M3\BADFIELD.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M3\BADINDEX.FE C:\FEC\TESTS\M3\BADINDEX.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\FORMAT.FE C:\FEC\TESTS\M4\FORMAT.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-ARI~1.FE C:\FEC\TESTS\M4\BAD-ARI.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-VERB.FE C:\FEC\TESTS\M4\BAD-VERB.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-RUN~1.FE C:\FEC\TESTS\M4\BAD-RUN.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-TYP~1.FE C:\FEC\TESTS\M4\BAD-TYP.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-TRY.FE C:\FEC\TESTS\M4\BAD-TRY.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\TRY-FPR~1.FE C:\FEC\TESTS\M4\TRY-FPR.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-WRI~1.FE C:\FEC\TESTS\M4\BAD-WRI.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\PROP.FE C:\FEC\TESTS\M4\PROP.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\PROPTEST.C C:\FEC\TESTS\M4\PROPTEST.C > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-MANY.FE C:\FEC\TESTS\M4\BAD-MANY.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-OPEN.FE C:\FEC\TESTS\M4\BAD-OPEN.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M4\BAD-CLS.FE C:\FEC\TESTS\M4\BAD-CLS.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\DEFER.FE C:\FEC\TESTS\M5\DEFER.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\OWNED.FE C:\FEC\TESTS\M5\OWNED.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\BAD-MOVE.FE C:\FEC\TESTS\M5\BAD-MOVE.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\BAD-DES~1.FE C:\FEC\TESTS\M5\BAD-DES.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\RUNTIME.FE C:\FEC\TESTS\M5\RUNTIME.FE > nul
if errorlevel 1 goto stage_fail
copy D:\FEC\TESTS\M5\RUNTIME.C C:\FEC\TESTS\M5\RUNTIME.C > nul
if errorlevel 1 goto stage_fail
call C:\FEC\TEST-DOS.BAT
if exist C:\FEC\TEST.OK goto vm_success
echo FAIL>C:\FEC\VM.FAIL
verify other 2>nul
goto stage_done
:vm_success
cd C:\FEC
goto stage_done
:stage_fail
echo FAIL>C:\FEC\STAGE.FAIL
echo FAIL>C:\FEC\VM.FAIL
verify other 2>nul
:stage_done
+2 -4
View File
@@ -1,16 +1,14 @@
[project] [project]
name = "ferrolang" name = "ferrolang"
version = "0.1.0" version = "0.1.0"
description = "Ferro language compiler and QEMU development automation" description = "Ferro language compiler and reproducible DOS development tools"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"onnxruntime>=1.28.0",
"pytest>=9.0.0", "pytest>=9.0.0",
"rapidocr>=3.9.2",
] ]
[project.scripts] [project.scripts]
ferro-vm = "ferrolang_vm.cli:main" ferro-dos = "ferrolang_vm.dos_cli:main"
ferro-test = "ferrolang_vm.test_cli:main" ferro-test = "ferrolang_vm.test_cli:main"
[build-system] [build-system]
+1 -1
View File
@@ -1 +1 @@
"""Windows-only QEMU and FreeDOS TCP-agent automation.""" """Reproducible DOS development tools for the Ferro compiler."""
-195
View File
@@ -1,195 +0,0 @@
"""Command line client for the Windows-only ferro-vm daemon."""
from __future__ import annotations
import argparse
import json
import shutil
import subprocess
import sys
import time
from multiprocessing.connection import Client
from pathlib import Path
from .daemon import PIPE, ROOT
def rpc(payload: dict[str, object], start_daemon: bool = False) -> object:
try:
conn = Client(PIPE, family="AF_PIPE")
except (FileNotFoundError, OSError):
if not start_daemon:
raise RuntimeError("ferro-vm daemon is not running; run `uv run ferro-vm start`")
flags = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0) | getattr(subprocess, "DETACHED_PROCESS", 0)
subprocess.Popen([sys.executable, "-m", "ferrolang_vm.daemon"], cwd=ROOT, creationflags=flags, close_fds=True)
deadline = time.monotonic() + 5
while True:
try:
conn = Client(PIPE, family="AF_PIPE")
break
except (FileNotFoundError, OSError):
if time.monotonic() >= deadline:
raise RuntimeError("ferro-vm daemon did not create its control pipe")
time.sleep(.1)
with conn:
conn.send(payload)
response = conn.recv()
if not response["ok"]:
raise RuntimeError(response["error"])
return response["result"]
def wait_ready(timeout: int) -> bool:
"""Wait quietly; do not turn an expected boot gap into error-log spam."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
try:
status = rpc({"op": "status"})
if status["agent_connected"] and str(rpc({"op": "ping"})["response"]).startswith("OK 504F4E47"):
return True
except RuntimeError:
pass
time.sleep(.5)
return False
def follow_logs() -> int:
log_path = ROOT / ".qemu" / "ferro-vm.log"
lnav = shutil.which("lnav.exe") or shutil.which("lnav")
if lnav:
return subprocess.run([lnav, str(log_path)]).returncode
print("lnav was not found; following the log with PowerShell.", file=sys.stderr)
return subprocess.run([
"powershell", "-NoProfile", "-Command",
f"Get-Content -LiteralPath '{log_path}' -Wait",
]).returncode
EPILOG = r"""examples:
uv run ferro-vm start boot the VM and start the daemon
uv run ferro-vm wait-ready block until TCPAGENT answers PING
uv run ferro-vm exec 'dir C:\FEC' run a DOS command, print exit code and output
uv run ferro-vm put fec/src/check.c 'C:\FEC\SRC\CHECK.C'
uv run ferro-vm get 'C:\FEC\TEST.OK' .qemu/TEST.OK
uv run ferro-vm exec --idle-timeout 180 'C:\FEC\BUILD-DOS.BAT'
uv run ferro-vm abort Ctrl+C the command running right now
uv run ferro-vm logs follow the structured daemon log
The authoritative workspace is C:\FEC inside the VM. Never build on D: (the vvfat
view is for exchange only). See AGENTS.md for verification rules and build traps.
"""
SIMPLE_COMMANDS = {
"start": "Start the daemon and boot QEMU. Safe to run when already up.",
"stop": "Quit QEMU cleanly and stop the daemon.",
"status": "Print daemon, QEMU, and TCPAGENT connection state as JSON.",
"ping": "Send PING to TCPAGENT. Expects 'OK 504F4E47' (PONG).",
"screenshot": "Capture the VGA console to a PPM/PNG under .qemu/.",
"ocr": "Capture the console and print recognized text (RapidOCR).",
"logs": "Follow the append-only daemon log. Uses lnav when available.",
"abort": "Interrupt the DOS command currently running (Ctrl+C via QEMU).",
}
def main() -> int:
parser = argparse.ArgumentParser(
prog="ferro-vm",
description="Windows-only QEMU/FreeDOS automation for the Ferro compiler.",
epilog=EPILOG,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
commands = parser.add_subparsers(dest="op", required=True, metavar="COMMAND")
for name, blurb in SIMPLE_COMMANDS.items():
commands.add_parser(name, help=blurb, description=blurb)
wait_help = "Block until TCPAGENT is connected and answers PING."
wait = commands.add_parser("wait-ready", help=wait_help, description=wait_help)
wait.add_argument("--timeout", type=int, default=45, metavar="SECONDS",
help="give up after this many seconds (default: %(default)s)")
reset_help = "Quit QEMU cleanly, reboot it, and wait for TCPAGENT."
reset = commands.add_parser("reset", help=reset_help, description=reset_help)
reset.add_argument("--timeout", type=int, default=45, metavar="SECONDS",
help="give up after this many seconds (default: %(default)s)")
exec_help = "Run a DOS command inside the VM and print its exit code and output."
execute = commands.add_parser(
"exec", help=exec_help,
description=exec_help + " Quote the command so the host shell does not eat"
r" backslashes: exec 'wcl386 -q HELLO.C'."
" A slow command is not a failed one: the wait ends"
" when the guest stops touching its disk, not when a"
" stopwatch expires.")
execute.add_argument("command", metavar="DOS_COMMAND",
help=r"command line to hand to COMMAND.COM, e.g. 'dir C:\FEC'")
execute.add_argument("--idle-timeout", type=float, default=60, metavar="SECONDS",
help="interrupt once the guest has made no disk access for"
" this long (default: %(default)s)")
execute.add_argument("--hard-timeout", type=float, default=900, metavar="SECONDS",
help="interrupt after this much total time regardless of"
" activity; the backstop for a CPU-bound hang"
" (default: %(default)s)")
put_help = "Copy a host file into the VM."
put = commands.add_parser("put", help=put_help, description=put_help)
put.add_argument("source", type=Path, metavar="HOST_PATH",
help="file on this machine")
put.add_argument("destination", metavar="DOS_PATH",
help=r"target inside the VM, e.g. 'C:\FEC\SRC\CHECK.C'."
" DOS uses 8.3 names, so long fixtures must be shortened"
" explicitly (BAD-ARI.FE, TRY-FPR.FE)")
get_help = "Copy a file out of the VM onto the host."
get = commands.add_parser("get", help=get_help, description=get_help)
get.add_argument("source", metavar="DOS_PATH",
help=r"file inside the VM, e.g. 'C:\FEC\TEST.OK'")
get.add_argument("destination", type=Path, metavar="HOST_PATH",
help="target on this machine")
args = parser.parse_args()
try:
if args.op == "logs":
return follow_logs()
if args.op == "wait-ready":
if wait_ready(args.timeout):
print(json.dumps({"agent": "PONG"}))
return 0
raise RuntimeError("TCPAGENT did not become ready")
if args.op == "ocr":
result = rpc({"op": "screenshot"})
import logging
logging.disable(logging.INFO)
from rapidocr import RapidOCR
recognized = RapidOCR()(result["path"])
print("\n".join(recognized.txts or ()))
return 0
if args.op == "reset":
rpc({"op": "stop"}, start_daemon=True)
time.sleep(.5)
rpc({"op": "start"}, start_daemon=True)
time.sleep(2)
rpc({"op": "monitor", "command": "sendkey ret"})
if wait_ready(args.timeout):
print(json.dumps({"reset": "complete", "agent": "PONG"}))
return 0
raise RuntimeError("TCPAGENT did not become ready")
payload: dict[str, object] = {"op": args.op}
if args.op == "exec":
payload["command"] = args.command
payload["idle_timeout"] = args.idle_timeout
payload["hard_timeout"] = args.hard_timeout
if args.op == "put":
payload["source"] = str(args.source.resolve())
payload["destination"] = args.destination
if args.op == "get":
payload["source"] = args.source
payload["destination"] = str(args.destination.resolve())
print(json.dumps(rpc(payload, start_daemon=args.op == "start"), ensure_ascii=False, indent=2))
return 0
except RuntimeError as exc:
print(f"ferro-vm: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
-467
View File
@@ -1,467 +0,0 @@
"""Long-lived Windows host for the FreeDOS TCP agent.
The only automation TCP listener is 127.0.0.1:5558, used exclusively by
TCPAGENT.EXE. Local commands use a Windows named pipe.
"""
from __future__ import annotations
import json
import logging
import os
import re
import select
import shutil
import socket
import subprocess
import sys
import threading
import time
from datetime import datetime, timezone
from multiprocessing.connection import Listener
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
QEMU = ROOT / ".qemu"
PIPE = r"\\.\pipe\ferrolang-vm"
AGENT_ADDRESS = ("127.0.0.1", 5558)
MONITOR_ADDRESS = ("127.0.0.1", 4444)
LOG_PATH = QEMU / "ferro-vm.log"
# Short commands answer promptly, so a plain socket timeout is the right guard.
REQUEST_TIMEOUT = 30
# EXEC is different: TCPAGENT is frozen inside system() for the whole command
# and cannot answer, so silence proves nothing. Wake up often, decide with
# guest liveness instead of a stopwatch, and never discard the connection just
# because a compile is slow.
EXEC_POLL_SECONDS = 2
DEFAULT_IDLE_TIMEOUT = 60
DEFAULT_HARD_TIMEOUT = 900
# Budget for collecting the result after Ctrl+C, before giving up on the stream.
INTERRUPT_GRACE_SECONDS = 15
class ExecInterrupted(RuntimeError):
"""The supervisor decided to stop the running DOS command."""
def configure_logging() -> None:
QEMU.mkdir(exist_ok=True)
handler = logging.FileHandler(LOG_PATH, encoding="utf-8")
handler.setFormatter(logging.Formatter("%(asctime)s.%(msecs)03dZ %(levelname)-7s %(message)s", "%Y-%m-%dT%H:%M:%S"))
logging.basicConfig(level=logging.INFO, handlers=[handler])
logging.Formatter.converter = time.gmtime
def log_event(level: int, event: str, **fields: object) -> None:
suffix = " ".join(f"{key}={json.dumps(value, ensure_ascii=False)}" for key, value in fields.items())
logging.log(level, "%s%s", event, f" {suffix}" if suffix else "")
class Host:
def __init__(self) -> None:
self.agent: socket.socket | None = None
self.agent_lock = threading.Lock()
self.agent_ready = threading.Event()
self.qemu: subprocess.Popen[bytes] | None = None
# QEMU accepts one monitor connection at a time, and the EXEC
# supervisor polls it while other control requests run concurrently.
self.monitor_lock = threading.Lock()
self.abort_requested = threading.Event()
self.exec_active = threading.Event()
@staticmethod
def bind_agent_listener() -> socket.socket:
"""Bind 5558 exclusively so a second daemon fails loudly.
SO_REUSEADDR means something different on Windows than on Unix: it lets
another process bind an already-bound port and quietly take over new
connections, so a duplicate daemon would be silently half-working
instead of refusing to start. SO_EXCLUSIVEADDRUSE is the Windows way to
say "only me".
"""
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
exclusive = getattr(socket, "SO_EXCLUSIVEADDRUSE", None)
if exclusive is not None:
server.setsockopt(socket.SOL_SOCKET, exclusive, 1)
else:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(AGENT_ADDRESS)
server.listen(1)
return server
def accept_agents(self, server: socket.socket) -> None:
log_event(logging.INFO, "agent listener ready", address="127.0.0.1:5558")
while True:
sock, peer = server.accept()
sock.settimeout(REQUEST_TIMEOUT)
with self.agent_lock:
if self.agent is not None:
sock.close()
log_event(logging.WARNING, "agent rejected", peer=str(peer), reason="already connected")
continue
self.agent = sock
try:
banner = self._read_line(sock).decode("ascii", "replace")
if banner != "TCPAGENT READY":
raise ConnectionError(f"invalid TCPAGENT banner: {banner!r}")
self.agent_ready.set()
log_event(logging.INFO, "agent connected", peer=f"{peer[0]}:{peer[1]}")
# Request() owns protocol reads. Between requests, peek only
# for EOF so TCPAGENT can reconnect without being rejected.
while self.agent is sock:
if self.agent_lock.acquire(blocking=False):
try:
readable, _, _ = select.select([sock], [], [], .25)
if readable and not sock.recv(1, socket.MSG_PEEK):
break
finally:
self.agent_lock.release()
else:
time.sleep(.05)
except (ConnectionError, OSError) as exc:
# Reset can close a connection during its banner. This is a
# per-connection event, never a reason to kill the listener.
log_event(logging.WARNING, "agent handshake/connection failed",
peer=f"{peer[0]}:{peer[1]}", error=str(exc))
finally:
with self.agent_lock:
if self.agent is sock:
self.agent = None
self.agent_ready.clear()
sock.close()
log_event(logging.INFO, "agent disconnected", peer=f"{peer[0]}:{peer[1]}")
@staticmethod
def _read_line(sock: socket.socket, supervise=None) -> bytes:
# Partial input is kept across timeouts, so supervise() may fire in the
# middle of a line without losing what has already arrived.
out = bytearray()
while not out.endswith(b"\n"):
try:
part = sock.recv(1)
except TimeoutError:
if supervise is None:
raise
supervise()
continue
if not part:
raise ConnectionError("TCP agent closed connection")
out.extend(part)
return bytes(out).rstrip(b"\r\n")
@staticmethod
def _read_exactly(sock: socket.socket, count: int, supervise=None) -> bytes:
chunks: list[bytes] = []
while count:
try:
chunk = sock.recv(min(65536, count))
except TimeoutError:
if supervise is None:
raise
supervise()
continue
if not chunk:
raise ConnectionError("TCP agent closed connection")
chunks.append(chunk)
count -= len(chunk)
return b"".join(chunks)
def guest_idle_seconds(self) -> float | None:
"""Seconds since the guest last touched a disk, or None if unknown.
QEMU keeps counting while TCPAGENT is frozen inside system(), so this
is the one progress signal available during a long DOS command. A
purely CPU-bound command looks idle here, which is what the hard
timeout is for.
"""
try:
text = self.monitor("info blockstats")
except OSError:
return None
idle: float | None = None
for line in text.splitlines():
operations = re.search(r"rd_operations=(\d+)", line)
elapsed = re.search(r"idle_time_ns=(\d+)", line)
if not operations or not elapsed or int(operations.group(1)) == 0:
continue
seconds = int(elapsed.group(1)) / 1e9
idle = seconds if idle is None else min(idle, seconds)
return idle
def request(self, command: str, payload: bytes = b"") -> str:
with self.agent_lock:
if self.agent is None:
raise RuntimeError("TCPAGENT is not connected")
sock = self.agent
started = time.monotonic()
try:
sock.sendall(command.encode("ascii") + b"\n" + payload)
response = self._read_line(sock).decode("ascii", "replace")
except OSError as exc:
if self.agent is sock:
self.agent = None
self.agent_ready.clear()
raise RuntimeError(f"TCPAGENT request failed: {exc}") from exc
log_event(logging.INFO, "agent request", command=command.split(" ", 1)[0], response=response[:200], elapsed_ms=round((time.monotonic()-started)*1000))
if response.startswith("ERR "):
raise RuntimeError(bytes.fromhex(response[4:]).decode("utf-8", "replace"))
return response
def ping(self) -> dict[str, object]:
return {"response": self.request("PING")}
def _read_exec_result(self, sock: socket.socket, supervise=None) -> tuple[int, int, bytes]:
header = self._read_line(sock, supervise).decode("ascii", "replace")
fields = header.split()
if len(fields) == 4 and fields[0] == "RESULT":
code, length, flags = int(fields[1]), int(fields[2]), int(fields[3])
return code, flags, self._read_exactly(sock, length, supervise)
if fields and fields[0] == "OK":
# Compatibility with an installed pre-RESULT agent.
return int(fields[1]), 1, bytes.fromhex(fields[2]) if len(fields) > 2 else b""
raise RuntimeError("malformed EXEC response: " + header)
def exec(self, command: str, idle_timeout: float = DEFAULT_IDLE_TIMEOUT,
hard_timeout: float = DEFAULT_HARD_TIMEOUT) -> dict[str, object]:
log_event(logging.INFO, "exec start", command=command,
idle_timeout=idle_timeout, hard_timeout=hard_timeout)
started = time.monotonic()
self.abort_requested.clear()
with self.agent_lock:
if self.agent is None:
raise RuntimeError("TCPAGENT is not connected")
sock = self.agent
self.exec_active.set()
reported = started
interrupt_at: float | None = None
interrupted = ""
def supervise() -> None:
"""Called every EXEC_POLL_SECONDS while the agent stays silent."""
nonlocal reported, interrupt_at, interrupted
now = time.monotonic()
idle = self.guest_idle_seconds()
if now - reported >= 15:
reported = now
log_event(logging.INFO, "exec running", elapsed_s=round(now-started, 1),
guest_idle_s=None if idle is None else round(idle, 1))
if interrupt_at is None:
if self.abort_requested.is_set():
interrupted = "aborted by request"
elif now - started > hard_timeout:
interrupted = f"hard timeout after {hard_timeout:.0f}s"
elif idle is not None and idle > idle_timeout:
interrupted = f"guest idle {idle:.0f}s exceeds {idle_timeout:.0f}s"
if interrupted:
interrupt_at = now
log_event(logging.WARNING, "exec interrupting", reason=interrupted)
self.monitor("sendkey ctrl-c")
return
waited = now - interrupt_at
# DOS answers Ctrl+C with "Terminate batch file (Y/N/A)?" and
# waits there. The prompt only appears once COMMAND.COM reaches
# the next batch line, which can be many seconds into a slow
# command, so answer on every poll rather than once: a single
# early 'y' is swallowed by whatever is still running. Send only
# 'y' -- the prompt takes one keystroke, and a trailing Enter
# gets read as "keep going".
self.monitor("sendkey y")
# Even answered, Ctrl+C is a request. It lands only at a DOS
# break check, and a DOS/4GW child (wcc386, wmake) runs in
# protected mode where it may never reach one, so the command
# can still run to completion. Keep collecting its result rather
# than abandoning a stream that still owes us one -- give up
# only once the guest has gone quiet too.
if waited > INTERRUPT_GRACE_SECONDS and (idle is None or idle > 5):
raise ExecInterrupted(interrupted + "; command did not stop")
try:
encoded = command.encode("ascii", "replace").hex().upper()
sock.settimeout(EXEC_POLL_SECONDS)
sock.sendall(f"EXEC {encoded}\n".encode("ascii"))
code, flags, raw = self._read_exec_result(sock, supervise)
except (OSError, ExecInterrupted) as exc:
# Only now is the stream beyond repair; drop it so the agent
# reconnects with a clean protocol state.
if self.agent is sock:
self.agent = None
self.agent_ready.clear()
sock.close()
raise RuntimeError(f"TCPAGENT EXEC failed: {exc}") from exc
finally:
self.exec_active.clear()
self.abort_requested.clear()
try:
sock.settimeout(REQUEST_TIMEOUT)
except OSError:
pass
output = raw.decode("cp437", "replace")
for line in output.splitlines():
log_event(logging.INFO, "dos output", line=line)
log_event(logging.INFO, "exec finish", exit=code, bytes=len(raw), flags=flags,
interrupted=interrupted or None,
elapsed_ms=round((time.monotonic()-started)*1000))
result = {"exit": code, "output": output, "bytes": len(raw), "flags": flags}
if interrupted:
result["interrupted"] = interrupted
return result
def abort(self) -> dict[str, object]:
if not self.exec_active.is_set():
return {"aborted": False, "reason": "no command is running"}
self.abort_requested.set()
log_event(logging.INFO, "abort requested")
return {"aborted": True}
def put(self, source: str, destination: str) -> dict[str, object]:
data = Path(source).read_bytes()
encoded = destination.encode("ascii").hex().upper()
self.request(f"PUT {encoded} {len(data)}", data)
stat = self.request(f"HASH {encoded}")
log_event(logging.INFO, "put", path=destination, bytes=len(data), stat=stat)
return {"path": destination, "bytes": len(data), "stat": stat}
def get(self, source: str, destination: str) -> dict[str, object]:
encoded = source.encode("ascii").hex().upper()
with self.agent_lock:
if self.agent is None:
raise RuntimeError("TCPAGENT is not connected")
sock = self.agent
sock.sendall(f"GET {encoded}\n".encode("ascii"))
header = self._read_line(sock).decode("ascii", "strict").split()
if len(header) != 2 or header[0] != "DATA":
raise RuntimeError("GET failed: " + " ".join(header))
remaining = int(header[1])
chunks: list[bytes] = []
while remaining:
chunk = sock.recv(min(65536, remaining))
if not chunk:
raise RuntimeError("TCPAGENT closed during GET")
chunks.append(chunk)
remaining -= len(chunk)
target = Path(destination)
target.parent.mkdir(parents=True, exist_ok=True)
data = b"".join(chunks)
target.write_bytes(data)
log_event(logging.INFO, "get", path=source, bytes=len(data), destination=str(target))
return {"path": source, "destination": str(target), "bytes": len(data)}
def monitor(self, command: str) -> str:
with self.monitor_lock, socket.create_connection(MONITOR_ADDRESS, timeout=3) as sock:
sock.settimeout(1)
time.sleep(.1)
try:
sock.recv(4096)
except TimeoutError:
pass
sock.sendall(command.encode("ascii") + b"\n")
time.sleep(.2)
chunks: list[bytes] = []
while True:
try:
chunk = sock.recv(4096)
except TimeoutError:
break
if not chunk:
break
chunks.append(chunk)
return b"".join(chunks).decode("ascii", "replace").strip()
def start(self) -> dict[str, object]:
if self.qemu is not None and self.qemu.poll() is None:
return {"started": False, "reason": "already running"}
try:
self.monitor("info status")
return {"started": False, "reason": "already running (external)"}
except OSError:
pass
executable = shutil.which("qemu-system-i386.exe")
disk = QEMU / "freedos.qcow2"
if not executable:
raise RuntimeError("qemu-system-i386.exe is not on PATH")
if not disk.exists():
raise RuntimeError(f"missing {disk}; run .qemu/setup.ps1 and .qemu/install.ps1")
self.qemu = subprocess.Popen([executable, "-machine", "pc,accel=whpx,kernel-irqchip=off,usb=on", "-smp", "1", "-m", "64", "-drive", f"file={disk},format=qcow2,if=ide,index=0,media=disk", "-nic", "user,model=ne2k_isa", "-monitor", "tcp:127.0.0.1:4444,server=on,wait=off", "-boot", "order=c", "-display", "default"], cwd=QEMU)
log_event(logging.INFO, "qemu started", pid=self.qemu.pid)
return {"started": True, "pid": self.qemu.pid}
def stop(self) -> dict[str, object]:
try:
self.monitor("quit")
log_event(logging.INFO, "qemu stop requested")
except OSError:
pass
return {"stopped": True}
def screenshot(self) -> dict[str, object]:
ppm, png = QEMU / "qemu-screen.ppm", QEMU / "qemu-screen.png"
self.monitor("screendump " + str(ppm).replace("\\", "/"))
ffmpeg = shutil.which("ffmpeg.exe")
if not ppm.exists() or not ffmpeg:
raise RuntimeError("screenshot failed or ffmpeg.exe is not on PATH")
subprocess.run([ffmpeg, "-y", "-loglevel", "error", "-i", str(ppm), str(png)], check=True)
ppm.unlink(missing_ok=True)
log_event(logging.INFO, "screenshot", path=str(png))
return {"path": str(png)}
def dispatch(self, request: dict[str, object]) -> object:
op = request["op"]
if op == "status":
try:
self.monitor("info status")
running = True
except OSError:
running = False
return {"agent_connected": self.agent_ready.is_set(), "qemu_running": running, "log": str(LOG_PATH)}
if op == "start": return self.start()
if op == "stop": return self.stop()
if op == "ping": return self.ping()
if op == "abort": return self.abort()
if op == "exec":
return self.exec(str(request["command"]),
float(request.get("idle_timeout", DEFAULT_IDLE_TIMEOUT)),
float(request.get("hard_timeout", DEFAULT_HARD_TIMEOUT)))
if op == "put": return self.put(str(request["source"]), str(request["destination"]))
if op == "get": return self.get(str(request["source"]), str(request["destination"]))
if op == "screenshot": return self.screenshot()
if op == "monitor": return {"output": self.monitor(str(request["command"]))}
raise ValueError(f"unknown operation: {op}")
def serve_pipe(host: Host) -> None:
listener = Listener(PIPE, family="AF_PIPE")
log_event(logging.INFO, "control pipe ready", pipe=PIPE)
def handle(conn) -> None:
try:
request = conn.recv()
try:
conn.send({"ok": True, "result": host.dispatch(request)})
except Exception as exc:
log_event(logging.ERROR, "control failed", error=str(exc))
conn.send({"ok": False, "error": str(exc)})
finally:
conn.close()
while True:
# One thread per request: `abort` has to be answerable while a long
# `exec` is still holding the agent.
threading.Thread(target=handle, args=(listener.accept(),), daemon=True).start()
def main() -> None:
if os.name != "nt":
raise SystemExit("ferro-vm currently supports Windows only")
configure_logging()
host = Host()
try:
server = host.bind_agent_listener()
except OSError as exc:
log_event(logging.ERROR, "agent listener bind failed", address="127.0.0.1:5558", error=str(exc))
raise SystemExit(f"another ferro-vm daemon already owns 127.0.0.1:5558 ({exc})")
threading.Thread(target=host.accept_agents, args=(server,), daemon=True).start()
serve_pipe(host)
if __name__ == "__main__":
main()
+80
View File
@@ -0,0 +1,80 @@
"""General-purpose disposable DOSBox-X/Open Watcom environment CLI."""
from __future__ import annotations
import argparse
import subprocess
import sys
from pathlib import Path
from .dosboxx import DosboxError, run_suite, setup
from .paths import ROOT
from .suite import Case
def _run(command: str | None, *, keep: bool, show_dos: bool) -> int:
cases = [] if command is None else [Case("command", 0, command, True)]
run = run_suite(cases, keep=keep, show_dos=show_dos, trace_dos=show_dos)
try:
if run.result() != "PASS":
print(run.log(), file=sys.stderr)
return 1
if cases and run.result(cases[0]) != "PASS":
print(run.log(cases[0]), file=sys.stderr)
return 1
if cases:
output = run.log(cases[0])
if output:
print(output, end="" if output.endswith("\n") else "\n")
if keep:
print(f"DOS workspace: {run.root}")
return 0
finally:
run.cleanup()
def main() -> int:
parser = argparse.ArgumentParser(
prog="ferro-dos",
description="Disposable directory-backed DOSBox-X/Open Watcom environment.",
)
commands = parser.add_subparsers(dest="action", required=True)
prepare = commands.add_parser("setup", help="install the pinned DOSBox-X and Open Watcom tools")
prepare.add_argument("--accept-watcom-license", action="store_true")
for name, help_text in (
("build", "build the current FEC source inside DOS"),
("exec", "build FEC and execute one DOS command"),
("batch", "build FEC and call a repository DOS batch"),
("shell", "build FEC and open an interactive DOS shell"),
):
command = commands.add_parser(name, help=help_text)
command.add_argument("--keep", action="store_true", help="preserve the temporary DOS workspace")
command.add_argument("--show-dos", action="store_true", help="show and pause the DOS window")
if name == "exec":
command.add_argument("dos_command")
elif name == "batch":
command.add_argument("path", type=Path)
args = parser.parse_args()
try:
if args.action == "setup":
dosbox, watcom = setup(accept_watcom_license=args.accept_watcom_license)
print(f"DOSBox-X: {dosbox}")
print(f"Open Watcom: {watcom}")
return 0
if args.action == "build":
return _run(None, keep=args.keep, show_dos=args.show_dos)
if args.action == "exec":
return _run(args.dos_command, keep=args.keep, show_dos=args.show_dos)
if args.action == "shell":
return _run("COMMAND.COM", keep=args.keep, show_dos=True)
path = (ROOT / args.path).resolve()
if (path != ROOT and ROOT not in path.parents) or not path.is_file():
raise DosboxError("batch path must be an existing file inside the repository")
relative = path.relative_to(ROOT).as_posix().replace("/", "\\").upper()
return _run(f"CALL R:\\{relative}", keep=args.keep, show_dos=args.show_dos)
except (DosboxError, subprocess.SubprocessError) as exc:
print(f"ferro-dos: {exc}", file=sys.stderr)
return 2
if __name__ == "__main__":
raise SystemExit(main())
+11 -12
View File
@@ -12,7 +12,7 @@ import zipfile
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path from pathlib import Path
from .daemon import ROOT from .paths import ROOT
from .suite import Case from .suite import Case
@@ -96,17 +96,14 @@ def setup(*, accept_watcom_license: bool = False) -> tuple[Path, Path]:
raise DosboxError("the DOSBox-X development backend currently supports Windows only") raise DosboxError("the DOSBox-X development backend currently supports Windows only")
lock = _lock() lock = _lock()
dosbox, watcom, watcom_required = _required_paths(lock) dosbox, watcom, watcom_required = _required_paths(lock)
marker = CACHE / "SETUP.OK" tools_ready = dosbox.is_file() and all(path.is_file() for path in watcom_required)
lock_hash = _sha256(LOCK_PATH) if not tools_ready and not accept_watcom_license:
if (marker.is_file() and marker.read_text(encoding="ascii").strip() == lock_hash
and dosbox.is_file() and all(path.is_file() for path in watcom_required)):
return dosbox, watcom
if not accept_watcom_license:
raise DosboxError( raise DosboxError(
"Open Watcom is distributed under the Sybase Open Watcom Public License. " "Open Watcom is distributed under the Sybase Open Watcom Public License. "
"Review tools/toolchains/dosboxx.lock.json and rerun " "Review tools/toolchains/dosboxx.lock.json and rerun "
"`uv run ferro-test setup --accept-watcom-license`." "`uv run ferro-dos setup --accept-watcom-license`."
) )
if not tools_ready:
dosbox_spec = lock["dosboxx"] dosbox_spec = lock["dosboxx"]
watcom_spec = lock["open_watcom"] watcom_spec = lock["open_watcom"]
assert isinstance(dosbox_spec, dict) and isinstance(watcom_spec, dict) assert isinstance(dosbox_spec, dict) and isinstance(watcom_spec, dict)
@@ -117,7 +114,7 @@ def setup(*, accept_watcom_license: bool = False) -> tuple[Path, Path]:
if not path.is_file()] if not path.is_file()]
if missing: if missing:
raise DosboxError("toolchain archive is missing: " + ", ".join(missing)) raise DosboxError("toolchain archive is missing: " + ", ".join(missing))
marker.write_text(lock_hash + "\n", encoding="ascii") (CACHE / "SETUP.OK").write_text(_sha256(LOCK_PATH) + "\n", encoding="ascii")
return dosbox, watcom return dosbox, watcom
@@ -125,7 +122,7 @@ def resolve_tools() -> tuple[Path, Path]:
lock = _lock() lock = _lock()
dosbox, watcom, required = _required_paths(lock) dosbox, watcom, required = _required_paths(lock)
if not dosbox.is_file() or not all(path.is_file() for path in required): if not dosbox.is_file() or not all(path.is_file() for path in required):
raise DosboxError("toolchain is not installed; run `uv run ferro-test setup`") raise DosboxError("toolchain is not installed; run `uv run ferro-dos setup`")
return dosbox, watcom return dosbox, watcom
@@ -165,7 +162,8 @@ def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool) -> str:
]) ])
lines.extend([ lines.extend([
"goto FINISH", ":BUILDFAIL", "echo FAIL>RESULTS\\BUILD.RES", ":FINISH", "goto FINISH", ":BUILDFAIL", "echo FAIL>RESULTS\\BUILD.RES", ":FINISH",
"echo DONE>RUN.OK", *(["pause"] if show_dos else []), "exit", "", "echo DONE>RUN.OK",
*(["pause"] if show_dos else []), "exit", "",
]) ])
return "\r\n".join(lines) return "\r\n".join(lines)
@@ -236,7 +234,8 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
command.append("-silent") command.append("-silent")
command.extend([ command.extend([
"-fastlaunch", "-conf", str(config), "-fastlaunch", "-conf", str(config),
"-c", f'mount C "{run_root}"', "-c", f'mount W "{watcom}" -ro', "-c", f'mount C "{run_root}"', "-c", f'mount R "{ROOT}" -ro',
"-c", f'mount W "{watcom}" -ro',
"-c", "C:", "-c", "cd \\FEC", "-c", "RUN.BAT", "-c", "C:", "-c", "cd \\FEC", "-c", "RUN.BAT",
]) ])
completed = subprocess.run(command, check=False, timeout=300) completed = subprocess.run(command, check=False, timeout=300)
+5
View File
@@ -0,0 +1,5 @@
"""Repository and local cache paths shared by Ferro developer tools."""
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
+45 -66
View File
@@ -2,75 +2,54 @@
## Host support ## Host support
Automation currently supports **Windows 10/11 only**. The fast development loop The automated DOS development environment currently supports Windows 10/11.
requires only `uv`; it downloads pinned DOSBox-X and Open Watcom DOS releases. The host only needs `uv`. The setup command downloads the pinned DOSBox-X and
The final milestone gate additionally requires QEMU with WHPX support and Open Watcom DOS archives, verifies their SHA-256 hashes, and installs them in the
`ffmpeg.exe` on `PATH`. Other hosts are not supported yet. ignored `.dosboxx/` cache.
## Getting started ```powershell
uv run ferro-dos setup --accept-watcom-license
```
Review the Open Watcom license referenced by
`tools/toolchains/dosboxx.lock.json` before accepting it. Neither downloaded
archives nor installed tools are committed.
## General DOS environment
`ferro-dos` provides the development entry points:
```powershell
uv run ferro-dos build
uv run ferro-dos exec "FEC.EXE --check TESTS\M6\OKLAST.FE"
uv run ferro-dos batch fec\test-dos.bat
uv run ferro-dos shell
uv run ferro-dos --help
```
Every invocation creates an isolated host directory under `.dosboxx/runs/` and
mounts it as writable `C:`. The repository is mounted read-only as `R:` and the
pinned Open Watcom installation as read-only `W:`. Current compiler sources,
the standard library, and fixtures are copied to `C:\FEC`; all compilation and
execution happen there inside DOSBox-X. Successful runs are removed by default.
Use `--keep` to retain a workspace and `--show-dos` to display the DOS window.
This directory-backed layout deliberately has no QEMU, disk-image, TCP-agent,
or OCR dependency. A future disk-image backend can be added without changing
the command interface.
## Pytest regression suite
`ferro-test` uses the same isolated DOSBox-X/Open Watcom environment, builds
`FEC.EXE` once, and executes all selected cases sequentially in that one DOS
instance. Pytest still reports each registered case separately.
```powershell ```powershell
uv run ferro-test setup --accept-watcom-license
uv run ferro-test run --through m6 -v uv run ferro-test run --through m6 -v
``` uv run ferro-test run --only m6 --dos-log
`setup` reads `tools/toolchains/dosboxx.lock.json`, downloads the exact official
archives, verifies their SHA-256 hashes, and extracts them under ignored
`.dosboxx/`. Review the Open Watcom license referenced by the lock file before
accepting it. Archives and installed tools are deliberately not committed.
Each `run` creates a disposable DOS drive, copies the current compiler, standard
library, and fixtures, then builds `FEC.EXE` once inside DOS with Open Watcom.
All selected milestone commands execute sequentially in that same DOSBox-X
instance, while pytest reports every emit, Watcom build, runtime, and rejection
check separately. Thus stale QEMU binaries cannot make the test pass.
`--through m6` runs cumulatively from M1; `--only m6` selects one milestone.
Use `--keep-failed` to preserve a failed drive under `.dosboxx/runs/`,
`--dos-log` to print the captured DOS console, `--trace-dos` to disable command
output redirection, and `--show-dos` to keep the GUI open until a key is pressed.
This is the quick development smoke test. Run the QEMU/FreeDOS workflow below
for the authoritative milestone completion gate.
```powershell
uv run ferro-vm start
uv run ferro-vm status
```
The command list lives in the CLI itself, not in this file:
```powershell
uv run ferro-vm --help
uv run ferro-vm <command> --help
uv run ferro-test --help uv run ferro-test --help
``` ```
Working rules, verification gates, and DOS build traps are in `AGENTS.md`. `--keep-failed` preserves a failed workspace, `--dos-log` prints captured DOS
output, `--trace-dos` disables per-command redirection, and `--show-dos` displays
## How it fits together the GUI. Working rules and DOS/Open Watcom build traps are in `AGENTS.md`.
`TCPAGENT.EXE` runs inside FreeDOS and dials out to `127.0.0.1:5558`; its wire
protocol is documented in `tcpagent/README.md`. The `ferro-vm` daemon owns that
connection and the QEMU monitor. Local commands reach the daemon over the
Windows named pipe `\\.\pipe\ferrolang-vm` — there is no controller or observer
TCP port.
The daemon writes an append-only structured log (`uv run ferro-vm logs`, which
uses `lnav` when installed and otherwise falls back to PowerShell `Get-Content
-Wait`). It records command metadata, DOS output, exit status, transfers, and
agent lifecycle events as UTF-8 lines, and deliberately never logs raw binary
payloads or protocol hex.
`reset` quits QEMU cleanly, restarts it, waits for FreeDOS to boot, submits the
default boot-menu Enter, and requires a TCPAGENT `PING`/`PONG` before returning.
QEMU `system_reset` is intentionally unsupported: repeated soft resets leave the
FreeDOS NE2000 packet driver stuck during initialization.
## Standalone OCR
`tools/qemu_ocr.py` remains available for OCRing an existing image:
```powershell
uv run python tools/qemu_ocr.py --image .qemu/qemu-screen.png
```
-79
View File
@@ -1,79 +0,0 @@
#!/usr/bin/env python3
"""Capture the QEMU VGA console and print its text with RapidOCR."""
from __future__ import annotations
import argparse
import json
import logging
import subprocess
import sys
from pathlib import Path
from rapidocr import RapidOCR
ROOT = Path(__file__).resolve().parent.parent
DEFAULT_IMAGE = ROOT / ".qemu" / "qemu-screen.png"
def capture() -> Path:
subprocess.run(
["ferro-vm", "screenshot"],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.PIPE,
text=True,
)
if not DEFAULT_IMAGE.is_file():
raise RuntimeError(f"QEMU screenshot was not created: {DEFAULT_IMAGE}")
return DEFAULT_IMAGE
def recognize(image: Path, min_score: float) -> list[dict[str, object]]:
logging.disable(logging.INFO)
result = RapidOCR()(image)
rows: list[dict[str, object]] = []
if result.txts is None or result.boxes is None or result.scores is None:
return rows
for box, text, score in zip(result.boxes, result.txts, result.scores):
if float(score) < min_score:
continue
rows.append(
{
"x": int(min(point[0] for point in box)),
"y": int(min(point[1] for point in box)),
"text": text,
"score": round(float(score), 5),
}
)
rows.sort(key=lambda row: (int(row["y"]), int(row["x"])))
return rows
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--image", type=Path, help="OCR an existing image instead of capturing QEMU")
parser.add_argument("--min-score", type=float, default=0.5)
parser.add_argument("--json", action="store_true", help="emit locations and scores as JSON")
parser.add_argument("-o", "--output", type=Path, help="also save output as UTF-8 text")
args = parser.parse_args()
image = args.image.resolve() if args.image else capture()
rows = recognize(image, args.min_score)
if args.json:
rendered = json.dumps({"image": str(image), "lines": rows}, ensure_ascii=False, indent=2)
else:
rendered = "\n".join(str(row["text"]) for row in rows)
if args.output:
args.output.write_text(rendered + ("\n" if rendered else ""), encoding="utf-8")
if rendered:
print(rendered)
return 0 if rows else 1
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, RuntimeError, subprocess.CalledProcessError) as exc:
print(f"qemu-ocr: {exc}", file=sys.stderr)
raise SystemExit(2)
-6
View File
@@ -1,6 +0,0 @@
@echo off
set WATCOM=C:\DEVEL\WATCOMC
set PATH=C:\DEVEL\WATCOMC\BINW;C:\DEVEL\WATCOMC\BINP;C:\FREEDOS\BIN
set INCLUDE=C:\DEVEL\WATCOMC\H
set EDPATH=
wmake
-15
View File
@@ -1,15 +0,0 @@
@echo off
if not exist TCPAGENT.EXE goto fail
copy TCPAGENT.EXE C:\FREEDOS\BIN\TCPAGENT.EXE > nul
if errorlevel 1 goto fail
find "TCPAGENT.EXE" C:\FDAUTO.BAT > nul
if not errorlevel 1 goto done
echo SET MTCPCFG=C:\FREEDOS\MTCP.CFG>>C:\FDAUTO.BAT
echo IF EXIST C:\FREEDOS\BIN\TCPAGENT.EXE C:\FREEDOS\BIN\TCPAGENT.EXE>>C:\FDAUTO.BAT
:done
echo TCPAGENT installed.
goto end
:fail
echo TCPAGENT install failed.
verify other 2>nul
:end
-38
View File
@@ -1,38 +0,0 @@
# Place this directory at MTCP\APPS\TCPAGENT in an mTCP checkout.
tcp_c_dir = ..\..\TCPLIB
memory_model = -mc
compile_options = @WPP.RSP
tcpobjs = packet.obj arp.obj eth.obj ip.obj tcp.obj tcpsockm.obj udp.obj utils.obj timer.obj ipasm.obj trace.obj
all : tcpagent.exe
.cpp.obj :
wpp $[* $(compile_options)
tcpagent.obj: tcpagent.cpp
wpp $(compile_options) tcpagent.cpp
packet.obj:
wpp $(tcp_c_dir)\packet.cpp $(compile_options)
arp.obj:
wpp $(tcp_c_dir)\arp.cpp $(compile_options)
eth.obj:
wpp $(tcp_c_dir)\eth.cpp $(compile_options)
ip.obj:
wpp $(tcp_c_dir)\ip.cpp $(compile_options)
tcp.obj:
wpp $(tcp_c_dir)\tcp.cpp $(compile_options)
tcpsockm.obj:
wpp $(tcp_c_dir)\tcpsockm.cpp $(compile_options)
udp.obj:
wpp $(tcp_c_dir)\udp.cpp $(compile_options)
utils.obj:
wpp $(tcp_c_dir)\utils.cpp $(compile_options)
timer.obj:
wpp $(tcp_c_dir)\timer.cpp $(compile_options)
trace.obj:
wpp $(tcp_c_dir)\trace.cpp $(compile_options)
ipasm.obj:
wasm -0 -mc $(tcp_c_dir)\ipasm.asm
tcpagent.exe: tcpagent.obj $(tcpobjs)
wlink system dos option map option eliminate option stack=16384 name $@ file *.obj
clean : .symbolic
del *.obj
del *.map
del tcpagent.exe
-101
View File
@@ -1,101 +0,0 @@
# FreeDOS resident TCP agent
`TCPAGENT.EXE` is a foreground resident automation process. It uses the mTCP
packet-driver stack and maintains an outbound connection to the QEMU host at
`10.0.2.2:5558`.
The Windows-only Python `ferro-vm` daemon owns that listener. It logs metadata
and decoded command output to `.qemu/ferro-vm.log`; it does not expose an
observer/controller TCP port or emit binary payloads to the log. Local host
control uses a Windows named pipe.
## Build in FreeDOS
1. Obtain the GPLv3 mTCP source tree (tested with the jhpyle/mTCP 2022 fork).
2. Copy this directory to `MTCP\APPS\TCPAGENT` inside that tree.
3. Set `WATCOM` for Open Watcom and run `BUILD.BAT`.
4. Run `INSTALL.BAT`; it installs the executable and adds startup lines after
the existing packet-driver setup in `C:\FDAUTO.BAT`.
The build uses mTCP's compact memory model and Open Watcom C++16. The agent is
therefore distributed under GPLv3 when linked with mTCP.
## Protocol
`PING`, `READ`, `WRITE`, and `LIST` use text commands. `EXEC` captures both
stdout and stderr at the DOS handle level and returns an untruncated raw body:
- `EXEC <hex command>\n` -> `RESULT <exit> <length> <flags>\r\n<raw bytes>`
Fast transfer commands are:
- `PUT <hex DOS path> <byte length>\n<raw bytes>` -> `OK\r\n`
- `GET <hex DOS path>\n` -> `DATA <length>\r\n<raw bytes>`
- `HASH <hex DOS path>\n` -> `STAT <length> <FNV1A32>\r\n`
The host invokes them through:
```powershell
uv run ferro-vm put host-file 'C:\DOS\FILE'
uv run ferro-vm get 'C:\DOS\FILE' host-file
```
## Agent-side logging
The foreground agent prints one timestamped line per event on the VGA console
and keeps the same text in `C:\TCPAGENT.LOG`, rotating files larger than 256 KiB
to `C:\TCPAGENT.OLD`. Payloads and command output are never written to that
metadata log.
Every command is logged with a request line and a result line carrying byte
counts and elapsed time — `EXEC`, `PUT`, `GET`, `HASH`, `LIST`, `READ`, and
`WRITE`. `PING` is deliberately excluded because `wait-ready` polls it twice a
second. Connection events (`connecting`, `connected`, `connect failed; retry N`,
`link lost`) are logged too; those are invisible to the host by definition,
since they happen when the socket is down.
Lines are colored by writing VGA attribute bytes after `cprintf` lays out the
line: gray timestamps, cyan requests, yellow `EXEC` command text, green success,
red failure. Open Watcom's DOS `conio.h` has no `textattr()`, and ANSI escapes
are not interpreted on this FreeDOS console, so neither of the usual routes
works. Elapsed times come from the BIOS tick counter at 18.2065 Hz (~55 ms
resolution).
## Long commands
mTCP is only driven when the agent calls it, and `system()` freezes the agent
for the entire child command. So during a long `EXEC` the DOS side is mute: it
cannot answer, cannot acknowledge, cannot report progress. Silence therefore
proves nothing about whether the command is healthy.
The host must not read that silence as failure. `ferro-vm exec` waits on
QEMU's own view of the guest instead: `info blockstats` keeps counting while
the agent is frozen, and `idle_time_ns` distinguishes a slow command from a
stuck one. See `--idle-timeout` and `--hard-timeout` in `ferro-vm exec --help`.
When the host does decide to stop a command it injects Ctrl+C through the QEMU
monitor, then answers COMMAND.COM's `Terminate batch file (Y/N/A)?` prompt.
That is a request, not a guarantee: Ctrl+C only lands at a DOS break check, and
with `BREAK=OFF` (the FreeDOS default in `C:\FDCONFIG.SYS`) a compute-bound
child whose output we redirected to a file may never reach one. The host keeps
collecting the result either way rather than abandoning a stream that still
owes it a `RESULT`.
Adding `BREAK=ON` to `C:\FDCONFIG.SYS` would make DOS check on every system
call and so make Ctrl+C reliable, at a small cost to every DOS call.
## Rebuilding inside the VM
`REBUILD.BAT` compiles and installs the agent in a single `exec`. `BUILD.BAT`
only runs `wmake` in the current directory, which is not where a host-driven
`ferro-vm exec` starts.
```powershell
uv run ferro-vm put tools/tcpagent/tcpagent.cpp 'C:\MTSRC\MTCP\APPS\TCPAGENT\TCPAGENT.CPP'
uv run ferro-vm put tools/tcpagent/REBUILD.BAT 'C:\REBUILD.BAT'
uv run ferro-vm exec 'C:\REBUILD.BAT'
uv run ferro-vm reset
```
The reset is required: the running agent holds the old image in memory, and
`C:\FDAUTO.BAT` starts it at boot.
-22
View File
@@ -1,22 +0,0 @@
@echo off
rem Rebuild TCPAGENT.EXE from C:\MTSRC and install it, in one EXEC.
rem BUILD.BAT only runs wmake in the current directory, which is not where a
rem host-driven `ferro-vm exec` starts. Copy this to C:\ and run it by path.
rem The running agent keeps the old image in memory, so reset the VM afterwards:
rem uv run ferro-vm reset
C:
cd C:\MTSRC\MTCP\APPS\TCPAGENT
set WATCOM=C:\DEVEL\WATCOMC
set PATH=C:\DEVEL\WATCOMC\BINW;C:\DEVEL\WATCOMC\BINP;C:\FREEDOS\BIN
set INCLUDE=C:\DEVEL\WATCOMC\H
set EDPATH=
if exist TCPAGENT.OBJ del TCPAGENT.OBJ
if exist TCPAGENT.EXE del TCPAGENT.EXE
wmake
if not exist TCPAGENT.EXE goto fail
copy /Y TCPAGENT.EXE C:\FREEDOS\BIN\TCPAGENT.EXE
echo BUILD-OK
goto end
:fail
echo BUILD-FAILED
:end
-11
View File
@@ -1,11 +0,0 @@
-0
-mc
-DCFG_H="tcpagent.cfg"
-oh
-os
-s
-zp2
-zpw
-we
-i=..\..\TCPINC
-i=..\..\INCLUDE
-12
View File
@@ -1,12 +0,0 @@
#ifndef CONFIG_H
#define CONFIG_H
#define MTCP_PROGRAM_NAME "tcpagent"
#include "Global.Cfg"
#define COMPILE_ARP
#define IP_FRAGMENTS_ON
#define COMPILE_UDP
#define COMPILE_TCP
#define COMPILE_ICMP
#undef TCP_MAX_SOCKETS
#define TCP_MAX_SOCKETS (1)
#endif
-303
View File
@@ -1,303 +0,0 @@
#include <bios.h>
#include <conio.h>
#include <dos.h>
#include <fcntl.h>
#include <io.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include "types.h"
#include "trace.h"
#include "utils.h"
#include "packet.h"
#include "arp.h"
#include "udp.h"
#include "tcp.h"
#include "tcpsockm.h"
#define LINE_SIZE 9000
#define CHUNK_SIZE 4096
#define SERVER_PORT 5558
#define LOCAL_PORT 2058
#define RECV_SIZE 12288
static char linebuf[LINE_SIZE];
static unsigned char data[CHUNK_SIZE+1];
static TcpSocket *socketp;
static volatile uint8_t stop_requested;
static FILE *put_file;
static unsigned long put_remaining;
static unsigned long put_started;
static char put_path[260];
static unsigned long ticks(void) {
long value=0;
_bios_timeofday(_TIME_GETCLOCK,&value);
return (unsigned long)value;
}
/* The BIOS tick is 18.2065 Hz, so one tick is 5.49254 hundredths of a second.
549/100 keeps the error under 0.05% and cannot overflow 32 bits for a delta
up to a full day (1573040 ticks * 549 fits). Plain delta/18 ran 1.1% fast. */
static void elapsed_text(unsigned long started,char *out) {
unsigned long now=ticks(),delta=now>=started?now-started:now+(1573040UL-started);
unsigned long hundredths=delta*549UL/100UL;
sprintf(out,"%lu.%02lus",hundredths/100UL,hundredths%100UL);
}
/* Open Watcom's DOS conio has no textattr()/textcolor() -- it offers only
cprintf/cputs/getch and friends -- and ANSI escapes are not interpreted on
the installed FreeDOS console. So let cprintf lay the line out (it scrolls
correctly) and then repaint the attribute bytes of the cells it just wrote.
The cursor sits at column 0 of the row after the line, which is what lets us
find those cells without tracking scrolling ourselves. */
#define TIMESTAMP_WIDTH 9
static void colorize(unsigned total,int attr) {
unsigned char __far *vram; unsigned cols,used,row,col,start,k;
if(*(unsigned char __far *)MK_FP(0x0040,0x0049)==7) return; /* MDA: no color */
cols=*(unsigned __far *)MK_FP(0x0040,0x004A);
if(cols<40||cols>132) cols=80;
used=(total+cols-1)/cols; if(!used) used=1;
row=*(unsigned char __far *)MK_FP(0x0040,0x0051);
if(row<used) return; /* line scrolled off the top; nothing to paint */
start=row-used;
vram=(unsigned char __far *)MK_FP(0xB800,0);
for(k=0;k<total;++k) {
row=start+k/cols; col=k%cols;
vram[((unsigned)row*cols+col)*2+1]=(unsigned char)(k<TIMESTAMP_WIDTH?0x08:attr);
}
}
static void log_line(int attr,const char *fmt,...) {
struct dostime_t now; va_list ap; char text[760];
_dos_gettime(&now); va_start(ap,fmt); vsprintf(text,fmt,ap); va_end(ap);
cprintf("%02u:%02u:%02u %s\r\n",now.hour,now.minute,now.second,text);
colorize(TIMESTAMP_WIDTH+(unsigned)strlen(text),attr);
{ FILE *f=fopen("C:\\TCPAGENT.LOG","a");
if(f){fprintf(f,"%02u:%02u:%02u %s\n",now.hour,now.minute,now.second,text);fclose(f);} }
}
static void init_log(void) {
FILE *f=fopen("C:\\TCPAGENT.LOG","rb"); long size=0;
if(f){fseek(f,0,SEEK_END);size=ftell(f);fclose(f);}
if(size>262144L){remove("C:\\TCPAGENT.OLD");rename("C:\\TCPAGENT.LOG","C:\\TCPAGENT.OLD");}
f=fopen("C:\\TCPAGENT.LOG","a");
if(f){fputs("--- TCPAGENT start ---\n",f);fclose(f);}
}
/* PUT completes either here in command_put (zero length) or in the receive loop
once the body arrives, so keep the one line both paths emit in one place. */
static void put_finished(void) {
char elapsed[24]; elapsed_text(put_started,elapsed);
log_line(0x0A,"< PUT %s OK %s",put_path,elapsed);
}
void __interrupt __far ctrl_break(void) { stop_requested=1; }
void __interrupt __far ctrl_c(void) { stop_requested=1; }
static void drive(void) {
PACKET_PROCESS_MULT(5);
Arp::driveArp();
Tcp::drivePackets();
}
static int send_all(const void *buffer, unsigned length) {
const uint8_t *p=(const uint8_t *)buffer;
unsigned sent=0;
int rc;
while(sent<length && !stop_requested) {
drive();
rc=socketp->send((uint8_t *)(p+sent),length-sent);
if(rc>0) sent+=(unsigned)rc;
else if(rc<0 || socketp->isRemoteClosed()) return -1;
}
return sent==length ? 0 : -1;
}
static void write_text(const char *s) { send_all(s,strlen(s)); }
static int hexval(int c) {
if(c>='0'&&c<='9') return c-'0';
if(c>='A'&&c<='F') return c-'A'+10;
if(c>='a'&&c<='f') return c-'a'+10;
return -1;
}
static int decode_hex(const char *src,unsigned char *dst,int cap) {
int n=0,hi,lo;
while(*src&&src[1]) {
if(n>=cap) return -1;
hi=hexval((unsigned char)src[0]); lo=hexval((unsigned char)src[1]);
if(hi<0||lo<0) return -1;
dst[n++]=(unsigned char)((hi<<4)|lo); src+=2;
}
return *src ? -1 : n;
}
static void write_hex(const unsigned char *src,unsigned count) {
static const char digits[]="0123456789ABCDEF";
char out[256]; unsigned i,n;
while(count) {
n=count>sizeof(out)/2 ? sizeof(out)/2 : count;
for(i=0;i<n;++i) { out[i*2]=digits[src[i]>>4]; out[i*2+1]=digits[src[i]&15]; }
send_all(out,n*2); src+=n; count-=n;
}
}
static void ok_data(const unsigned char *src,unsigned count) {
write_text("OK "); write_hex(src,count); write_text("\r\n");
}
static void error_text(const char *s) {
write_text("ERR "); write_hex((const unsigned char *)s,strlen(s)); write_text("\r\n");
}
static int decode_path(const char *hex,char *path,int cap) {
int n=decode_hex(hex,(unsigned char *)path,cap-1);
if(n<0) return 0; path[n]='\0'; return 1;
}
static void command_read(char *args) {
char path[260],*off=strchr(args,' '); FILE *f; long pos; size_t count; int eof;
if(!off){log_line(0x0C,"< READ ERR missing offset");error_text("READ requires path and offset");return;} *off++='\0';
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< READ ERR bad path encoding");error_text("Invalid path encoding");return;}
pos=atol(off); log_line(0x0B,"> READ %s @%ld",path,pos);
f=fopen(path,"rb"); if(!f){log_line(0x0C,"< READ ERR cannot open");error_text("Cannot open file");return;}
if(fseek(f,pos,SEEK_SET)){fclose(f);log_line(0x0C,"< READ ERR cannot seek");error_text("Cannot seek file");return;}
count=fread(data,1,CHUNK_SIZE,f); eof=count<CHUNK_SIZE; fclose(f);
log_line(0x0A,"< READ %uB eof=%d",(unsigned)count,eof);
sprintf(linebuf,"OK %d ",eof); write_text(linebuf); write_hex(data,count); write_text("\r\n");
}
static void command_write(char *args) {
char path[260],*mode=strchr(args,' '),*payload; FILE *f; int count;
if(!mode){log_line(0x0C,"< WRITE ERR missing mode");error_text("WRITE requires path, mode and data");return;} *mode++='\0';
payload=strchr(mode,' '); if(!payload){log_line(0x0C,"< WRITE ERR missing data");error_text("WRITE requires data");return;} *payload++='\0';
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< WRITE ERR bad path encoding");error_text("Invalid path encoding");return;}
count=decode_hex(payload,data,CHUNK_SIZE); if(count<0){log_line(0x0C,"< WRITE ERR bad data encoding");error_text("Invalid data encoding");return;}
log_line(0x0B,"> WRITE %s %c %dB",path,mode[0]=='A'?'A':'T',count);
f=fopen(path,mode[0]=='A'?"ab":"wb"); if(!f){log_line(0x0C,"< WRITE ERR cannot open");error_text("Cannot write file");return;}
if(count&&fwrite(data,1,count,f)!=(size_t)count){fclose(f);log_line(0x0C,"< WRITE ERR short write");error_text("Short write");return;}
fclose(f); log_line(0x0A,"< WRITE OK"); ok_data((const unsigned char *)"",0);
}
static void command_put(char *args) {
char path[260],*length_text=strchr(args,' ');
if(!length_text){log_line(0x0C,"< PUT ERR missing length");error_text("PUT requires path and length");return;}
*length_text++='\0';
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< PUT ERR bad path encoding");error_text("Invalid path encoding");return;}
put_remaining=strtoul(length_text,0,10); strcpy(put_path,path); put_started=ticks();
log_line(0x0B,"> PUT %s %luB",put_path,put_remaining);
put_file=fopen(path,"wb");
if(!put_file){put_remaining=0;log_line(0x0C,"< PUT %s ERR cannot open",put_path);error_text("Cannot write file");return;}
if(!put_remaining){fclose(put_file);put_file=0;put_finished();ok_data((const unsigned char *)"",0);}
}
static void command_get(char *args) {
char path[260],elapsed[24]; FILE *f; long length; size_t count; unsigned long started=ticks();
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< GET ERR bad path encoding");error_text("Invalid path encoding");return;}
log_line(0x0B,"> GET %s",path);
f=fopen(path,"rb"); if(!f){log_line(0x0C,"< GET %s ERR cannot open",path);error_text("Cannot open file");return;}
fseek(f,0,SEEK_END); length=ftell(f); fseek(f,0,SEEK_SET);
sprintf(linebuf,"DATA %ld\r\n",length); write_text(linebuf);
while((count=fread(data,1,CHUNK_SIZE,f))>0) if(send_all(data,count)<0)break;
fclose(f); elapsed_text(started,elapsed);
log_line(0x0A,"< GET OK %ldB %s",length,elapsed);
}
static void command_hash(char *args) {
char path[260],elapsed[24]; FILE *f; size_t count; unsigned i;
unsigned long length=0,hash=2166136261UL,started=ticks();
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< HASH ERR bad path encoding");error_text("Invalid path encoding");return;}
log_line(0x0B,"> HASH %s",path);
f=fopen(path,"rb"); if(!f){log_line(0x0C,"< HASH %s ERR cannot open",path);error_text("Cannot open file");return;}
while((count=fread(data,1,CHUNK_SIZE,f))>0){length+=(unsigned long)count;for(i=0;i<count;++i){hash^=data[i];hash*=16777619UL;}}
fclose(f); elapsed_text(started,elapsed);
log_line(0x0A,"< HASH %luB %08lX %s",length,hash,elapsed);
sprintf(linebuf,"STAT %lu %08lX\r\n",length,hash); write_text(linebuf);
}
static void command_list(char *args) {
char path[260],pattern[300],output[CHUNK_SIZE]; struct find_t found;
unsigned used=0,entries=0; int rc,truncated=0;
if(!decode_path(args,path,sizeof(path))){log_line(0x0C,"< LIST ERR bad path encoding");error_text("Invalid path encoding");return;}
log_line(0x0B,"> LIST %s",path);
strcpy(pattern,path); if(pattern[0]&&pattern[strlen(pattern)-1]!='\\') strcat(pattern,"\\"); strcat(pattern,"*.*");
rc=_dos_findfirst(pattern,_A_NORMAL|_A_RDONLY|_A_HIDDEN|_A_SYSTEM|_A_SUBDIR|_A_ARCH,&found);
while(rc==0) { char entry[100]; int len;
if(strcmp(found.name,".")&&strcmp(found.name,"..")) { sprintf(entry,"%s\t%lu\t%s\n",found.name,found.size,(found.attrib&_A_SUBDIR)?"DIR":"FILE"); len=strlen(entry); if(used+(unsigned)len>=sizeof(output)) {truncated=1;break;} memcpy(output+used,entry,len); used+=(unsigned)len; ++entries; }
rc=_dos_findnext(&found);
}
log_line(truncated?0x0E:0x0A,"< LIST %u entries%s",entries,truncated?" (truncated)":"");
ok_data((unsigned char *)output,used);
}
static void command_exec(char *args) {
char command[700],elapsed[24]; const char *tmp="C:\\PIEXEC.TMP"; FILE *f;
int n,code=-1,fd=-1,save1=-1,save2=-1; long length=0; size_t count; unsigned long started;
n=decode_hex(args,(unsigned char *)command,sizeof(command)-1); if(n<0){error_text("Invalid command encoding");return;} command[n]='\0';
started=ticks(); log_line(0x0E,"> EXEC %.640s",command);
fflush(stdout); fflush(stderr);
save1=dup(1); save2=dup(2);
fd=open(tmp,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY,S_IREAD|S_IWRITE);
if(save1<0||save2<0||fd<0||dup2(fd,1)<0||dup2(fd,2)<0) {
if(fd>=0)close(fd);
if(save1>=0){dup2(save1,1);close(save1);}
if(save2>=0){dup2(save2,2);close(save2);}
log_line(0x0C,"< EXEC ERR redirect failed"); error_text("Cannot capture command output"); return;
}
close(fd); code=system(command); fflush(stdout); fflush(stderr);
dup2(save1,1); dup2(save2,2); close(save1); close(save2);
f=fopen(tmp,"rb");
if(f){fseek(f,0,SEEK_END);length=ftell(f);fseek(f,0,SEEK_SET);}
elapsed_text(started,elapsed);
log_line(code?0x0C:0x0A,"< EXEC exit=%d %ldB %s",code,length,elapsed);
sprintf(linebuf,"RESULT %d %ld 0\r\n",code,length); write_text(linebuf);
while(f&&(count=fread(data,1,CHUNK_SIZE,f))>0)if(send_all(data,count)<0)break;
if(f)fclose(f); remove(tmp);
}
static void process_line(char *line) {
char *cmd=line,*args=strchr(line,' '); if(args)*args++='\0';else args=cmd+strlen(cmd);
/* PING is deliberately not logged: wait-ready polls it twice a second. */
if(!strcmp(cmd,"PING"))ok_data((const unsigned char *)"PONG",4);
else if(!strcmp(cmd,"READ"))command_read(args);
else if(!strcmp(cmd,"WRITE"))command_write(args);
else if(!strcmp(cmd,"PUT"))command_put(args);
else if(!strcmp(cmd,"GET"))command_get(args);
else if(!strcmp(cmd,"HASH"))command_hash(args);
else if(!strcmp(cmd,"LIST"))command_list(args);
else if(!strcmp(cmd,"EXEC"))command_exec(args);
else if(!strcmp(cmd,"QUIT")){log_line(0x07,"* QUIT received");ok_data((const unsigned char *)"BYE",3);stop_requested=1;}
else { char message[96]; sprintf(message,"Unknown command: %.70s",cmd); log_line(0x0C,"< ERR %s",message); error_text(message); }
}
static int connect_host(void) {
IpAddr_t host={10,0,2,2}; int8_t rc;
socketp=TcpSocketMgr::getSocket(); if(!socketp)return -1;
socketp->setRecvBuffer(RECV_SIZE);
rc=socketp->connect(LOCAL_PORT,host,SERVER_PORT,10000);
while(rc==0&&!socketp->isConnectComplete()&&!socketp->isRemoteClosed()&&!stop_requested)drive();
if(socketp->isRemoteClosed()){TcpSocketMgr::freeSocket(socketp);socketp=0;return -1;}
return 0;
}
int main(void) {
int used,rc; uint16_t key; unsigned attempts=0;
init_log();
log_line(0x0B,"* TCPAGENT starting; Alt-X returns to DOS");
if(Utils::parseEnv()!=0){log_line(0x0C,"* MTCP configuration error");return 2;}
if(Utils::initStack(1,TCP_SOCKET_RING_SIZE,ctrl_break,ctrl_c)){log_line(0x0C,"* TCP stack initialization error");return 3;}
while(!stop_requested) {
if(!attempts)log_line(0x07,"* connecting 10.0.2.2:%u",SERVER_PORT);
if(connect_host()!=0){unsigned long spins=0;++attempts;if(attempts==1||attempts%10==0)log_line(0x0C,"* connect failed; retry %u",attempts);while(spins++<60000UL&&!stop_requested)drive();continue;}
attempts=0; log_line(0x0A,"* connected; host automation owns console");
write_text("TCPAGENT READY\r\n"); used=0;
while(!stop_requested&&!socketp->isRemoteClosed()) {
drive(); rc=socketp->recv((uint8_t *)data,CHUNK_SIZE);
if(rc<0)break;
for(int i=0;i<rc;++i) {
int c=data[i];
if(put_remaining) {
unsigned available=(unsigned)(rc-i);
unsigned take=put_remaining<available ? (unsigned)put_remaining : available;
if(fwrite(data+i,1,take,put_file)!=(size_t)take){fclose(put_file);put_file=0;put_remaining=0;log_line(0x0C,"< PUT %s ERR short write (disk full?)",put_path);error_text("Short write");}
else {
put_remaining-=take; i+=(int)take-1;
if(!put_remaining){fclose(put_file);put_file=0;put_finished();ok_data((const unsigned char *)"",0);}
}
} else if(c=='\r'||c=='\n') {
if(used){linebuf[used]='\0';process_line(linebuf);used=0;}
} else if(used<LINE_SIZE-1) linebuf[used++]=(char)c;
}
if(_bios_keybrd(1)){key=_bios_keybrd(0);if((key&0xff)==3||(key>>8)==45)stop_requested=1;}
}
socketp->close(); TcpSocketMgr::freeSocket(socketp); socketp=0;
if(!stop_requested)log_line(0x0C,"* link lost; retrying");
}
log_line(0x07,"* stopped; returning to DOS");
Utils::endStack(); return 0;
}
Generated
+1 -598
View File
@@ -2,152 +2,6 @@ version = 1
revision = 1 revision = 1
requires-python = ">=3.12" requires-python = ">=3.12"
[[package]]
name = "antlr4-python3-runtime"
version = "4.9.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d19459005ca000b6e7012f2f1ca597746cbcd1fbfe5e/antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b", size = 117034 }
[[package]]
name = "certifi"
version = "2026.7.22"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/c2/24167ea9858356b47a87a50d39908bfdb72ceeefe0041586e704e5376b3a/certifi-2026.7.22.tar.gz", hash = "sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55", size = 138112 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0b/a7/71ac2cff56fec219ed242bb11b8efb69fcc4bec75db06fb7bfe35de520e6/certifi-2026.7.22-py3-none-any.whl", hash = "sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775", size = 136983 },
]
[[package]]
name = "charset-normalizer"
version = "3.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e5/3f/143b048436775b0f76ac3eec145c019e8173ccc2885c8f20319b996d5e83/charset_normalizer-3.5.1.tar.gz", hash = "sha256:6117b84ea48435e5356dc737f5121485c30920ba43375fa7b434fd753df0eac3", size = 171764 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/30/27/78873dc8b6a56357517b74b6bb9568b80450e7bb4f6ef7e3fa9d22aa0bd7/charset_normalizer-3.5.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5b6d1386bf0096d26d3a863dc0a487a5b4eb9aa93cf5ba69683d29dde6b9d60f", size = 344456 },
{ url = "https://files.pythonhosted.org/packages/9a/4c/be49ada26b1f0232d57aa89bbebf997a5cc2332a5616b6eca26ff680044d/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4582c27e8c889d64811987b5967fbd3ae0c823fe1fd933b543d55ac20bb475fa", size = 238530 },
{ url = "https://files.pythonhosted.org/packages/76/84/6f1290fa07ae6978d3960caa3eb1b8019bf9284ab7c2297b00c099ef4250/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1d1c7a53a6c2103925cdd6d7229f8c567379f211c869793df679f2e9f738c369", size = 230200 },
{ url = "https://files.pythonhosted.org/packages/e7/a0/47b18adeed31c8f16ba9700f32c1b18594cfa09f47eb672a488c273c22bf/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e6621fb2a4988d6e53eedc455e5903e2679f3967b8acb3d639f1b63c14a2e893", size = 262222 },
{ url = "https://files.pythonhosted.org/packages/38/fe/341861ac118dae06f3ec0eb487488af52128f2ef2faf0b11003944d22259/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7c0c10730342b0c9b35dd1d619beb8214e520bd96a1f870f452680b238aab3e0", size = 258951 },
{ url = "https://files.pythonhosted.org/packages/6f/89/bb5108dc6c3651dca963f2b0a3ba19bbcb370c94e1b6d3e0e844a58e6dca/charset_normalizer-3.5.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b9af956078716df40d985fb0dfeb2c2120c5ca92ba4ff4b388acfd01cdc14d08", size = 248801 },
{ url = "https://files.pythonhosted.org/packages/b1/ba/ef83ae3aca816393decfa3530976f38a79812d707b80b580ac33b83f9877/charset_normalizer-3.5.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9f8405c2c758532c74fed975dbee57be1f31a6e865c031870c79a6ed3212ada", size = 244070 },
{ url = "https://files.pythonhosted.org/packages/f6/0b/c5292a2462d69b7378ea89793bbb5b2b6fcf6f7dd6d1667f9619094ad553/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:96fef3e886d6a9874b14f27fc193fbdc69d5d8035783d86aa4e1cea594e695f9", size = 240110 },
{ url = "https://files.pythonhosted.org/packages/46/22/111e5be3b740d5c2a5bfcedb3d237b6591e5c2e82ae9d6ffcb121fe0909c/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5d8531a6569d025f68e2321e7638fb7978f23db58e5f69f56913837aae03816e", size = 232836 },
{ url = "https://files.pythonhosted.org/packages/f9/d2/d2aad6fe0dbb44b194bf3becb60f5a0ac48446ade999a47fe7bb41eb09a7/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aae2ee51122d3ae968a3837d97dc24a0aeebb0dea23694422cd172bd30017cd6", size = 262712 },
{ url = "https://files.pythonhosted.org/packages/35/5a/337e4663a5eae6de99db940ee8066d4145caafb61327db62deda15313cce/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7235dc28fc6dd9d832ac7c7bce95367dedb85929f17368a0c2bee1e080b9acbf", size = 242977 },
{ url = "https://files.pythonhosted.org/packages/ca/85/f82f8a92e31c7519410e2e1afdc630f28ec47490ce2c09a11c1a43cbb459/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4abdc5f9ad448c1ecbfae2974b820535d6bc6e7eef63babbab3d81cf46968c71", size = 260207 },
{ url = "https://files.pythonhosted.org/packages/b7/52/643d11ffd60e9ac2fd1fb87e167a19285b9eefeff4a40e63c87cbfbeab36/charset_normalizer-3.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ba501e667c17d8411f98e67a022d9604ef179aff0e459b7e292c796837c13573", size = 250562 },
{ url = "https://files.pythonhosted.org/packages/62/16/46556278c2168d12df9da7fede5dc6fc70e60301b26a82bbeec238c9cfe3/charset_normalizer-3.5.1-cp312-cp312-win32.whl", hash = "sha256:cfa1c0cc3a8f9f53f1243a5a99ac36fd003880199383b37672e86ddda9cb07e2", size = 178507 },
{ url = "https://files.pythonhosted.org/packages/9d/7a/4c6c298171e6b3e745633180ff59350fc0ca0db1ffd28df1e369e0579f71/charset_normalizer-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:3617ac3cfd8b9888f145ad89dd6e692285834b0201c6074a5eeaad3fd4d668c2", size = 200551 },
{ url = "https://files.pythonhosted.org/packages/cd/d7/eb95a042f0dd22e304b0b6472b154f3546a1a039a9ee89ccb2a7f61591fc/charset_normalizer-3.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:88e85ab89cb822c1e635f51d6d32e488f94e002e70e2f492bdb8b945543f345a", size = 180700 },
{ url = "https://files.pythonhosted.org/packages/bc/61/2cb6ad133dbbb449fa2d37ccae973232f4827e799af258d15e589a3d1e9e/charset_normalizer-3.5.1-cp313-cp313-android_24_arm64_v8a.whl", hash = "sha256:4f298bdadb8f0b9e5672877f647d1be9373ef5320c9e2f049795e26cad28b6a9", size = 211584 },
{ url = "https://files.pythonhosted.org/packages/18/57/a305c968be1ca13f3dd1b32f445877e97addf55d80b65c7cb35fac82b777/charset_normalizer-3.5.1-cp313-cp313-android_24_x86_64.whl", hash = "sha256:88ca277405c2d3b71c4e1c2ee0e7966e807bcba86a69d11e19ba199d18ae4491", size = 223359 },
{ url = "https://files.pythonhosted.org/packages/09/0a/d3646670292ce8d8f8cc11ac067d44885e697a5591f57a9221128da5e7b3/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9362dd90aa7dab48c0054a21187791ccf05473f7dba5d92b8033ae62164675e7", size = 194464 },
{ url = "https://files.pythonhosted.org/packages/de/93/d51ec556e01042fed6f993ea859311bc7917b466684182fbbceb6ca24762/charset_normalizer-3.5.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:977cdbd483a9cff38179bea4fd754289a6f2195c7abd414aba85410b3e66cc5e", size = 197676 },
{ url = "https://files.pythonhosted.org/packages/a4/a0/562247944386f7d4ef94467e84876600cc1e0f1b93239aaa9213d2bc3cbd/charset_normalizer-3.5.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e90251c0c7bdd54a100a0dce3c07b7e637278c93af29dbf78ebb89a58c4bac7d", size = 340473 },
{ url = "https://files.pythonhosted.org/packages/31/e7/1d994be1b93d41e9502b8b0460eaa88a1dd8df335df415db87d6c3e91ab2/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94d78ecec2605a8d0398b0f365d5f12a63248438516f5dac536a5eff7337df4a", size = 240156 },
{ url = "https://files.pythonhosted.org/packages/09/53/27923ce5cc6cbccb832037b27dca98882d9c53e9b69e866bbbef4aae7fc8/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d59b75732e9b6f27388e10c14b0259cc5f2e48c78627d185e6a177b58ad3cffe", size = 228246 },
{ url = "https://files.pythonhosted.org/packages/ce/48/5a97e84d63af1d55c07439cb80e56d99a8efb4295700eb4e18c0d1615d2c/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0d929fc574b4d6fd9e7c0f5c2ede8716a41911923aa7fa5fce38e0818aa4a1ac", size = 263660 },
{ url = "https://files.pythonhosted.org/packages/7a/c2/071575791dcc88316c0a9a65ce38897a82e4cfe4a325f0f7fe1b1ac47bcf/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:394fea06235c8543390050ed5f529187074b029fb027213f6c46ac11ab5d950e", size = 260354 },
{ url = "https://files.pythonhosted.org/packages/fb/af/63240b0c0248c075c2535a1f1bd992821d8251b9f173abc13329661d09e4/charset_normalizer-3.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62b55f6722735a6c472f88361cde6640608773d9443cebdbb51abf436a1fcdd3", size = 250638 },
{ url = "https://files.pythonhosted.org/packages/4d/66/70dfad64f15be09c15ccfee81330a7e515895dbe296dd23114e9a231268a/charset_normalizer-3.5.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa48b1b63d639f9483e0633e092f5851e2348c352f1f9bb6c8182f87884ef876", size = 244583 },
{ url = "https://files.pythonhosted.org/packages/c0/24/ef36367d38b9ddd4bccbf72888c342e8de1f5ae506fa0b2dcf970e2732a1/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c71fb0d56c920c269cd3e2e3fe7c610e3f1fdb21a6ce60efa6430ff63676cea6", size = 242038 },
{ url = "https://files.pythonhosted.org/packages/db/ab/55e683ba0fff2e43adafc10daa3001eac90fdaa419a97227d5a7067eedde/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:485a0d363cafefcd2538a73c7c838daa2035f09b2c9f9b5e3133f80c6aeb84c2", size = 233677 },
{ url = "https://files.pythonhosted.org/packages/bd/67/0f40eaf8d1b6e7cf15e82382a2965efaca787fc1c2794b7021d37aaf5036/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0ea61a470e070686aa30892fed79e297d2c8d0ab46b8bcdf027d38c51da591", size = 264491 },
{ url = "https://files.pythonhosted.org/packages/5c/64/12b4c2a11ee8df4fcc518c78b0d93e3a92bd3d5253d1617ce74ff0e8c7ef/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:90b7481fb62fbe172c558bc6fd1c4c98d82004a54a7551f20e11ac9bf0b8708c", size = 245196 },
{ url = "https://files.pythonhosted.org/packages/37/2e/651d910af6d0fba325eee1cda37ec5443462ed25360e666c144166eb6091/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:35fe081843b35aad20ffeccec3eeffbe637b15d14f3fb22cc1b59cd8ec17e93c", size = 261660 },
{ url = "https://files.pythonhosted.org/packages/90/c6/b09e05e6db7f64338e0dc067c79577b1138da86c1e38369096851d96be88/charset_normalizer-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd0350afdc3aabd5576f60ea109228bd5538139713c7b094c5cd27c73a98bc6f", size = 252618 },
{ url = "https://files.pythonhosted.org/packages/76/4e/362d4f9fdcdf5556fb2aa3ce7d4a58ebce03ed1ff03aa1d9aca8d02f13f3/charset_normalizer-3.5.1-cp313-cp313-pyemscripten_2025_0_wasm32.whl", hash = "sha256:9d9a0dc7cbe9bec24c3f767c9122c41fe5a1bc43f47cd099d00d393e09769de4", size = 140362 },
{ url = "https://files.pythonhosted.org/packages/b4/d4/703be739b26acce318bd29eb3b25b7209e1b1f527f9eae3d1f1f01fdde2b/charset_normalizer-3.5.1-cp313-cp313-win32.whl", hash = "sha256:d63600d620ad0064c3a748b950ac5ea38a80190e5498532efefa4b7b3f1da1f3", size = 177755 },
{ url = "https://files.pythonhosted.org/packages/8a/33/56d97ade41c8db611e727168c52ae46c9224c362ec28d4b65d7e9869e8da/charset_normalizer-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:aea996a6aba25260827c9ea511d1addfde2da9eb686ac961838509086188b7e6", size = 199295 },
{ url = "https://files.pythonhosted.org/packages/5b/75/5b20dd1e6573a01a08158fe104104fa2c8abf941745596954185726cd46c/charset_normalizer-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:fd0a274c0e5f9a21565cd9d3dd749b61f96b7aa1e20a93aa1ba4029518f2e5c0", size = 179856 },
{ url = "https://files.pythonhosted.org/packages/29/cd/2b812ce5e888f1ce69a5350281e58aab07ae64a958ecae8912f30865718e/charset_normalizer-3.5.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:774d157f112367ff4abd29019f38f023c24e00e56edc7829c20e358a5a913ad8", size = 212318 },
{ url = "https://files.pythonhosted.org/packages/9e/4a/a6ee107430768a5334e6d63f31f148a04a1a491ef161a1ac9415a73f2fa8/charset_normalizer-3.5.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:26422d45fd13551cf564c58932f7d72b4f58b93b0fcf18c35ba6be12b46bb102", size = 224897 },
{ url = "https://files.pythonhosted.org/packages/c3/d9/35ae3f64f29d0179c35c3baefe575904df2913dde519129c7f75995a2b1d/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:09a7bba9f739468c8e78c36a75c33768e53cb1959fc638f510454c14683f00d5", size = 194848 },
{ url = "https://files.pythonhosted.org/packages/74/76/f2fc7380f056cc273a53af37f50d08ad54b2c59f61078f31432edcf1c2bd/charset_normalizer-3.5.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4c9548dc78002099910abaebc0a72ac58b7d30931869e0351c09b507dff4ece3", size = 198163 },
{ url = "https://files.pythonhosted.org/packages/e9/40/095ce62fa078483cccc1fa2b36e6bc9580b85422a20ee9f925341c50e44f/charset_normalizer-3.5.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:c428c6c31eb5f4277d7f8eccaf767fbd548ddd5ce3c8b4f4cbbfab3d96b5904c", size = 341823 },
{ url = "https://files.pythonhosted.org/packages/f1/5a/0e58b1c04a1596e0256f407274a92d5fb2ee21324409d1fab1da48a65b5b/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f06b7eae9dbe77fe1d644ca244dad508de8d302870a43f3c559b521270938a0", size = 242458 },
{ url = "https://files.pythonhosted.org/packages/22/95/b4618ce912e6db0b1aae89ba788e38e8a7eba0f3025cc66e8c0699f977b2/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b7430cf5728e68f6c462254009a6ef4086e1bea43cf2f57aa9c55fb4f50ff96", size = 226717 },
{ url = "https://files.pythonhosted.org/packages/8a/76/c681192bbda3d55356db5dadd64381d5202b37c6b598fcda5282e88b5d3d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ab743e9bc90c1f73552ec33e10e3331315acd2c397b36065b591b0181de533cc", size = 266111 },
{ url = "https://files.pythonhosted.org/packages/88/be/55127bfca72c0cff6c022488d140d7c5b04c771e3b72e9bdb4836d54979d/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6f7deae3feb4edfa2efaf7c574fe88cbf055038a6abdb40188e4fff66d5699f", size = 263128 },
{ url = "https://files.pythonhosted.org/packages/e0/91/39c3af510b0aa32bbda03374259200f28430febfd1bf5e511fe765282ce5/charset_normalizer-3.5.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15f024313246a4ed976c60f440bb8d257815513a681d212ff74fd46f7d715a90", size = 251240 },
{ url = "https://files.pythonhosted.org/packages/1c/a5/cbe418bbc6ecdfc3e05a0116002897c4b403a5e838d697e64c78e9f0190d/charset_normalizer-3.5.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:823f82903d189af463d7df250ef1f7f696f3cee08cc8d91deb565e8d425f6506", size = 245282 },
{ url = "https://files.pythonhosted.org/packages/cc/a4/689bb42e8e7cd492f3cb64907c6bc00ad247ec9a3628cd3f8eed126e8ae1/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:01e93745f7f219b703b60ba7afead36cfc4242782be5af484673fc500df12da5", size = 244597 },
{ url = "https://files.pythonhosted.org/packages/c1/ce/9962938e179cf9f699d3f1e7b3114b5d7642dee6a893745229f9dd04f274/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:329fc3ccb63ad22d867d84c2adea759a64079a37ba4a343433b02c7a2816871e", size = 231376 },
{ url = "https://files.pythonhosted.org/packages/85/54/46000450ada53bd9eac5429a2c8c54cd2d9b39c0c255f229aea9af0948a5/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:bb57753e36e4855b8ca375069482250a6246372331a3e4f3407eaebb007443f5", size = 266715 },
{ url = "https://files.pythonhosted.org/packages/3d/bb/618749d70f792b44252a777bf89bfb86823b9bbc1ea13fe8ce759b07f38a/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fce8cbd4997efeb450bd298b54f755dcdff18d496f7a5ddbb4867c6d7c88fdc3", size = 245848 },
{ url = "https://files.pythonhosted.org/packages/7e/3f/ffb64458527c7668031d5eb095d978de561958dc9f5b53f8e488a533e603/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:6c9cdde8becb25a7fde49924511aa2644d6f8081cc8df8e9452724303348d8e3", size = 264521 },
{ url = "https://files.pythonhosted.org/packages/4f/ab/74a55fd803916a35ac461daf002708191aac19b546b80dc8cabfedc63d98/charset_normalizer-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:9ac4444d8d4fd4c4bd08bf451ed3167aa9e7ec6cdb41b648794f1d1103652e36", size = 253054 },
{ url = "https://files.pythonhosted.org/packages/a0/2a/6a9034b7d3c60b17499afb482df5878bf9fa20b50cc3887d5ef017a833db/charset_normalizer-3.5.1-cp314-cp314-pyemscripten_2026_0_wasm32.whl", hash = "sha256:f03ac127268b43ef4fe9e6ab6794a6794b49485a0cc0c1db79876d2f33f75bc7", size = 140580 },
{ url = "https://files.pythonhosted.org/packages/f3/46/1d362e1a00d035d66b9869e1281eee115907f7e390a16a07824ab5737360/charset_normalizer-3.5.1-cp314-cp314-win32.whl", hash = "sha256:1f5883d77fd409a261abb5dc8ccbe335720d798b1de4abb3b1d47ccbbc76b53b", size = 180325 },
{ url = "https://files.pythonhosted.org/packages/7a/7c/4938c329b6a9d446f6a59aa2092ff7118f274209b5ed0e26893d1d30a63c/charset_normalizer-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:c658c50ac0c98cd755a2dd50b7977d3bca7df401dcc47fbdfa87db53ef7d4e8b", size = 204175 },
{ url = "https://files.pythonhosted.org/packages/ac/33/eeb384dbd8dec570661354592f4f2e1b2fcc92585624d146a000caf53841/charset_normalizer-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:4bea7f8ebe90bbd7f0e4a2de42ca6924ba23e3e76418c408ff82f1d46fabd687", size = 184123 },
{ url = "https://files.pythonhosted.org/packages/1c/6c/c73fa9d5a85f6ab05395de61c5f6984e0a9ff40bb5ff888d46dff02526c6/charset_normalizer-3.5.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fbc597639158fd7c14d55e808718848319540f51b0e6746e3eefa59723a4a348", size = 381682 },
{ url = "https://files.pythonhosted.org/packages/30/c7/63565f860921457feba93bae6c86fb7746deb4cffeed2f375cb845318146/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71c909f353863b2b89c83de2ebed71ea6d0df8a6ef65a128193c5e650766bef", size = 240826 },
{ url = "https://files.pythonhosted.org/packages/06/ae/7ae8807410dfa33f8e6f1715740adeaafa8a816cc4cb33508f54b1f7c896/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7ac76cf9afd34929d76eb7fcb63be476a4853d8a96f0dcf2d0db68a0cbdf9885", size = 227861 },
{ url = "https://files.pythonhosted.org/packages/e9/a3/887c1642f0da26000b0e0652d91071113c0e72cea33952e225cf589f49a9/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a3a370082ce34d0612f421e15fe011c53bb1feff21a26d06ad4fb244dab5a375", size = 260758 },
{ url = "https://files.pythonhosted.org/packages/3e/11/e6f5b9a3d0e55b0ef7505cd3765cdd48f22db89994c947b316f52f801fd8/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:256dd4d85d9e4dc595e2bc983c980e73f62ddeb3165c58b4c3dfe78c5c8548c1", size = 259950 },
{ url = "https://files.pythonhosted.org/packages/1b/ee/e4e10a94d51cd1ee638aa7e00b65399e6b2a4e8376ab6d2eac9f95586671/charset_normalizer-3.5.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:58d4aa13a59c969dbfdf9e6a9560e242cbfd9e8a8f50c2747714df1a423adf65", size = 249329 },
{ url = "https://files.pythonhosted.org/packages/c4/25/d5f4198819e6059735a84e8d0bfb72dc33976da67b97adcd3fb5a5e07ec6/charset_normalizer-3.5.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0c6dfb5ca6723eeed15aa8e564a014d69fcb8812f94eef11fe3631e0508199f5", size = 243137 },
{ url = "https://files.pythonhosted.org/packages/a5/e9/e925ca7569cf9fb9701fd82503fee73eea5268fdb856bdd64947092d3daa/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c010f5581d9c612804cc59fcf7b524b707fbcb72828551237ab545bb5c7034af", size = 242820 },
{ url = "https://files.pythonhosted.org/packages/34/17/672c251a888ed2aebcdd2fe830ad0104e25ff83c43f5c4f9c15e9fc6853c/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:52ec005752a56ae79547a05c0139ca2501a0c866390b6115008456b9f0e7cde1", size = 230504 },
{ url = "https://files.pythonhosted.org/packages/3f/fc/f6a85abebd42ce4da2f1db0aa56cc6a0df1995e318b3875d14401b8381d1/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2bced4061f000f7187254a02ad3433ae17eaf991747ceea2f478422590a5bba9", size = 263087 },
{ url = "https://files.pythonhosted.org/packages/98/66/7c42677e739ba66746b297e2046918d793078094dc239e1e72768cffccc6/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:9eea3ab2597a5e65fe65296e2d6a84570845a6b55532d90333d740d48bbc850a", size = 243269 },
{ url = "https://files.pythonhosted.org/packages/de/d8/a50b79237f417af10f8c2a501ce8d1ca87829a22e69117891ca4ba20a69e/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:496846868fea80e479324862fa877f02411f2fd0f83b79ccee2607aa68b2a032", size = 258766 },
{ url = "https://files.pythonhosted.org/packages/2e/1d/0fc91aeaeb3c83b748f532399ce67cf84604b48297405d740000f7a9e786/charset_normalizer-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:85d5855daafc240cc045c026d7a15fd198a09b0fc8ff6f5ecbb5297b509cb11e", size = 250814 },
{ url = "https://files.pythonhosted.org/packages/ae/10/3d8c777cf9024615295aa1b808324ad5b4a77855869c00824bad74ffaf8a/charset_normalizer-3.5.1-cp314-cp314t-win32.whl", hash = "sha256:58d3e12c88e0950bca850ae1f7c256055c097639c2edb9eb123af9807d8b15e4", size = 191074 },
{ url = "https://files.pythonhosted.org/packages/4d/81/ae557d3c44d1a1d688696d60563413a0866a91b7ebc50f20df838be3d8c8/charset_normalizer-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:acaf604462bf330b0d07e7a07c1d6e4adac79e5fb13e9c5140590542cafacc00", size = 216476 },
{ url = "https://files.pythonhosted.org/packages/27/e9/61c01fb8b804692569c036b3fc50495814502dcf13a60649c6055390b02c/charset_normalizer-3.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:fdb8a068947befafba9952162645dc2fecaeb400e64584829ed5e9b2fbe21a7f", size = 194115 },
{ url = "https://files.pythonhosted.org/packages/4a/4e/8544831ef59d8f27ce92c80871380fdacc8076a8a56ed62f82e54f991333/charset_normalizer-3.5.1-cp315-cp315-macosx_10_15_universal2.whl", hash = "sha256:9085f87b0e38a2b92b8923059b4e8789fe40d9279712d15dcc670048d77079af", size = 342048 },
{ url = "https://files.pythonhosted.org/packages/7f/a6/e3b46852424246065355644f4fb6dbccc0239a42a2eee27ecfc8957f0bcd/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2679de311c7946dde5d3b6f44941844133ff5c7cb86099c0061ab1e8901c20a8", size = 242997 },
{ url = "https://files.pythonhosted.org/packages/03/3b/0cc9a26777334ab2f2e3089b948bbf4e4fe72ea70b897715ef6415043ec8/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:baf3775a2635e5a11fbd5e4e64ee69c7e86875d224a5c72aca4c141064589a90", size = 237014 },
{ url = "https://files.pythonhosted.org/packages/8c/c2/027335f0aa337a2a2e121bac1ad88c4f02ba6053ea0926802784f3db11af/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ac8c94b6539074e0f40899301273ac8402b9b3e01c7b7ba269ff30340aaaf20", size = 266174 },
{ url = "https://files.pythonhosted.org/packages/86/d3/e367787febe4e74769dec0f406f2c3c8d1b955fce5aee1fd0f94e8367a45/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe532b3c966d1fb794e0698e4589d0444017ae77fc0b31edea13c0e35bcc449", size = 263361 },
{ url = "https://files.pythonhosted.org/packages/af/3d/391b193eb9f3e84b02f9314088c386debdc0debee843535aaea2e2c6715d/charset_normalizer-3.5.1-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5c84bec0ab5ae0c64bfe73a7d2adcb5ce73b467523fc27fd6a28ab2aa6cbe35a", size = 252143 },
{ url = "https://files.pythonhosted.org/packages/2e/57/de221f1745a90d418199761967e2776bfe2c275a1194220985e8c1d37833/charset_normalizer-3.5.1-cp315-cp315-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:854066be00447fa8de2ccbbe893e2ffc4b123ef16d897af794c1e18bd4a714b0", size = 252086 },
{ url = "https://files.pythonhosted.org/packages/c8/e3/d119f86a01f9331e8186175f24873b1d74a7ee9e2e4b4d68f9947dae5afd/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:21b82d8082f6f5e7f456ef0bd16323d08de1266efbfeb476e64b2a91d1471a4e", size = 245231 },
{ url = "https://files.pythonhosted.org/packages/26/de/d8e48c135ae480879539cdb179c8d3b50c7879497d75dd899b5763b69cee/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_armv7l.whl", hash = "sha256:838648accb3a7fd9803fd45c87bce8509648eb0c11bc34e216141300977244f2", size = 241546 },
{ url = "https://files.pythonhosted.org/packages/67/c4/217755fd1abc50d326c252922cd642002758095a81ff45010337b8b3ef65/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_ppc64le.whl", hash = "sha256:195ce897c6153c0700078142cf8efe3e6454ca4cf4357499e4078dfd83396626", size = 267033 },
{ url = "https://files.pythonhosted.org/packages/b8/d7/34d8e404e358d2adcc5a228c2134643af00104c8fb0bf525f3688d756f05/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_riscv64.whl", hash = "sha256:978eab16f55b4ab2c2a745be9a0a840bf8f09a7f227d9c76eb30214d078865a5", size = 252045 },
{ url = "https://files.pythonhosted.org/packages/5e/fa/40414471acf0aa0692ca77305aa00e434fcd8288f0941c93c30e9a5f8f2f/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_s390x.whl", hash = "sha256:cc0329df4caaceb950d2f580b5ac716a377f7059624a0bafaeaf8a218c6ed774", size = 264866 },
{ url = "https://files.pythonhosted.org/packages/32/90/fcc850bae791abd2e0c041847f13e270aa08692a79f3e00de6d2dce1cb50/charset_normalizer-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:687c9ca3035544b113bea2055e180af96fb63c0c476e22a9180f51925186e7b7", size = 253932 },
{ url = "https://files.pythonhosted.org/packages/af/af/53afe99068b3c10b4cbae592a52ef72a7c92c0188440e83ee3a078fd8f75/charset_normalizer-3.5.1-cp315-cp315-win32.whl", hash = "sha256:706bfd38730a5ac7a365793269a00f4e988178cec121391f4248d84ad8c972e9", size = 180320 },
{ url = "https://files.pythonhosted.org/packages/c9/bc/f46a132041b29e4a8779ed712d3df1bf112e94ca8de58b66d7ec2c0cf8b9/charset_normalizer-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:92caef967d287a407085d61176fce4012b1dd62daed4eb6d5ceb26d3d2538712", size = 204174 },
{ url = "https://files.pythonhosted.org/packages/a1/5d/9ed554480eda8e447b673648628fdc29574d23dbad01fe11837adedd1cae/charset_normalizer-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:5fc45d653ea8c9a20479167e11d4a0f8cb2fa3470737ab6f9c827532313187b7", size = 184126 },
{ url = "https://files.pythonhosted.org/packages/3b/32/9b8929bf384061ee1fe5d9c27c6f9776d3d824039ad4e14c88ec00c7808e/charset_normalizer-3.5.1-cp315-cp315t-macosx_10_15_universal2.whl", hash = "sha256:59171c6e45bf07d0d5cab3b0bf81d945035530f6873398b3b531c31184d46663", size = 381441 },
{ url = "https://files.pythonhosted.org/packages/96/10/e9aa7923d3ddac652c99a1c5f7be494e737e151566a44abe018daf757f2c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dbdd9205662134957cf0c324f639bdc5031c0ca056e2369e238db75187c0f11", size = 241742 },
{ url = "https://files.pythonhosted.org/packages/28/53/a2d249ebddf47b889a100c0bdcb61a2f9dbb8bc24ef325cc062e4f476877/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e4b018dc5a0eee4676e38fe84a47a427816c590b93b55d9025274ec4d6ffc2dc", size = 235298 },
{ url = "https://files.pythonhosted.org/packages/7d/07/469f78af590f7d5cd48e20d8dbfa3d66deeff9ba37768c04d886b5afd45c/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ced3fdd71aaa83ce593746c2edb42b7a59cb4c19c8b5c407781c72e493aae55a", size = 262500 },
{ url = "https://files.pythonhosted.org/packages/55/66/3bb56a47f7dcba014055b1a1d33c6f08bbe9c1e74dba154cfa25f90ae885/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:19a3dd5aa73cef1c99687c4fc57db016a9c17104ae1185da88ba566a5d3bebe4", size = 258888 },
{ url = "https://files.pythonhosted.org/packages/ff/c1/2adc2800903fb013210349313b710a5376856578d9e33e6b9a1d8b36714a/charset_normalizer-3.5.1-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cc5d36d96478aa9c60654bd932525bf32964c62a7281eafdf16d85003a8d6004", size = 250243 },
{ url = "https://files.pythonhosted.org/packages/95/b5/a18d0dd1157ab655cc2cb14a545f4a4784bbad70ab3502412e36097502d9/charset_normalizer-3.5.1-cp315-cp315t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:04368edf83514385ffc3e1cfd4546e595f4f1272dd23ba437a93a9cc3741d47b", size = 249871 },
{ url = "https://files.pythonhosted.org/packages/ad/c3/525f508cd1e58d0450ac55ed40ac75bc3a97482c59def5278456a5fbf03c/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:9b5db6052055d34d41230fb78d7c439c23dc536a9896f6cb039e8dd92cfc1263", size = 243580 },
{ url = "https://files.pythonhosted.org/packages/7c/c1/49a91fe7e97c8140094ca5c64161ab623a70d9f636bf834eace14048acb5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_armv7l.whl", hash = "sha256:252d099029bcbea642f2a06c4ed5046bdf8b5a8150b64afa5e027e88b106e5ee", size = 239807 },
{ url = "https://files.pythonhosted.org/packages/d3/58/56a48c296601274c4689b864a8e2dfb209b81dfcb39472753ce95eea662b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_ppc64le.whl", hash = "sha256:6199d5606e2bbf2b096cf64d03f8b6790c91081d5ac866b8e7bb6422738cc60c", size = 264083 },
{ url = "https://files.pythonhosted.org/packages/10/4c/dc48409274a1817ff349711d26c62aa0c597df865d4d69ef79160c859193/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_riscv64.whl", hash = "sha256:77efcff2b23071c349402ac1066667a3d011f62398d81408c9b88ad991747c9e", size = 250317 },
{ url = "https://files.pythonhosted.org/packages/81/58/d325912115caec62d6bdd77bbab5e0b7da5d234a9f20affdffcbcb530d0b/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_s390x.whl", hash = "sha256:a5cbd90ecf0fc62e64726917ad083b73001f0563657a87ec3c0b504e277dc90d", size = 258173 },
{ url = "https://files.pythonhosted.org/packages/34/f7/b13b1ccae2c8ec63980d13be1890eb73f8aeabbfce02a24aabc0908788f5/charset_normalizer-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:4d26f14f041e83dd8edfd61f4cd4fa7285d31798b5bf1f28e70c367ba6c41d61", size = 251960 },
{ url = "https://files.pythonhosted.org/packages/1e/25/ed3f9919c5aef8cc818be1f972f565f7610d7b2076b8ebb98839516ffc3c/charset_normalizer-3.5.1-cp315-cp315t-win32.whl", hash = "sha256:ac13b004224fb341e1e25a1ed5e19d32f57cdb2a403e01f003b46f051a550f6f", size = 191186 },
{ url = "https://files.pythonhosted.org/packages/69/d5/43c2b3e9d8267092b913eb8b0603f0f71993c395632886bd37a7223f96cf/charset_normalizer-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:35aea775dc2bd5f54cd84a1cd2696cc3207c479cb9cf0bd346f0d343e4300ddb", size = 215947 },
{ url = "https://files.pythonhosted.org/packages/a8/76/9aad3e9c8865e5e0efa9a7f6f81c37a67635a985145ecd44528a81e088ee/charset_normalizer-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:fb78f6e7fcd8ad785d28cd577168bc1aaee827b25bb8755638f694794ea98f0a", size = 193909 },
{ url = "https://files.pythonhosted.org/packages/5b/97/fb4e82231aba271ffd775a1b4993b0defc4e3059f286ae41d9433409fe85/charset_normalizer-3.5.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:41876ee62a3dddf48ff1121ad8f0798032aa03f2fd35f21f34a4cab14f18d8d2", size = 331467 },
{ url = "https://files.pythonhosted.org/packages/9f/2f/fe3f187327aac18e2d54e9d2b08e15d27bf9b642d9e51c219f130fc34d1a/charset_normalizer-3.5.1-cp37-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a6dac12ff6b846103483683f60c5f8fee205121adc58ffd87e90a90a3af69e99", size = 253057 },
{ url = "https://files.pythonhosted.org/packages/d7/c7/9e48cee5c161fe24da823b61bf381921d77cb994a0a4de148e95018c1984/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cee5dd7c6fb5dd52a0fe2a740f9bc6e3593f5f8b1788bde49de02086f30182b2", size = 240930 },
{ url = "https://files.pythonhosted.org/packages/49/e0/716601f3cc69be7b198951150c75ead1ece33c3c8036ff6ffa46029659a0/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:343fb4f2821043bd87095f7b08a1a181febc8e36ac64212143bbfd0a0e1bc235", size = 230822 },
{ url = "https://files.pythonhosted.org/packages/d3/05/71bfc5caa0abcc45aea1f6a4d50ac68e59605ddc7666fe8494f4cd229665/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ae4a097991662cd4fff0ddc74e0fe7874f82e00042fa0ea00855645ed0c79598", size = 260037 },
{ url = "https://files.pythonhosted.org/packages/c3/92/de7e32ed05341e7a9c4c877c318418197b7f2d66a3b68d561bf2ac57ca3e/charset_normalizer-3.5.1-cp37-abi3-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b599739b93b2cbeded49645ae3c8d1405c29ddfbceac1545c87a3f9580a9e96", size = 255097 },
{ url = "https://files.pythonhosted.org/packages/f5/7b/ade0a122600319dfa0b1000ab0f9731c94a817904cf3c5de408c73a4ede7/charset_normalizer-3.5.1-cp37-abi3-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b39b69b347e5e47a3b5b8cfc005c68c1ba347474e3960236c4944a8ecd174962", size = 250166 },
{ url = "https://files.pythonhosted.org/packages/75/9c/019fbb9f4834491a160951349b1a3714439376f66e5f7cf18b4f18f0c7aa/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a2028475ba855475b8b4d3cfeb4994269c967aea8b9892dfba907f4263a863a3", size = 241821 },
{ url = "https://files.pythonhosted.org/packages/2b/b8/11d4840bfc99330cc7fbcc2681ee5a044553a6e77655508d8f9b2bff7b34/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:36047af20e17097c3bb9476c2b7655f2f7aa51322c0ba58c07695bedf755a950", size = 232529 },
{ url = "https://files.pythonhosted.org/packages/18/96/2b3a21492d9f65171ac75d872f5018260013d00bfa0ff70ec9f179148cbd/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_ppc64le.whl", hash = "sha256:4c4fb141a727957c93edfe5c32a26ceb6b5f6461d67146e2d39f51e16170bea8", size = 260348 },
{ url = "https://files.pythonhosted.org/packages/d6/aa/a69a2028e8bd052476c245460ab19d7de595de084dd968f2d75cd50c3e25/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_riscv64.whl", hash = "sha256:2f293479cce755c75f1697e87c409b7ae4c555c7dfecb6e988ad13abba943031", size = 247234 },
{ url = "https://files.pythonhosted.org/packages/35/8a/3d130aeabcaf3d2466af76b7b141c08d9e89c9016ab4b7cdd0f7dc2d1c62/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_s390x.whl", hash = "sha256:3588e376b3ea2eea84976f67273d679f229e24c66dce7b82ae45aef04ff6e072", size = 256917 },
{ url = "https://files.pythonhosted.org/packages/80/c2/a7379b840292d0c1ab9fbd17d1f3967aa81794dc95bc74be8999d7fedcf7/charset_normalizer-3.5.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e199fb99720074809a7720f1c0b4d919eea8b87e88713e0f8f602f7bef543d9d", size = 254846 },
{ url = "https://files.pythonhosted.org/packages/01/65/d43b714731bb2f40d4053dfa00ecfc1c5a301f8e3316c5db3a09af59fe94/charset_normalizer-3.5.1-cp37-abi3-win32.whl", hash = "sha256:dd732602a7009217f658d5863d12d79d373a4de0eebc111094bcdd3bb8e0a6cc", size = 174216 },
{ url = "https://files.pythonhosted.org/packages/35/4f/b911ed898b26a09789eba9c9200c999aff6c61b4bafaf4838e56d1a1e1a3/charset_normalizer-3.5.1-cp37-abi3-win_amd64.whl", hash = "sha256:70055ff39b97c99e7ae40ea3e393fb62aa2e44dbd9b29f8d14f42fb0025c3959", size = 199764 },
{ url = "https://files.pythonhosted.org/packages/f0/a7/920baf467bfd9bf689f3b318340f37aee4572a71f162bd8db51da55ba4fa/charset_normalizer-3.5.1-cp37-abi3-win_arm64.whl", hash = "sha256:87e4f41d375c0b9be2fb5251aee4b8a689169e134535aed81bf085c3b647451e", size = 287318 },
{ url = "https://files.pythonhosted.org/packages/cc/61/d01fc49b8dea277640b55a9e15960dbca9fdc8c9fde18e572d39c59f4019/charset_normalizer-3.5.1-py3-none-any.whl", hash = "sha256:6df0ec430f9a831772c23ca5a224cba36517a58a84bb32c32bb59a9fa67c47f6", size = 68658 },
]
[[package]] [[package]]
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
@@ -157,51 +11,16 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
] ]
[[package]]
name = "colorlog"
version = "6.12.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8c/55/ba79756cb90c8d69d599d57785398ac87bba7b19c80e87f4e8a562197c93/colorlog-6.12.0.tar.gz", hash = "sha256:2a7924c1dadf18b22a0eb8b06d1c7b01d5341707ec1641eb6fcc4fde0c3e8e5f", size = 18151 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/19/0b6647bf5e331521e55d2b63bfbdc210bd9cd605189273f03614a05f702d/colorlog-6.12.0-py3-none-any.whl", hash = "sha256:30d392604e9110045a2c2aeefc27d7a017abbab63f3a8aee594eac0801df784e", size = 12239 },
]
[[package]] [[package]]
name = "ferrolang" name = "ferrolang"
version = "0.1.0" version = "0.1.0"
source = { editable = "." } source = { editable = "." }
dependencies = [ dependencies = [
{ name = "onnxruntime" },
{ name = "pytest" }, { name = "pytest" },
{ name = "rapidocr" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [{ name = "pytest", specifier = ">=9.0.0" }]
{ name = "onnxruntime", specifier = ">=1.28.0" },
{ name = "pytest", specifier = ">=9.0.0" },
{ name = "rapidocr", specifier = ">=3.9.2" },
]
[[package]]
name = "flatbuffers"
version = "25.12.19"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/2d/d2a548598be01649e2d46231d151a6c56d10b964d94043a335ae56ea2d92/flatbuffers-25.12.19-py2.py3-none-any.whl", hash = "sha256:7634f50c427838bb021c2d66a3d1168e9d199b0607e6329399f04846d42e20b4", size = 26661 },
]
[[package]]
name = "idna"
version = "3.18"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455 },
]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
@@ -212,143 +31,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 },
] ]
[[package]]
name = "numpy"
version = "2.5.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/9a/80/db0b4559e57ec36362bedbb05530a87fafbcb6067708c946967a41d449e7/numpy-2.5.2.tar.gz", hash = "sha256:d482d171c406ae88c5b19cad3b6a1c4c5209f886ab74bc44c2c865c23f52d860", size = 20773161 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/72/dccb0aaf40972777283303919f613964227266d0c13adebb79ac124f1c3e/numpy-2.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14e373cfc6387177e8409dac3c7159be8eb05cd77096cd7c950268b86f62831c", size = 16891693 },
{ url = "https://files.pythonhosted.org/packages/60/2e/b5aee50a1f74ac815cf8331812cb8251e29024025de462e0c047641c614c/numpy-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4bbd96c833ecc8cc069ce518078fc8c60cb9cbfb0fea5b7a803ad65035596d03", size = 11903109 },
{ url = "https://files.pythonhosted.org/packages/f3/f4/29e78102a80601cf034d4e9767022cffeca2c3b4c926e1754572ca95593d/numpy-2.5.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:6e8172ddfcf5cf74b811d372b570b83c60bd2de87a6fbfbebdadb4a9bd9c6cbb", size = 5350202 },
{ url = "https://files.pythonhosted.org/packages/11/4b/dcd3b7eadaf4035d2c7a4289d232523a6964f602598ef7674e4bd7291f93/numpy-2.5.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:65f188481f1669e26f62b701e8205d19e460fa4a9b52a1414ba382330e4a3414", size = 6687736 },
{ url = "https://files.pythonhosted.org/packages/e5/21/4947e0e9d6c9fc2e2ff15b8949049ee44f63adb9cacc729ab8793f97e712/numpy-2.5.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ee9c4eeb8454b3660a8b53493563c3e121c2fc94fbd72b848ef814ed7b676a9", size = 15612696 },
{ url = "https://files.pythonhosted.org/packages/3a/5f/62d28cf019460c7f1394105b4d49d9911a9c444cb77ab0bd95a204c5a6de/numpy-2.5.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3cdec01fa790a186d430433fdd4d4ffb70eed6f0eeb4bf05c8dbe2dce0a9bcb8", size = 16722264 },
{ url = "https://files.pythonhosted.org/packages/14/25/3f0be4c1b9fdf5dd5e708a6806978564d7c46a055c000496309ff2a2f8af/numpy-2.5.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7999d4ddb0c4025018373fd787510d46e04c769467af22869707b3c1cfd459ab", size = 16974396 },
{ url = "https://files.pythonhosted.org/packages/22/72/6262cbdeeb45da9d971e40715f579d791603ba8ec0b5e2db1ac55454421d/numpy-2.5.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1f017dc0875c9209d219f97feceb7d54c2661bb243deb4114478e1295808af7", size = 18476044 },
{ url = "https://files.pythonhosted.org/packages/36/33/29208b8b075bde62d26a81d14b358c42b0f69b6cabd98d4ff97f37f22b05/numpy-2.5.2-cp312-cp312-win32.whl", hash = "sha256:d6a48072864e3324e194a8fbb3c657bcc5b5c869dbc64c9537b1d5c862572c0a", size = 6072817 },
{ url = "https://files.pythonhosted.org/packages/7f/b9/87fea2769fe1c47c1b5b01d8310772c9d1a85d485de7cf386ef7a3332b02/numpy-2.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:28ac63476ec7651484215ee7fa15a1f78b57c14621f01e392afe17b9a1390ce4", size = 12464674 },
{ url = "https://files.pythonhosted.org/packages/14/52/032b97e00461ab0809bbe4c588b035620e5a14b8cdee47ecddefc7b17d33/numpy-2.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:27650bb0e7140fa3d37b9923b4803645e0b125d190f326eecfd3f4dad8e8ade1", size = 10397131 },
{ url = "https://files.pythonhosted.org/packages/f5/d2/6b24738a0ef4557d189b150046cd07823c50e4273e8aebd651222e24306f/numpy-2.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8e4cb9a754c8a0c62eaa88273a5fba3391f4a610d1dee893c0755da31c083f15", size = 16886595 },
{ url = "https://files.pythonhosted.org/packages/65/60/f2d208d366f263f39c6e69ed309290717aab41078b6d04c9be2a84fa2a07/numpy-2.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52c808f96484f5571a5cc863775ce50247c17dfb3b0361f8ed6b4b0456f80080", size = 11896845 },
{ url = "https://files.pythonhosted.org/packages/3c/79/81e0bf24f4d020a2b1d5cd297a9f60c3f24eeb116f9bba5870443f7b6a4a/numpy-2.5.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:29d81e97f668489cba8ebfd796b9bdd453525d35dd9e162e2daec94bf3fc7740", size = 5343880 },
{ url = "https://files.pythonhosted.org/packages/ba/cc/e3141cf06d1a8a2c7e107543fe1269c1d1af760d4d683c0794a4ee1127c2/numpy-2.5.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afb3f0632d6b2e3ba04dbce8d1e48d321b369138b73830b5ca371a0e8d479d56", size = 6682264 },
{ url = "https://files.pythonhosted.org/packages/29/f1/2a64a307d92c5d98f5255a4014eb43bb6103ee477087b61ecae44a3aa9b9/numpy-2.5.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0aadf13b60048d501e05fa699efaf7734e2494f3498a4c2a5521d822640324f3", size = 15609566 },
{ url = "https://files.pythonhosted.org/packages/7b/44/59a1eb68e773c4098d107ef34a0dbdeca501d72ffcfbff9a7707343921ce/numpy-2.5.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29b86ff8a6cc556b47ec6b64b194815cc80e6bf5eedcc6cddfd65318cb0b4eee", size = 16709995 },
{ url = "https://files.pythonhosted.org/packages/8a/4c/3e54d4ddbc359a1295f8b633e8106bcd4d7d4a206e82df051bdfb3058755/numpy-2.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6950c4b7dd562453090548ba7f5da7e59f57f85663f15d5dcc60e249192f7e59", size = 16972511 },
{ url = "https://files.pythonhosted.org/packages/f2/9f/02e371638ebf19b66d46231e4be52999e87f32d1961b113bc45656608b22/numpy-2.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9727f472d2f3888053b8a75ab0cb94745a9de224bb5846dbadc0092101bc71d", size = 18465609 },
{ url = "https://files.pythonhosted.org/packages/eb/ae/ad6645abc7a3510fe48e8ea1ab4598166f500057ef4ebf38bfad4f1577de/numpy-2.5.2-cp313-cp313-win32.whl", hash = "sha256:4f9744f9fbdcea0bc552e8f19e1f141f811a3f9bc2be2cc6e86d982cab23e3f4", size = 6070204 },
{ url = "https://files.pythonhosted.org/packages/15/20/f3489f86d81ea460b2bcdceaed094142ca6579f6be0ec527b781d39afe68/numpy-2.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:85aaccb24182c25df891ad0ec333585967e115269d5f1b17f2c9ae005bc96657", size = 12460532 },
{ url = "https://files.pythonhosted.org/packages/d5/21/35b31dde1b283b79de828b80f876afd8c94e28fe1e9c375f89e261cc4c0d/numpy-2.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:bd68ece1553d2023c09a4226d9e41c586ad2d20594d1a456186c33513d2cb3f2", size = 10396725 },
{ url = "https://files.pythonhosted.org/packages/ac/f8/c3b222bf075b50afd8e949a07a15c4b312a4a84bd8102a332bcd953cbbb4/numpy-2.5.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d787cf769c3baeb5f6235e778edb52c08dfa923789b5958f28e6450f96107cb1", size = 16885180 },
{ url = "https://files.pythonhosted.org/packages/17/e1/2c1d4b1987795a92b5bbf7c24fe249ab96aa2573ab0d7604802c189d7b86/numpy-2.5.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:24b9dc2e3d84aa58523798805194e23e736f3f6ce2d1a5b92583ae734e6dbda8", size = 11907878 },
{ url = "https://files.pythonhosted.org/packages/b9/ee/d08226fc858044355983a6e5b94f08ff6f3969e0a2b160a4a89f0ddb3445/numpy-2.5.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:9e9413326d726c2545bfa65d2c0876871e8d8386e77f992c1d426e180bbd4323", size = 5354922 },
{ url = "https://files.pythonhosted.org/packages/94/f0/6d3d933056440ebbc5e6bad92065fc6c26a48a84a36b1208580e94eea76c/numpy-2.5.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:60e902ac295855348a5ca2ea4c89108989a9f5fddfad3dfc0a8f36b10358567e", size = 6679168 },
{ url = "https://files.pythonhosted.org/packages/c4/3b/ecd49dd90033cceb2704d88ca905d4d7d89b0e8c739608754ffd325fa820/numpy-2.5.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50e500dc868e9313530ce12ba470fe50ff3afe3d62993ed6eff652dacd555b65", size = 15624501 },
{ url = "https://files.pythonhosted.org/packages/c7/99/461bd36dbdfac6c1c53efa370bd55a83227542d0d118f1677dbf1a3dacd5/numpy-2.5.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318b9a4c845dbea06708a29c84ee429cc3065048db34cdb799047643492050ee", size = 16713701 },
{ url = "https://files.pythonhosted.org/packages/f9/9c/2b251df9e8a5d647b62b0cbc1b90a91850c1cf4859ecb532fd0b4eacff6c/numpy-2.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:34c319e2963be042673fb46570501b2f06c41924e17e3563d58646b4380dfb68", size = 16986065 },
{ url = "https://files.pythonhosted.org/packages/8f/25/20de43f53ff1390534a124475055a19f01fe10c920a0fd11b8e18d6d6052/numpy-2.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f06571a052127dc1b4e8b83029b4d1b20daa2b64a31cdd181fc6bc774e9000eb", size = 18470031 },
{ url = "https://files.pythonhosted.org/packages/56/5e/0c577ca308d6da5eb79b546ba10bbe5b60148192194e2da060913b1de4f1/numpy-2.5.2-cp314-cp314-win32.whl", hash = "sha256:2cc779226e476d1e1f08c74068c419e60f41a9e0e069c92f6671d31d5c985e98", size = 6121028 },
{ url = "https://files.pythonhosted.org/packages/15/5c/7bcbd5b11f94199073320410cddcbb80cee62415bfeb540874b265c2d922/numpy-2.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:7587f53dfbd5edc0f7b87c6217b4c6d2d1f2ef9c3da70bc1315e7db5f8d7ec9d", size = 12597627 },
{ url = "https://files.pythonhosted.org/packages/87/bc/4d0b06fba0da90ccc75af62823cb9dcedb6c9ea0cffa058cb2c9ee773a77/numpy-2.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3e4c367352d3747784248a227fbec218e193b56f7e6692e3b64fc805478ecfdf", size = 10680414 },
{ url = "https://files.pythonhosted.org/packages/cd/17/f429aac9dc08833a0d0f188eba38c532a751b1a1f2ca6018a37b455cb321/numpy-2.5.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b879fb674276e331513fb136b78dbc6bd3c848309e0d841cfd63be3896c4cfc1", size = 12026967 },
{ url = "https://files.pythonhosted.org/packages/ca/9f/d0849de96a2a4ceaa16662f18ee13eaa9c0aa418269fdc8c4857c56b11da/numpy-2.5.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:fd0d703772bba096843785bd38371e31bb4a0c1151497ad5739d182114a73f7f", size = 5473874 },
{ url = "https://files.pythonhosted.org/packages/89/3c/8df216d4a4a5422a3de045301cf7df8ea47286d76f5cb7160b0128ac26b7/numpy-2.5.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:3a2f061cebd9e3d23bdcfaaded5e2293a4c6a5b60fa42df85d410a725ce621bf", size = 6789276 },
{ url = "https://files.pythonhosted.org/packages/e6/3a/20d7e9891c4ddfadd6ff8d95bf4b29f353d8e1770553de2099880551dfb9/numpy-2.5.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6df895598c0edcb41030126c89e0f353b07d93238116143b7405e937359736c4", size = 15659154 },
{ url = "https://files.pythonhosted.org/packages/aa/d6/f3aa3d2688bf501b858835c6bd087ae9b51a56ae6fca8e2b0990abd177af/numpy-2.5.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ab3d4a901f844ea836c3e80bf463c6a27d7f3c14e8e292fcf28d348b25b9bce", size = 16748909 },
{ url = "https://files.pythonhosted.org/packages/7d/8f/1c5cae8d2baf86ab802ae97a00be55bc7e21ebc11b12bbc33376c5f05342/numpy-2.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:cebc2d6dbb605a7703d59751dea4bd6b0ab127a5a4338a6f432df1936fef8b26", size = 17027685 },
{ url = "https://files.pythonhosted.org/packages/5c/27/71d3467404aedc1c24ce79610f91b52b0b0f466c43a701aa56fc75c145ab/numpy-2.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eaca7ff36f0f52e2111ec71f169d8fd3e889e7ddc0d2592e0d703fd8d3ce8fac", size = 18501181 },
{ url = "https://files.pythonhosted.org/packages/14/2f/42921d27c40aea7e077f4a423ae509fd9220b028cd787bafefd8ab2b3a5f/numpy-2.5.2-cp314-cp314t-win32.whl", hash = "sha256:ddf47472af2e4280d79bac82304f5e80150211f1b9e614b760061d5fdfbb6eba", size = 6271085 },
{ url = "https://files.pythonhosted.org/packages/75/e6/bad5f5d56de9b1971bac959963dda276d35c40f1854475005434bbe08692/numpy-2.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:44ef9675d908e65f9953063837c3277730f3f4437615a4cdab67b366cabaf884", size = 12787971 },
{ url = "https://files.pythonhosted.org/packages/df/05/f608795cb34391acd67e38d94a3c36abd8d8576293a3a80727d7595c372c/numpy-2.5.2-cp314-cp314t-win_arm64.whl", hash = "sha256:eaa088384c46f519dacb93b7ec483a6d6b19a4a2085ae4f25ab9b1c43d387d1e", size = 10750306 },
{ url = "https://files.pythonhosted.org/packages/33/c6/28de0191c5f82b7d42a0a51390ba98587048aa93a39fafb05bdbe6e8d00c/numpy-2.5.2-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:078f9b027b478c9379b9677babbf0f8b8f1ecfada27636d7b9a93990c638739f", size = 16885274 },
{ url = "https://files.pythonhosted.org/packages/dd/d1/973ca116000d244897e468ea1aff30b589e5022e3c8744b71706fe33bd57/numpy-2.5.2-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:50a68f4bacd8a2b33d8da3d2269d0d78500f86ea582e4786dc10f5ef2c2c6842", size = 11907846 },
{ url = "https://files.pythonhosted.org/packages/78/d9/8c4b3937ef204cb2fd88d389ccd0f265a2ffb11f35a01d2064cf46714bd6/numpy-2.5.2-cp315-cp315-macosx_14_0_arm64.whl", hash = "sha256:e79aba74ffaf5f78a050d777c184cddf8fdffabab38acf5f3ef1fecbc17895d6", size = 5354892 },
{ url = "https://files.pythonhosted.org/packages/74/9b/b6ee65ea2999fdb7023935e108e6fb776ee4082aa15f159acfa857e578c8/numpy-2.5.2-cp315-cp315-macosx_14_0_x86_64.whl", hash = "sha256:9a0731745a72a184490a582fb4af2533512bd071ace67785b5fdffc0ae58dce8", size = 6679309 },
{ url = "https://files.pythonhosted.org/packages/43/f3/acb18d8b137a393c8e7803a8c994c9e64bde3930692a69d826993113a159/numpy-2.5.2-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ec954036759bcee3aa484f8603bd9c14f3e776293b85578b8734c2d72777c69", size = 15625850 },
{ url = "https://files.pythonhosted.org/packages/a9/bf/a8e9bb0db815a0e265b5744ebedd3af0bd5faad8604e5b50a1cd012f3c91/numpy-2.5.2-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc649493697006bc90614a5f0bbc8cb3cb1866715c474e473694968d7e6b99ab", size = 16713664 },
{ url = "https://files.pythonhosted.org/packages/0c/c3/6e913736b3dd6582344af32418b5fb9dab34282e8a8174ae1d54ceb0fc13/numpy-2.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:cf7de32f486e4ac9e2d93b810f9e9ac72a728dd46a32a0bb403222f27f653514", size = 16986749 },
{ url = "https://files.pythonhosted.org/packages/80/09/7d3b23eff5c7428ef6c01e6f7052bb60d504c4d33e317b36b8959c24ad97/numpy-2.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:2ffa7bacab3e2ee1b19ed31766bb60bb380b68c23f051e199c5cc598afd68710", size = 18470495 },
{ url = "https://files.pythonhosted.org/packages/a5/a4/68a321d825374f6eb677ffe8ef8c6b9a328304e6fd2e39d9530822776607/numpy-2.5.2-cp315-cp315-win32.whl", hash = "sha256:6b588cc8f902d6bff201c19fd00c43ab8545671e3554d014e12e14139e5e8617", size = 6120696 },
{ url = "https://files.pythonhosted.org/packages/c8/23/deafbb1700f79fae9cd1e91220f133d124cc267de1b584da3fbf6db2f6cd/numpy-2.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:07d4e89f3a9ab0a9ba24264ccdb642b3dd951b2281e8883a5481a4aa79cc31a7", size = 12597324 },
{ url = "https://files.pythonhosted.org/packages/33/cd/3272ba105e3bbbdaeb11357eda31e7a6825ffe159e8171665660299a948f/numpy-2.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:a610dc7e3c52edd39c2bc2375ff9c3fd59cb3ad00e4472d36f83bc1457145788", size = 10680466 },
{ url = "https://files.pythonhosted.org/packages/0e/0e/58370637b1bb70a5c9ce2b43f4b521ccb224e36ccb76a6596b17ae4b447c/numpy-2.5.2-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:40f4d451aed46a8046a1aae41c4e55fb3612273df9c502480135e1501576a34b", size = 16993947 },
{ url = "https://files.pythonhosted.org/packages/10/93/2abcb807712b289d6d60fe4cf30532f98974a8396d885650f3ba5a13026e/numpy-2.5.2-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:c081cbe16ba1ab53078e5ff29013621e33c509eedab055775d956427712c236e", size = 12025331 },
{ url = "https://files.pythonhosted.org/packages/8b/3a/2898e003a5fbaf87e76c039b4ee1f5eb390471b4ffe74887c1f34c4e791e/numpy-2.5.2-cp315-cp315t-macosx_14_0_arm64.whl", hash = "sha256:0090ccdd57ec2703e9b49d0bf554767370581c1dd0a6b2bb2b2d9def317d042a", size = 5472336 },
{ url = "https://files.pythonhosted.org/packages/61/a5/23f69d07c544597b29758b31b55c27dc9d541012a2c1496189fef702aec2/numpy-2.5.2-cp315-cp315t-macosx_14_0_x86_64.whl", hash = "sha256:6a9bb119fb8dd21ba30b3f0e555b7e2b081bd9883af21ec9c1c633d161cda3a8", size = 6788387 },
{ url = "https://files.pythonhosted.org/packages/15/ea/c0dbdbcf22f43782510a3e492dd3da73c6112b69cac8929d16d127536fc4/numpy-2.5.2-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a839318485284a6fb31be4f8f2c91c8f2cb22f4543c4a8903f12b0671ffe07cc", size = 15667096 },
{ url = "https://files.pythonhosted.org/packages/fc/5e/29c73c31748cdb0f7566642125ba17fd5b56780cddf891b085dab27e4466/numpy-2.5.2-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba0a474801b8dc67b66bf465548abc90e82b44d2611b5770f33008dcabffe8ec", size = 16751730 },
{ url = "https://files.pythonhosted.org/packages/47/95/02501e8454796bb58dadf7a99d3181e0b464bf264e1003039572f9779fac/numpy-2.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:0a4035ae1129ff8777f08bfbd44f1e5d8e9c049ce0c2dd78fc0d92c13e7251c0", size = 17038686 },
{ url = "https://files.pythonhosted.org/packages/0e/b5/53a681d91b5c82687067d8ea5035e02d917b5509d6f334cb06484a954714/numpy-2.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:77843ca236b777e67f8d6b3660ea116e499612703a0ecd7093f316201eb9d8e2", size = 18507727 },
{ url = "https://files.pythonhosted.org/packages/42/06/6e11443f7b64ee376c860506091103bf68f92d2cab9e8d96d4501babf07c/numpy-2.5.2-cp315-cp315t-win32.whl", hash = "sha256:7354826bc6f8f69402e9b7fe28d15fcd34feebd74f856f111585c5b0c9fb0251", size = 6269775 },
{ url = "https://files.pythonhosted.org/packages/f1/18/195d6b86cd72dbbc501edfa778005fa6b87afd34c153e46028cd3a0938f4/numpy-2.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:e5651f3f87add730ee6608d915009e19c911fba0cb000c7e3ea994b7d768eb12", size = 12782559 },
{ url = "https://files.pythonhosted.org/packages/b4/07/458c344f0f0c178f4481dad5cca790626ffe4c34eabf9467069d06ee4999/numpy-2.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:5f8e00be2ec6f45f4e8a41a527f68d44a7d96fee92a650e4d8b1326f77f61e6e", size = 10748103 },
]
[[package]]
name = "omegaconf"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "antlr4-python3-runtime" },
{ name = "pyyaml" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ce/3d/e4b57b8d9008c6ebe0d5eff901f91d5700cf7bdb8c8863df817463a7fd5e/omegaconf-2.3.1.tar.gz", hash = "sha256:e5e7de64aeebeddaf8e6d3f7a783b32ac2a01c0fbd9c878012caecb891a1f42a", size = 3298472 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a4/0e/152509871bf30df6fc38569f52a2db9b55dd41aae957adae50a053ac7778/omegaconf-2.3.1-py3-none-any.whl", hash = "sha256:3d701d14e9a8828f1edd28bb70b725908b34277cdd72cf7d6a83f94dadc6b6a0", size = 79502 },
]
[[package]]
name = "onnxruntime"
version = "1.28.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "flatbuffers" },
{ name = "numpy" },
{ name = "packaging" },
{ name = "protobuf" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/f8/dcbe7700dca82fa540035abd3c868fe5ad0f86af00b9a3db7c2e27d15c7d/onnxruntime-1.28.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:26ff0fdd06efb6c155bae95387a09db1a2be89c7a03e4d0bffd5a171cc2826da", size = 19141362 },
{ url = "https://files.pythonhosted.org/packages/28/5b/1d77e62097fdbe07e2dc827f389b1c4c0c275f6fab0369a8f46d2461af27/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e81a23df16e7acb9d51b06d30cc098e49315ef9180f97bc2221d167b4b04d9c", size = 17050628 },
{ url = "https://files.pythonhosted.org/packages/95/df/5486ab03e9be288d5268867054c8b04bebcf95bfd12e801c05cc67703dab/onnxruntime-1.28.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a83bdb70d143cede762b677789bf2a7acca54b3fb82565601d5c30695aa933c", size = 19214257 },
{ url = "https://files.pythonhosted.org/packages/3e/3b/986ca67c274932ba9ac5332fb10de56f643dfd433c74e33f8ae8f847cf24/onnxruntime-1.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:c35064f9b3c43c81c5d5d282091401d0f1ff22796d93ccade4ea2ece5e137ab8", size = 13755036 },
{ url = "https://files.pythonhosted.org/packages/1d/46/059dba81d46c6ba88e0c2d1c64321ac8098847d678423300a183d42ecbd6/onnxruntime-1.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:e02feeb0165c5f13b4cc954738078d59b90128516ac12b671ee24a530242bf02", size = 13454462 },
{ url = "https://files.pythonhosted.org/packages/9c/12/3807e2b17d9eb71d3cb78ed2ba76869b05c637c9b9d6112e636098b0c97a/onnxruntime-1.28.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:31410f544674f534c2f27348af52ef81682ca9c8719154bf4d48f0ef23823b1e", size = 19141759 },
{ url = "https://files.pythonhosted.org/packages/c0/23/b46045c3bf67a9cf54c12f5df0f018a422c65fbb9d6072b10071bebfaae2/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f649dd6f6452d12a8059888aa489fe519e062e18793dac72b9efa0f9fdb64135", size = 17049339 },
{ url = "https://files.pythonhosted.org/packages/78/b6/8c5396e7894e77c5a7d1e026f3acb9dd39c4b5644e412e37a0055eaa3bc5/onnxruntime-1.28.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:54fa221d669282bd8f582708ce4c96010a7e9fb0661f9006b37fe2fedafb73fe", size = 19214329 },
{ url = "https://files.pythonhosted.org/packages/56/f1/51225c202edba4dfc94e1ea03f3d78f1aaf307da75fd792c0ce1946b2514/onnxruntime-1.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:1a1a19175464665c9b8d50bc916f216cc0b569110045b7bbca8f9f290b186f58", size = 13755033 },
{ url = "https://files.pythonhosted.org/packages/f4/db/f59f715edfdd96a051f32b5ef0e680a20a8755d4ecd75f63090e960e347a/onnxruntime-1.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:cfab507abe09d6ffeb817eee07944d452fdc0b00fdcef34cab4db10a45e378c7", size = 13454175 },
{ url = "https://files.pythonhosted.org/packages/47/28/810314fa88647af9f4cdaf438a30ad1cfebebb53ded55499232d7a0094e6/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac301f53b1930402fc46c368e268acfed02f3207272aaff05070d7e09f96f031", size = 17057307 },
{ url = "https://files.pythonhosted.org/packages/3d/cc/9e9f193cc0f29f263a8f09ec08487aed6c96ee856d5fd77da32a425c1949/onnxruntime-1.28.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7f022a1103cae591c75fc4565589a515f2ddd14a6ac8e8a05812dfeda142e28", size = 19222954 },
{ url = "https://files.pythonhosted.org/packages/4e/eb/952314c451d9463e5c9aed9978eec76cf32930d407d9ab8700dd0f4ea1ea/onnxruntime-1.28.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:8adff67a3f28257b37cfe945a7e952e4122666aa8c91a0380862e9fd4c2ed19f", size = 19143748 },
{ url = "https://files.pythonhosted.org/packages/3f/e9/139180b4dd810329aaa42c238b4e6383c906202d98609ae29d66eb7c32b1/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc2565e487b4896fb988d6383577d875d958e071fc5f6c3550bd5d02ae98264b", size = 17051950 },
{ url = "https://files.pythonhosted.org/packages/03/88/9432428273356ad3c8aa01f52c1b3e7f53c4c0192748f41ad983872b436b/onnxruntime-1.28.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6afdc83f1317c136e92fc29f5ee9f058de59d87c0b22cee3fdbfbaa0ccc2098a", size = 19214924 },
{ url = "https://files.pythonhosted.org/packages/bb/e2/6feb3a43517aaf2b1bf7e46897ba5eb81a29717f7d7901420614d5ee4653/onnxruntime-1.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:f2a3b9e30ce880d4ca54999cb313569e36da4f62eefe25f87be18f43e9a3a4d5", size = 14093738 },
{ url = "https://files.pythonhosted.org/packages/fc/8f/83974a1e201dc2e58e5e7111bcaeb1ca2413e9c41f505d26419ee9e3dddf/onnxruntime-1.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:07fb3cbe990d6bf0ab3c22bfbbfb0e314151266046ea6edb4a07f556b4258c5f", size = 13821117 },
{ url = "https://files.pythonhosted.org/packages/0d/83/00e606bc25c756d76a267370c39b7516ad52f9cf134d7ff2bff8b6108bc4/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e562d6e36a749f6764481c0ddb0f2af3d0b5a3c164291361d08803c557f369af", size = 17055518 },
{ url = "https://files.pythonhosted.org/packages/94/a9/68707e1ce345cbdbcd4df65932ebc82a673e917d63eda0007ebcff948691/onnxruntime-1.28.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f6e92367ddce1e4d33cf295024f40192be6c6171a09208f515ba169ced06c8e", size = 19222976 },
]
[[package]]
name = "opencv-python"
version = "5.0.0.93"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/79/4c/a438d23e09ce2033c09f7b784ad2fbdb0adf529e434101ed28f142226f98/opencv_python-5.0.0.93.tar.gz", hash = "sha256:66aac3e5b5faa48d4025816592f3af19e4bfc2c68dec067bae2dbb4ca10aa9e2", size = 81802749 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/75/76f6ade78f6102c61034f828e2a22616708df2c9504bc8d6af9dd8f73dc5/opencv_python-5.0.0.93-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:198a75138241810206a17c829dbcc40a7cb1841cda538ca86cbbfc6c7d95f898", size = 48322443 },
{ url = "https://files.pythonhosted.org/packages/15/8c/bc1bda6aae69a32e9d84fc34153ba104cd25226861eb4aea33b2cea4860d/opencv_python-5.0.0.93-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:6bbc32f59e1b1a7db7b39c81f63d00625f041d333037fd8702f6da52cc39108b", size = 34782755 },
{ url = "https://files.pythonhosted.org/packages/f4/8a/b04776ec45d2dea08a1b176f1829201db3515d4ed16c35f8fcc9fa7beb16/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e2b4272e736836f66c2d176e43ab8101f3a00d45654916399f52e150c58981ac", size = 50614064 },
{ url = "https://files.pythonhosted.org/packages/95/54/eb47866b94f2b5b42dde17644b78055ef1ee05aae59962c7290e55270803/opencv_python-5.0.0.93-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8b6d0a212253dd26ad338c812f1f23ca118fdf05a9c8c6b9444f161aa8c5881", size = 71064711 },
{ url = "https://files.pythonhosted.org/packages/93/da/962579f1e703cbf8c5422fd1f576467dcb3b5b0b0b81c1471c979764353a/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:08d5d91d967b58d6db86073b2ad3eaef88ca4ebdfd45c9059bf59f5ded0c7ad2", size = 49798576 },
{ url = "https://files.pythonhosted.org/packages/cf/4c/c73f828fdbcd37eaf21d08fa852544a3ca7c2dbb3ea76873d64f2ea413d1/opencv_python-5.0.0.93-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c8de2dec111122a02e8beb28e16c31904992dfd6186560b142a92c71403c1039", size = 73783032 },
{ url = "https://files.pythonhosted.org/packages/e2/4b/edaf83b996ca5a1a3d8ccad485706b9c6d4742b13b9c4586bf1c1e7d9423/opencv_python-5.0.0.93-cp37-abi3-win32.whl", hash = "sha256:4b4b1a34c79bf8d3738e3cfe9a9e67b51a79663f6b692cbdad8c31f570da4157", size = 35564734 },
{ url = "https://files.pythonhosted.org/packages/21/f0/9fa6e85cb10c8eb36a0222d27e50fe381b86ce49a55446bf39f491727564/opencv_python-5.0.0.93-cp37-abi3-win_amd64.whl", hash = "sha256:f90ba04b8f73bc5c3814037699739f0156f597338a98f05956c684e7c3ca10d2", size = 44000345 },
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.3" version = "26.3"
@@ -358,77 +40,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956 }, { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956 },
] ]
[[package]]
name = "pillow"
version = "12.3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/3d/bb7fca845737cf9d7dbde16ed1843984665ff2e0a518f5db43e77ec540b9/pillow-12.3.0.tar.gz", hash = "sha256:3b8182a766685eaa002637e28b4ec8d6b18819a0c71f579bf0dbaa5830297cce", size = 47025035 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/37/bf/fb3ebff8ddcb76aac5a01389251bbbb9519922a9b520d8247c1ca864a25d/pillow-12.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ba09209fbe443b4acccebe845d8a138b89a8f4fbaeedd44953490b5315d5e965", size = 5345969 },
{ url = "https://files.pythonhosted.org/packages/d8/66/9a386a92561f402389a4fc70c18838bf6d35eb5eb5c6850b4b2dc64f5048/pillow-12.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffd0c5368496f41b0944be820fcb7a838aa6e623d250b01acf2643939c3f99d7", size = 4780323 },
{ url = "https://files.pythonhosted.org/packages/25/27/ac8f99618ffd3dde21db0f4d4b1d2ab00c0880595bfd17df103f7f39fd0c/pillow-12.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9c7f76c0673154f044e9d78c8655fb4213f6ca31a836df48b40fe5d187717b9", size = 6266838 },
{ url = "https://files.pythonhosted.org/packages/84/21/a35af28dcc61f37ed850a2d64c65c701321dfbf25085e469d5559360cbbf/pillow-12.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:78cb2c6865a35ab8ff8b75fd122f6033b92a62c82801110e48ddd6c936a45d91", size = 6940830 },
{ url = "https://files.pythonhosted.org/packages/eb/51/8b08617af3ad95e33ce6d7dd2c99ed6c8298f7fb131636303956be022e25/pillow-12.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e491916b378fba47242221bb9ead245211b70d504f495d105d17b14a24b4907c", size = 6344383 },
{ url = "https://files.pythonhosted.org/packages/1d/72/cf78ac9780bb93c28328f408973845a309d4d145041665f734572ced1b52/pillow-12.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0dd2064cbc55aaec028ef5fbb60fa47bb6c3e7918e07ff17935284b227a9d2df", size = 7052934 },
{ url = "https://files.pythonhosted.org/packages/20/20/25e0f4dc178a6bc0696793720055519a0de89e7661dae886992decbd2f81/pillow-12.3.0-cp312-cp312-win32.whl", hash = "sha256:dbce0b29841537a2fa4a214c2bbf14de3587c9680caa9b4e217568472490b28f", size = 6472684 },
{ url = "https://files.pythonhosted.org/packages/45/89/da2f7971a317f83d807fdd4065c0af40208e59e692cc43d315a71a0e96d1/pillow-12.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a2b55dd6b2a4c4b7d87ffa56bdb33fdc5fdb9a462173861a7bc097f17d91cb09", size = 7227137 },
{ url = "https://files.pythonhosted.org/packages/de/47/4845a0a6c0dbf1db8456bd9fc791f13c5ced7ced20606d08a0aacfd25b49/pillow-12.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:331b624368d4f1d069149002f25f44bc61c8919ce8ddb3c45bdad8f6e2d89510", size = 2568267 },
{ url = "https://files.pythonhosted.org/packages/9d/ac/31fb64e1e7efb5a4b50cd3d92049ba89ac6e4d8d3bb6a74e15048ca3353e/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:21900ce7ba264168cd50defae43cd75d25c833ad4ad6e73ffc5596d12e25ac89", size = 4161684 },
{ url = "https://files.pythonhosted.org/packages/87/b4/9805e23d2b4d77842b468513841fda254ee42f0289d25088340e4ff46e2d/pillow-12.3.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:4e8c2a84d977f50b9daed6eeaf3baef67d00d5d74d932288f02cb94518ee3ace", size = 4255487 },
{ url = "https://files.pythonhosted.org/packages/df/39/ecf519435a200c693fe053a6ee4d835b41cf963a4dfc2551c4e637cb2a71/pillow-12.3.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:ae26d61dfa7a47befdc7572b521024e8745f3d809bd95ca9505a7bba9ef849ec", size = 3696433 },
{ url = "https://files.pythonhosted.org/packages/42/92/2fc3ffad878ae8dd5469ec1bc8eb83b71f48e13efdf68f02709003982a32/pillow-12.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7a743ff716f746fc19a9557f60dab1600d4613255f8a7aeb3cdde4db7eb15a66", size = 5345889 },
{ url = "https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35", size = 4780109 },
{ url = "https://files.pythonhosted.org/packages/1f/01/e18aff37cb0b4aac47ac90f016d347a49aca667ef97f190b06ac2aabc928/pillow-12.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7401aebd7f581d7f83a439d87d474999317ee099218e5ad25d125290990ba65", size = 6263736 },
{ url = "https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3", size = 6937129 },
{ url = "https://files.pythonhosted.org/packages/70/4d/105627a13300c5e0df1d174230b32fd1273062c96f7745fd552b945d1e1d/pillow-12.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:571b9fcb07b97ef3a492028fb3d2dc0993ca23a06138b0315286566d29ef718a", size = 6339562 },
{ url = "https://files.pythonhosted.org/packages/6b/1d/f13de01a553988ab895ba1c722e06cf3144d4f57656fd5b81b6d881f1179/pillow-12.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:756c768d0c9c2955feb7a56c37ea24aea2e369f8d36a88da270b6a9f19e62b5e", size = 7049439 },
{ url = "https://files.pythonhosted.org/packages/c9/f9/066794cca041b969964f779ee5fa66a9498bbf34248ac39c5d7954e4198f/pillow-12.3.0-cp313-cp313-win32.whl", hash = "sha256:a876864214e136f0eb367788dbd7df045f4806801518e2cfe9e13229cfe06d8f", size = 6473287 },
{ url = "https://files.pythonhosted.org/packages/a6/9b/7a58e61d62be561da3a356fe2384d4059a6345fc130e23ef1c36a5b81d24/pillow-12.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1cca606cd25738df4ed873d5ad46bbdb3d83b5cbca291f6b4ff13a4df6b0bbe8", size = 7239691 },
{ url = "https://files.pythonhosted.org/packages/aa/b0/c4ed4f0ef8f8fa5ee8351537db6650bb8189f7e118842978dd6589065692/pillow-12.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:b629de27fda84b42cde7edef0d85f13b958b47f6e9bbcbba9b673c562a89bd8b", size = 2568185 },
{ url = "https://files.pythonhosted.org/packages/dc/01/001f65b68192f0228cc1dbbc8d2530ab5d58b61037ba0587f946fea607cd/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:9cf95fe4d0f84c82d282745d9bb08ad9f926efa00be4697e767b814ce40d4330", size = 4161736 },
{ url = "https://files.pythonhosted.org/packages/1a/d2/0219746d0fd16fc8a84498e79452375be3797d3ce4044596ce565164b84f/pillow-12.3.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:8728f216dcdb6e6d555cf971cb34076139ad74b31fc2c14da4fafc741c5f6217", size = 4255435 },
{ url = "https://files.pythonhosted.org/packages/c8/02/8d0bc62ef0302318c46ff2a512822d2610e81c7aa46c9b3abe6cbaca5ad0/pillow-12.3.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:a45650e8ce7fafffd731db8550230db6b0d306d181a90b67d3e6bca2f1990930", size = 3696262 },
{ url = "https://files.pythonhosted.org/packages/85/e2/73c77d218410b14f5f2d565e8a998d5317b7b9c75368d29985139f7a46f0/pillow-12.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:ba54cfebe86920a559a7c4d6b9050791c20513650a1952ebe3368c7dc70306f8", size = 5350344 },
{ url = "https://files.pythonhosted.org/packages/c7/da/32c752228ae345f489e3a42499d817b6c3996da7e8a3bc7a04fc806b243b/pillow-12.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e158cb00350dc278f3b91551101aa7d12415a66ebf2c91d8d5ac14e56ddd3ad0", size = 4780131 },
{ url = "https://files.pythonhosted.org/packages/b1/9d/8b2c807dbef61a5197c047afe99823787eb66f63daf9fb2432f91d6f0462/pillow-12.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9aeb04d6aef139de265b29683e119b638208f88cf73cdd1658aa07221165321", size = 6263757 },
{ url = "https://files.pythonhosted.org/packages/5c/44/c85361f65dbe00eea8576ee467c768d25129989efb76e94f205e9ca9bb46/pillow-12.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:251bf95b67017e27b13d82f5b326234ca62d70f9cf4c2b9032de2358a3b12c7b", size = 6936962 },
{ url = "https://files.pythonhosted.org/packages/18/7e/e483414b35800b86b6f08dbbc7803fb5cd52c4d6f897f47d53ea2c7e6f65/pillow-12.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fe3cca2e4e8a592be0f269a1ca4835c25199d9f3ce815c8491048f785b0a0198", size = 6339171 },
{ url = "https://files.pythonhosted.org/packages/f0/f4/68c491844841ede6bed70189546b3ee9731cf9f2cbad396faff5e1ccba45/pillow-12.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:23aceaa007d6172b02c277f0cd359c79492bbb14f7072b4ede9fbcaf20648130", size = 7048116 },
{ url = "https://files.pythonhosted.org/packages/a3/34/77f3f793fed8efc7d243f21b33c5a3f0d1c97ee70346d3db855587e155ff/pillow-12.3.0-cp314-cp314-win32.whl", hash = "sha256:af8d94b0db561cf68b88a267c5c44b49e134f525d0dc2cb7ed413a66bc23559a", size = 6467209 },
{ url = "https://files.pythonhosted.org/packages/f1/e0/492879f69d94f91f60fc8cd05ba03650e9520afebb2fb7aa12777d7c7f38/pillow-12.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:fdafc9cce40277e0f7a0feabce0ee50dd2fa1800f3b38015e51296b5e814048d", size = 7237707 },
{ url = "https://files.pythonhosted.org/packages/c9/ac/6b11f2875f1c2ac040d84e1bbf9cf22a88038f901ca1037898b280b38365/pillow-12.3.0-cp314-cp314-win_arm64.whl", hash = "sha256:e91206ee562682b51b98ef4b26a6ef48fd84e15fd4c4bc5ec768eb641d206838", size = 2565995 },
{ url = "https://files.pythonhosted.org/packages/52/69/c2208e56af9bfc1913afb24020297a691eb1d4ef688474c8a04913f65e04/pillow-12.3.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:164b31cd1a0490ab6efae01aa5df49da7061be0af1b30e035b6e9a1bfe34ee6e", size = 5352503 },
{ url = "https://files.pythonhosted.org/packages/07/70/e5686d753e898a45d778ff1718dba8516ead6ab6b95d85fc8c4b70650cf2/pillow-12.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5afb51d599ea772b8365ae807ae557f18bccfe46ab261fd1c2a9ed700fc6eb17", size = 4782956 },
{ url = "https://files.pythonhosted.org/packages/d5/37/25c6692f06927ee973ff18c8d9ee98ad0b4d84ee67a09610c2dd1447958e/pillow-12.3.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3edce1d53195db527e0191f84b71d02022de0540bf43a16ed734ed7537b07385", size = 6322855 },
{ url = "https://files.pythonhosted.org/packages/cc/91/420637fcb8f1bc11029e403b4538e6694744428d8246118e45719f944556/pillow-12.3.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bf16ba1b4d0b6b7c8e534936632270cf70eb00dbe09005bc345b2677b726855c", size = 6989642 },
{ url = "https://files.pythonhosted.org/packages/10/08/b94d7811281ccf0d143a1cf768d1c49e1e54af63e7b708ab2ee3eb87face/pillow-12.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:24870b09b224f7ae3c39ed07d10e819d06f8720bc551847b1d623832b5b0e28d", size = 6391281 },
{ url = "https://files.pythonhosted.org/packages/d2/87/24233f785f55474dc02ce3e739c5528a77e3a862e9333d1dd7a25cc31f70/pillow-12.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:30f2aa603c41533cc25c05acd0da21636e84a315768feb631c937177db558931", size = 7096716 },
{ url = "https://files.pythonhosted.org/packages/23/26/fcb2f6e37175b04f53570b59937867e2b80ee1685e744023153028fc14f9/pillow-12.3.0-cp314-cp314t-win32.whl", hash = "sha256:4b0a7fe987b14c31ebda6083f74f22b561fd3739bc0ac51e019622e3d72668c7", size = 6474125 },
{ url = "https://files.pythonhosted.org/packages/90/de/3634abee5f1c9e13c56787b7d5517b0ba8d6de51700b95578cf338349c9f/pillow-12.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:962864dc93511324d51ddbb5b9f8731bf71675b93ca612a07441896f4688fb8c", size = 7242939 },
{ url = "https://files.pythonhosted.org/packages/ce/2a/fd13f8eb24de5714a6eb444a3d67e2842c6c576e159a43793adf23051351/pillow-12.3.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0740a512dc522224c77d9aa5a8d70d8b7d73fb91f2c21125d8d025d3b8990e45", size = 2567506 },
{ url = "https://files.pythonhosted.org/packages/5d/dc/8fdce34ec725a33c81c6ba122b904d6b9024e50ea9ac7bede62fab54506c/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0feb2e9d6ad6c9e3c06effe9d00f3f1e618a6643273576b016f591e9315a7139", size = 4162063 },
{ url = "https://files.pythonhosted.org/packages/76/66/2044b9a63d3b84ff048228dfcb7cd9bf0df983e8470971bf7d4c57b693de/pillow-12.3.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:9e881fca225083806662a5c43d627d215f258ff43c890f831966c7d7ba9c7402", size = 4255549 },
{ url = "https://files.pythonhosted.org/packages/52/7e/1f67e6f4ece6b582ee4b539decbcc9f848dc245a93ed8cd7338bafef72f1/pillow-12.3.0-cp315-cp315-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:4998562bf62a445225f22e07c896bb04b35b1b1f2eb6d760584c9c51d7a5f78c", size = 3696331 },
{ url = "https://files.pythonhosted.org/packages/12/40/d306fc2c8e4d45d7f175c77edca7063be7b86fe7fe6e68f4353bf71d808c/pillow-12.3.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:dc624f6bc473dacdf7ef7eb8678d0d08edf15cd94fad6ae5c7d6cc67a4e4902f", size = 5350370 },
{ url = "https://files.pythonhosted.org/packages/dd/44/668fb1437e8ce420f62d6106eb66e44a5971602a4d794615bdf79315d82d/pillow-12.3.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:71d6097b330eea8fd15097780c8e89cb1a8ce7838669f48c5bacd6f663dd4701", size = 4780147 },
{ url = "https://files.pythonhosted.org/packages/0c/08/93fa2e70e30a2d81547e481b6ee2bb9522117221fb1e0ce4b5df70967677/pillow-12.3.0-cp315-cp315-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:28ce87c5ab450a9dd970b52e5aca5fe63ed432d18a2eaddd1979a00a1ba24ace", size = 6273659 },
{ url = "https://files.pythonhosted.org/packages/f8/6d/043e96ff814fc31a33077e4cba86082167db520c93632afdf2042febbb0c/pillow-12.3.0-cp315-cp315-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6b02afb9b97f65fbca5f31db6a2a3ba21aa93030225f150fa3f249717e938fb4", size = 6947439 },
{ url = "https://files.pythonhosted.org/packages/af/92/ba71d2ee2ac0edf3fa33bd9d5ee9ee080da70b1766f3ca3934f9938ddac9/pillow-12.3.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:1182d52bc2d5e5d7d0949503aa7e36d12f42205dc287e4883f407b1988820d39", size = 6353577 },
{ url = "https://files.pythonhosted.org/packages/0f/ce/e63064e2122923ff687c8ad792d0d736a7b3920a56a46982e81a7fdd25d6/pillow-12.3.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:e795b7eb908249c4e43c7c99fac7c2c75dab0c43566e37db472a355f63693d71", size = 7060394 },
{ url = "https://files.pythonhosted.org/packages/54/76/a09cc3ccc8d773a7283d34c38bec1708f9e3cc932093cbc4c5e71ac4060b/pillow-12.3.0-cp315-cp315-win32.whl", hash = "sha256:57b3d78c95ba9059768b10e28b813002261d3f3dfc55cc48b0c988f625175827", size = 6467375 },
{ url = "https://files.pythonhosted.org/packages/3e/03/1846c49ba3b1d5550392a4bbd06d6fb4578e1cd91a803198b5c90f5f7d53/pillow-12.3.0-cp315-cp315-win_amd64.whl", hash = "sha256:fa4ecea169a355be7a3ade2c783e2ed12f0e40d2c5621cda8b3297faf7fbb9f5", size = 7237048 },
{ url = "https://files.pythonhosted.org/packages/fb/bb/89f35dcc79610423f9f195504d7def7f0d1416a711541b42867e25fe3412/pillow-12.3.0-cp315-cp315-win_arm64.whl", hash = "sha256:877c3f311ff35410f690861c4409e7ccbf0cd2f878e50628a28e5a0bb689e658", size = 2566006 },
{ url = "https://files.pythonhosted.org/packages/30/88/707027ba09942dfa2c28759b5c222d769290a41c6d20ea60ec250801941f/pillow-12.3.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:e9871b1ffbfa9656b60aeee92ed5136a5742696006fa322b29ea3d8da0ecc9cf", size = 5352509 },
{ url = "https://files.pythonhosted.org/packages/b0/6d/00352fa25332c2569cd387851f568cc5a4b75a9adbfb37ac4fbce4c02eec/pillow-12.3.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:53aa02d20d10c3d814d536aa4e5ac9b84ca0ff5a88377963b085ad6822f93e64", size = 4783167 },
{ url = "https://files.pythonhosted.org/packages/13/4f/9e049dfa21af7c22427275720e2490267ba8138120add5c4c574deb69782/pillow-12.3.0-cp315-cp315t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:446c34dcc4324b084a53b705127dc15717b22c5e140ae0a3c38349d4efec071e", size = 6329237 },
{ url = "https://files.pythonhosted.org/packages/36/16/cf6eeaae8d0fce8dd390a33437cf68c5d5bd73834a2bc6e2f14efda0ab45/pillow-12.3.0-cp315-cp315t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cf1845d02ad822a369a49f2bb9345b1614744267682e7a03527dc3bf6eea1777", size = 6997047 },
{ url = "https://files.pythonhosted.org/packages/1e/69/dbf769bdd55f48bf5733cac28edc6364ffaa072ec9ba336266e4fe66be55/pillow-12.3.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:186941b6aef820ad110fb01fb06eb925374dc3a21b17e37ec9a53b250c6fe2d1", size = 6400440 },
{ url = "https://files.pythonhosted.org/packages/a0/e1/ffc9cfc2eea0d178da8018e18e959301ad9d6bc9f3edb7181e748a474b97/pillow-12.3.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:f13c32a3abd6079a66d9526e18dad9b6d280384d49d7c54040cd57b6424041d9", size = 7105895 },
{ url = "https://files.pythonhosted.org/packages/18/f0/a5595c1e8c3ae44b9828cb2f0fa8155e5095ef04d6327b8f61cf44a3df85/pillow-12.3.0-cp315-cp315t-win32.whl", hash = "sha256:1657923d2d45afb66526e5b933e5b3052e6bdea196c90d3abb2424e18c77dae8", size = 6474384 },
{ url = "https://files.pythonhosted.org/packages/e4/04/62bcd9f844984c5938d3b05264a61d797a29d3e0812341a8204af70bbdee/pillow-12.3.0-cp315-cp315t-win_amd64.whl", hash = "sha256:8cd2f7bdda092d99c9fc2fb7391354f306d01443d22785d0cbfafa2e2c8bb418", size = 7243537 },
{ url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491 },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.6.0" version = "1.6.0"
@@ -438,51 +49,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
] ]
[[package]]
name = "protobuf"
version = "7.35.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/da/01/9ef0afd7999eb9badb3a768b4aedd78c86d4c65cfaf1958ab276199e76b4/protobuf-7.35.1.tar.gz", hash = "sha256:ce115a26fe0c39a2c29973d914d327e516a6455464489fe3cd1e51a1b354f81a", size = 458717 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/10/03/8aeeb7458d22546bf64b5250ca1daeb5ff757d900e8e4a7476c6f0db843e/protobuf-7.35.1-cp310-abi3-macosx_10_9_universal2.whl", hash = "sha256:24f857477359a85c0c235261b8ba905fd51b2562f4a64ca1df5473f29850cbf6", size = 433226 },
{ url = "https://files.pythonhosted.org/packages/37/4b/dfb89eb0e652a1ff073c39a59fb5e3a83cfe9b57a2c83fa6d78270101767/protobuf-7.35.1-cp310-abi3-manylinux2014_aarch64.whl", hash = "sha256:11d6b0ec246892d85215b0a13ca6e0233cf5284b68f0ac02646427f4ff88a799", size = 328847 },
{ url = "https://files.pythonhosted.org/packages/0f/58/dc12f2cd484951524af6e3382c785869b9b3fb5e52ee95ae23add53ee8f9/protobuf-7.35.1-cp310-abi3-manylinux2014_s390x.whl", hash = "sha256:b73f9489a4b8b1c9cb1f8ed951c736392592edb24b9d6819f36d2e10b171d5b4", size = 344030 },
{ url = "https://files.pythonhosted.org/packages/e4/be/5b3cfe508bfab6761414ff944e3366eb13be4fd71efcd69450f89ba39f43/protobuf-7.35.1-cp310-abi3-manylinux2014_x86_64.whl", hash = "sha256:74758715c53d7158fb76caf4f0cfdacc5329a4b1bb994f865d6cf302d413a1c4", size = 327130 },
{ url = "https://files.pythonhosted.org/packages/d8/bc/6d6c7ba8709c85f8f2c390b2b118d6fb08a783676a572271851bf45a7d22/protobuf-7.35.1-cp310-abi3-win32.whl", hash = "sha256:353652e4efd0bca5b5fc2656abf8307ef351f0cf938c9eba09f0e09c20a25c30", size = 428945 },
{ url = "https://files.pythonhosted.org/packages/0a/19/8d0cb6f20a1ef7b18f1c8986ad5783f22f84cce39c6ce9a6e645ea55192e/protobuf-7.35.1-cp310-abi3-win_amd64.whl", hash = "sha256:230a75ddfc2de4806e56696ce9640c1cdfdb6543b7cfce98d42a4c0a0e7bdb87", size = 439996 },
{ url = "https://files.pythonhosted.org/packages/19/c7/5f7c636ec43e0c545e28d1f1db71990108306f7bdcb89f069ba97e428e7f/protobuf-7.35.1-py3-none-any.whl", hash = "sha256:4bc97768d8fe4ad6743c8a19403e314511ed9f6d13205b687e52421c023ac1b9", size = 171659 },
]
[[package]]
name = "pyclipper"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f6/21/3c06205bb407e1f79b73b7b4dfb3950bd9537c4f625a68ab5cc41177f5bc/pyclipper-1.4.0.tar.gz", hash = "sha256:9882bd889f27da78add4dd6f881d25697efc740bf840274e749988d25496c8e1", size = 54489 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/1b/7a07b68e0842324d46c03e512d8eefa9cb92ba2a792b3b4ebf939dafcac3/pyclipper-1.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:222ac96c8b8281b53d695b9c4fedc674f56d6d4320ad23f1bdbd168f4e316140", size = 265676 },
{ url = "https://files.pythonhosted.org/packages/6b/dd/8bd622521c05d04963420ae6664093f154343ed044c53ea260a310c8bb4d/pyclipper-1.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f3672dbafbb458f1b96e1ee3e610d174acb5ace5bd2ed5d1252603bb797f2fc6", size = 140458 },
{ url = "https://files.pythonhosted.org/packages/7a/06/6e3e241882bf7d6ab23d9c69ba4e85f1ec47397cbbeee948a16cf75e21ed/pyclipper-1.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1f807e2b4760a8e5c6d6b4e8c1d71ef52b7fe1946ff088f4fa41e16a881a5ca", size = 978235 },
{ url = "https://files.pythonhosted.org/packages/cf/f4/3418c1cd5eea640a9fa2501d4bc0b3655fa8d40145d1a4f484b987990a75/pyclipper-1.4.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce1f83c9a4e10ea3de1959f0ae79e9a5bd41346dff648fee6228ba9eaf8b3872", size = 961388 },
{ url = "https://files.pythonhosted.org/packages/ac/94/c85401d24be634af529c962dd5d781f3cb62a67cd769534df2cb3feee97a/pyclipper-1.4.0-cp312-cp312-win32.whl", hash = "sha256:3ef44b64666ebf1cb521a08a60c3e639d21b8c50bfbe846ba7c52a0415e936f4", size = 95169 },
{ url = "https://files.pythonhosted.org/packages/97/77/dfea08e3b230b82ee22543c30c35d33d42f846a77f96caf7c504dd54fab1/pyclipper-1.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:d1e5498d883b706a4ce636247f0d830c6eb34a25b843a1b78e2c969754ca9037", size = 104619 },
{ url = "https://files.pythonhosted.org/packages/67/d0/cbce7d47de1e6458f66a4d999b091640134deb8f2c7351eab993b70d2e10/pyclipper-1.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d49df13cbb2627ccb13a1046f3ea6ebf7177b5504ec61bdef87d6a704046fd6e", size = 264342 },
{ url = "https://files.pythonhosted.org/packages/ce/cc/742b9d69d96c58ac156947e1b56d0f81cbacbccf869e2ac7229f2f86dc4e/pyclipper-1.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37bfec361e174110cdddffd5ecd070a8064015c99383d95eb692c253951eee8a", size = 139839 },
{ url = "https://files.pythonhosted.org/packages/db/48/dd301d62c1529efdd721b47b9e5fb52120fcdac5f4d3405cfc0d2f391414/pyclipper-1.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:14c8bdb5a72004b721c4e6f448d2c2262d74a7f0c9e3076aeff41e564a92389f", size = 972142 },
{ url = "https://files.pythonhosted.org/packages/07/bf/d493fd1b33bb090fa64e28c1009374d5d72fa705f9331cd56517c35e381e/pyclipper-1.4.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f2a50c22c3a78cb4e48347ecf06930f61ce98cf9252f2e292aa025471e9d75b1", size = 952789 },
{ url = "https://files.pythonhosted.org/packages/cf/88/b95ea8ea21ddca34aa14b123226a81526dd2faaa993f9aabd3ed21231604/pyclipper-1.4.0-cp313-cp313-win32.whl", hash = "sha256:c9a3faa416ff536cee93417a72bfb690d9dea136dc39a39dbbe1e5dadf108c9c", size = 94817 },
{ url = "https://files.pythonhosted.org/packages/ba/42/0a1920d276a0e1ca21dc0d13ee9e3ba10a9a8aa3abac76cd5e5a9f503306/pyclipper-1.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:d4b2d7c41086f1927d14947c563dfc7beed2f6c0d9af13c42fe3dcdc20d35832", size = 104007 },
{ url = "https://files.pythonhosted.org/packages/1a/20/04d58c70f3ccd404f179f8dd81d16722a05a3bf1ab61445ee64e8218c1f8/pyclipper-1.4.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:7c87480fc91a5af4c1ba310bdb7de2f089a3eeef5fe351a3cedc37da1fcced1c", size = 265167 },
{ url = "https://files.pythonhosted.org/packages/bd/2e/a570c1abe69b7260ca0caab4236ce6ea3661193ebf8d1bd7f78ccce537a5/pyclipper-1.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81d8bb2d1fb9d66dc7ea4373b176bb4b02443a7e328b3b603a73faec088b952e", size = 139966 },
{ url = "https://files.pythonhosted.org/packages/e8/3b/e0859e54adabdde8a24a29d3f525ebb31c71ddf2e8d93edce83a3c212ffc/pyclipper-1.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:773c0e06b683214dcfc6711be230c83b03cddebe8a57eae053d4603dd63582f9", size = 968216 },
{ url = "https://files.pythonhosted.org/packages/f6/6b/e3c4febf0a35ae643ee579b09988dd931602b5bf311020535fd9e5b7e715/pyclipper-1.4.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9bc45f2463d997848450dbed91c950ca37c6cf27f84a49a5cad4affc0b469e39", size = 954198 },
{ url = "https://files.pythonhosted.org/packages/fc/74/728efcee02e12acb486ce9d56fa037120c9bf5b77c54bbdbaa441c14a9d9/pyclipper-1.4.0-cp314-cp314-win32.whl", hash = "sha256:0b8c2105b3b3c44dbe1a266f64309407fe30bf372cf39a94dc8aaa97df00da5b", size = 96951 },
{ url = "https://files.pythonhosted.org/packages/e3/d7/7f4354e69f10a917e5c7d5d72a499ef2e10945312f5e72c414a0a08d2ae4/pyclipper-1.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:6c317e182590c88ec0194149995e3d71a979cfef3b246383f4e035f9d4a11826", size = 106782 },
{ url = "https://files.pythonhosted.org/packages/63/60/fc32c7a3d7f61a970511ec2857ecd09693d8ac80d560ee7b8e67a6d268c9/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:f160a2c6ba036f7eaf09f1f10f4fbfa734234af9112fb5187877efed78df9303", size = 269880 },
{ url = "https://files.pythonhosted.org/packages/49/df/c4a72d3f62f0ba03ec440c4fff56cd2d674a4334d23c5064cbf41c9583f6/pyclipper-1.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:a9f11ad133257c52c40d50de7a0ca3370a0cdd8e3d11eec0604ad3c34ba549e9", size = 141706 },
{ url = "https://files.pythonhosted.org/packages/c5/0b/cf55df03e2175e1e2da9db585241401e0bc98f76bee3791bed39d0313449/pyclipper-1.4.0-cp314-cp314t-win32.whl", hash = "sha256:bbc827b77442c99deaeee26e0e7f172355ddb097a5e126aea206d447d3b26286", size = 105308 },
{ url = "https://files.pythonhosted.org/packages/8f/dc/53df8b6931d47080b4fe4ee8450d42e660ee1c5c1556c7ab73359182b769/pyclipper-1.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:29dae3e0296dff8502eeb7639fcfee794b0eec8590ba3563aee28db269da6b04", size = 117608 },
]
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.20.0" version = "2.20.0"
@@ -507,166 +73,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536 }, { url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536 },
] ]
[[package]]
name = "pyyaml"
version = "6.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 },
{ url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 },
{ url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 },
{ url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 },
{ url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 },
{ url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 },
{ url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 },
{ url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 },
{ url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 },
{ url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 },
{ url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 },
{ url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 },
{ url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 },
{ url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 },
{ url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 },
{ url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 },
{ url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 },
{ url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 },
{ url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 },
{ url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 },
{ url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814 },
{ url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809 },
{ url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454 },
{ url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355 },
{ url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175 },
{ url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228 },
{ url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194 },
{ url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429 },
{ url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912 },
{ url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108 },
{ url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641 },
{ url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901 },
{ url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132 },
{ url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261 },
{ url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272 },
{ url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923 },
{ url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062 },
{ url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341 },
]
[[package]]
name = "rapidocr"
version = "3.9.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorlog" },
{ name = "numpy" },
{ name = "omegaconf" },
{ name = "opencv-python" },
{ name = "pillow" },
{ name = "pyclipper" },
{ name = "pyyaml" },
{ name = "requests" },
{ name = "shapely" },
{ name = "six" },
{ name = "tqdm" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/ed/0ee9b9281986974be9d2406ae0134c8d7c91d2fc613f16ffda9701eeda6f/rapidocr-3.9.2-py3-none-any.whl", hash = "sha256:04d6b8d151f823d930bd91910555f57bea897c0c44fa6794267b94cf9c1ef9a0", size = 27275208 },
]
[[package]]
name = "requests"
version = "2.34.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "charset-normalizer" },
{ name = "idna" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ac/c3/e2a2b89f2d3e2179abd6d00ebd70bff6273f37fb3e0cc209f48b39d00cbf/requests-2.34.2.tar.gz", hash = "sha256:f288924cae4e29463698d6d60bc6a4da69c89185ad1e0bcc4104f584e960b9ed", size = 142856 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a0/f4/c67b0b3f1b9245e8d266f0f112c500d50e5b4e83cb6f3b71b6528104182a/requests-2.34.2-py3-none-any.whl", hash = "sha256:2a0d60c172f83ac6ab31e4554906c0f3b3588d37b5cb939b1c061f4907e278e0", size = 73075 },
]
[[package]]
name = "shapely"
version = "2.1.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550 },
{ url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556 },
{ url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308 },
{ url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844 },
{ url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842 },
{ url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714 },
{ url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745 },
{ url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861 },
{ url = "https://files.pythonhosted.org/packages/c3/90/98ef257c23c46425dc4d1d31005ad7c8d649fe423a38b917db02c30f1f5a/shapely-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b510dda1a3672d6879beb319bc7c5fd302c6c354584690973c838f46ec3e0fa8", size = 1832644 },
{ url = "https://files.pythonhosted.org/packages/6d/ab/0bee5a830d209adcd3a01f2d4b70e587cdd9fd7380d5198c064091005af8/shapely-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8cff473e81017594d20ec55d86b54bc635544897e13a7cfc12e36909c5309a2a", size = 1642887 },
{ url = "https://files.pythonhosted.org/packages/2d/5e/7d7f54ba960c13302584c73704d8c4d15404a51024631adb60b126a4ae88/shapely-2.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe7b77dc63d707c09726b7908f575fc04ff1d1ad0f3fb92aec212396bc6cfe5e", size = 2970931 },
{ url = "https://files.pythonhosted.org/packages/f2/a2/83fc37e2a58090e3d2ff79175a95493c664bcd0b653dd75cb9134645a4e5/shapely-2.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7ed1a5bbfb386ee8332713bf7508bc24e32d24b74fc9a7b9f8529a55db9f4ee6", size = 3082855 },
{ url = "https://files.pythonhosted.org/packages/44/2b/578faf235a5b09f16b5f02833c53822294d7f21b242f8e2d0cf03fb64321/shapely-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a84e0582858d841d54355246ddfcbd1fce3179f185da7470f41ce39d001ee1af", size = 3979960 },
{ url = "https://files.pythonhosted.org/packages/4d/04/167f096386120f692cc4ca02f75a17b961858997a95e67a3cb6a7bbd6b53/shapely-2.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc3487447a43d42adcdf52d7ac73804f2312cbfa5d433a7d2c506dcab0033dfd", size = 4142851 },
{ url = "https://files.pythonhosted.org/packages/48/74/fb402c5a6235d1c65a97348b48cdedb75fb19eca2b1d66d04969fc1c6091/shapely-2.1.2-cp313-cp313-win32.whl", hash = "sha256:9c3a3c648aedc9f99c09263b39f2d8252f199cb3ac154fadc173283d7d111350", size = 1541890 },
{ url = "https://files.pythonhosted.org/packages/41/47/3647fe7ad990af60ad98b889657a976042c9988c2807cf322a9d6685f462/shapely-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:ca2591bff6645c216695bdf1614fca9c82ea1144d4a7591a466fef64f28f0715", size = 1722151 },
{ url = "https://files.pythonhosted.org/packages/3c/49/63953754faa51ffe7d8189bfbe9ca34def29f8c0e34c67cbe2a2795f269d/shapely-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2d93d23bdd2ed9dc157b46bc2f19b7da143ca8714464249bef6771c679d5ff40", size = 1834130 },
{ url = "https://files.pythonhosted.org/packages/7f/ee/dce001c1984052970ff60eb4727164892fb2d08052c575042a47f5a9e88f/shapely-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:01d0d304b25634d60bd7cf291828119ab55a3bab87dc4af1e44b07fb225f188b", size = 1642802 },
{ url = "https://files.pythonhosted.org/packages/da/e7/fc4e9a19929522877fa602f705706b96e78376afb7fad09cad5b9af1553c/shapely-2.1.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8d8382dd120d64b03698b7298b89611a6ea6f55ada9d39942838b79c9bc89801", size = 3018460 },
{ url = "https://files.pythonhosted.org/packages/a1/18/7519a25db21847b525696883ddc8e6a0ecaa36159ea88e0fef11466384d0/shapely-2.1.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:19efa3611eef966e776183e338b2d7ea43569ae99ab34f8d17c2c054d3205cc0", size = 3095223 },
{ url = "https://files.pythonhosted.org/packages/48/de/b59a620b1f3a129c3fecc2737104a0a7e04e79335bd3b0a1f1609744cf17/shapely-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:346ec0c1a0fcd32f57f00e4134d1200e14bf3f5ae12af87ba83ca275c502498c", size = 4030760 },
{ url = "https://files.pythonhosted.org/packages/96/b3/c6655ee7232b417562bae192ae0d3ceaadb1cc0ffc2088a2ddf415456cc2/shapely-2.1.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6305993a35989391bd3476ee538a5c9a845861462327efe00dd11a5c8c709a99", size = 4170078 },
{ url = "https://files.pythonhosted.org/packages/a0/8e/605c76808d73503c9333af8f6cbe7e1354d2d238bda5f88eea36bfe0f42a/shapely-2.1.2-cp313-cp313t-win32.whl", hash = "sha256:c8876673449f3401f278c86eb33224c5764582f72b653a415d0e6672fde887bf", size = 1559178 },
{ url = "https://files.pythonhosted.org/packages/36/f7/d317eb232352a1f1444d11002d477e54514a4a6045536d49d0c59783c0da/shapely-2.1.2-cp313-cp313t-win_amd64.whl", hash = "sha256:4a44bc62a10d84c11a7a3d7c1c4fe857f7477c3506e24c9062da0db0ae0c449c", size = 1739756 },
{ url = "https://files.pythonhosted.org/packages/fc/c4/3ce4c2d9b6aabd27d26ec988f08cb877ba9e6e96086eff81bfea93e688c7/shapely-2.1.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:9a522f460d28e2bf4e12396240a5fc1518788b2fcd73535166d748399ef0c223", size = 1831290 },
{ url = "https://files.pythonhosted.org/packages/17/b9/f6ab8918fc15429f79cb04afa9f9913546212d7fb5e5196132a2af46676b/shapely-2.1.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1ff629e00818033b8d71139565527ced7d776c269a49bd78c9df84e8f852190c", size = 1641463 },
{ url = "https://files.pythonhosted.org/packages/a5/57/91d59ae525ca641e7ac5551c04c9503aee6f29b92b392f31790fcb1a4358/shapely-2.1.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f67b34271dedc3c653eba4e3d7111aa421d5be9b4c4c7d38d30907f796cb30df", size = 2970145 },
{ url = "https://files.pythonhosted.org/packages/8a/cb/4948be52ee1da6927831ab59e10d4c29baa2a714f599f1f0d1bc747f5777/shapely-2.1.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:21952dc00df38a2c28375659b07a3979d22641aeb104751e769c3ee825aadecf", size = 3073806 },
{ url = "https://files.pythonhosted.org/packages/03/83/f768a54af775eb41ef2e7bec8a0a0dbe7d2431c3e78c0a8bdba7ab17e446/shapely-2.1.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:1f2f33f486777456586948e333a56ae21f35ae273be99255a191f5c1fa302eb4", size = 3980803 },
{ url = "https://files.pythonhosted.org/packages/9f/cb/559c7c195807c91c79d38a1f6901384a2878a76fbdf3f1048893a9b7534d/shapely-2.1.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cf831a13e0d5a7eb519e96f58ec26e049b1fad411fc6fc23b162a7ce04d9cffc", size = 4133301 },
{ url = "https://files.pythonhosted.org/packages/80/cd/60d5ae203241c53ef3abd2ef27c6800e21afd6c94e39db5315ea0cbafb4a/shapely-2.1.2-cp314-cp314-win32.whl", hash = "sha256:61edcd8d0d17dd99075d320a1dd39c0cb9616f7572f10ef91b4b5b00c4aeb566", size = 1583247 },
{ url = "https://files.pythonhosted.org/packages/74/d4/135684f342e909330e50d31d441ace06bf83c7dc0777e11043f99167b123/shapely-2.1.2-cp314-cp314-win_amd64.whl", hash = "sha256:a444e7afccdb0999e203b976adb37ea633725333e5b119ad40b1ca291ecf311c", size = 1773019 },
{ url = "https://files.pythonhosted.org/packages/a3/05/a44f3f9f695fa3ada22786dc9da33c933da1cbc4bfe876fe3a100bafe263/shapely-2.1.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:5ebe3f84c6112ad3d4632b1fd2290665aa75d4cef5f6c5d77c4c95b324527c6a", size = 1834137 },
{ url = "https://files.pythonhosted.org/packages/52/7e/4d57db45bf314573427b0a70dfca15d912d108e6023f623947fa69f39b72/shapely-2.1.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5860eb9f00a1d49ebb14e881f5caf6c2cf472c7fd38bd7f253bbd34f934eb076", size = 1642884 },
{ url = "https://files.pythonhosted.org/packages/5a/27/4e29c0a55d6d14ad7422bf86995d7ff3f54af0eba59617eb95caf84b9680/shapely-2.1.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b705c99c76695702656327b819c9660768ec33f5ce01fa32b2af62b56ba400a1", size = 3018320 },
{ url = "https://files.pythonhosted.org/packages/9f/bb/992e6a3c463f4d29d4cd6ab8963b75b1b1040199edbd72beada4af46bde5/shapely-2.1.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a1fd0ea855b2cf7c9cddaf25543e914dd75af9de08785f20ca3085f2c9ca60b0", size = 3094931 },
{ url = "https://files.pythonhosted.org/packages/9c/16/82e65e21070e473f0ed6451224ed9fa0be85033d17e0c6e7213a12f59d12/shapely-2.1.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:df90e2db118c3671a0754f38e36802db75fe0920d211a27481daf50a711fdf26", size = 4030406 },
{ url = "https://files.pythonhosted.org/packages/7c/75/c24ed871c576d7e2b64b04b1fe3d075157f6eb54e59670d3f5ffb36e25c7/shapely-2.1.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:361b6d45030b4ac64ddd0a26046906c8202eb60d0f9f53085f5179f1d23021a0", size = 4169511 },
{ url = "https://files.pythonhosted.org/packages/b1/f7/b3d1d6d18ebf55236eec1c681ce5e665742aab3c0b7b232720a7d43df7b6/shapely-2.1.2-cp314-cp314t-win32.whl", hash = "sha256:b54df60f1fbdecc8ebc2c5b11870461a6417b3d617f555e5033f1505d36e5735", size = 1602607 },
{ url = "https://files.pythonhosted.org/packages/9a/f6/f09272a71976dfc138129b8faf435d064a811ae2f708cb147dccdf7aacdb/shapely-2.1.2-cp314-cp314t-win_amd64.whl", hash = "sha256:0036ac886e0923417932c2e6369b6c52e38e0ff5d9120b90eef5cd9a5fc5cae9", size = 1796682 },
]
[[package]]
name = "six"
version = "1.17.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
]
[[package]]
name = "tqdm"
version = "4.70.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/21/3b/6c24bec5be5e743ffd99576daa5cc077722fc7d5bbc00bd133fa0c698dc6/tqdm-4.70.0.tar.gz", hash = "sha256:55b0b0dbd97462d06ebee91e4dac24ed4d4702be82b24f07e6c1d27e08cea220", size = 795438 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/1c/01bfd571a64e7f270e6bab5e33777debe0edc56759233ce84f27dec92d14/tqdm-4.70.0-py3-none-any.whl", hash = "sha256:7f585706bfddbdebf89daac705b2dfcc16890130727d3197ca62c732b4310953", size = 80184 },
]
[[package]]
name = "urllib3"
version = "2.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087 },
]