dev: promote DOSBox-X and remove QEMU support
This commit is contained in:
+2
-19
@@ -1,23 +1,9 @@
|
||||
# QEMU runtime state, downloaded media, and generated captures
|
||||
.qemu/*
|
||||
!.qemu/*.ps1
|
||||
!.qemu/*.mjs
|
||||
!.qemu/*.txt
|
||||
!.qemu/share/
|
||||
!.qemu/share/*.c
|
||||
.qemu/share/fec/
|
||||
# Legacy local QEMU state is no longer used, but may contain large user-owned images.
|
||||
.qemu/
|
||||
|
||||
# Reproducible DOSBox-X/Open Watcom development cache and ephemeral runs
|
||||
.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.
|
||||
# Committing it breaks checkout on Windows.
|
||||
nul
|
||||
@@ -39,6 +25,3 @@ __pycache__/
|
||||
node_modules/
|
||||
.npm/
|
||||
.cache/
|
||||
|
||||
# Temporary DOS build sources
|
||||
.qemu/share/dosag*.c
|
||||
|
||||
@@ -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
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
|
||||
@@ -10,53 +10,39 @@ DOS용 시스템 프로그래밍 언어 Ferro와 그 컴파일러 `fec`. 규범
|
||||
| `SPEC.md` | 언어 명세 + 구현 지시서. 유일한 규범 문서 |
|
||||
| `SPEC.AUDIT.md` | 명세 변경의 문제·결정·근거·구현 영향 누적 로그 |
|
||||
| `tools/README.md` | 호스트 요구사항, 최초 셋업, 자동화 구조 |
|
||||
| `tools/tcpagent/README.md` | DOS 내부 TCP 에이전트 프로토콜과 빌드 |
|
||||
|
||||
VM 자동화 **명령 목록과 플래그는 문서가 아니라 CLI가 규범**이다. 문서에 복제하면
|
||||
반드시 드리프트하므로 아래로 확인한다.
|
||||
개발 환경과 테스트 명령 목록·플래그는 CLI로 확인한다.
|
||||
|
||||
```powershell
|
||||
uv run ferro-vm --help
|
||||
uv run ferro-vm <command> --help
|
||||
uv run ferro-dos --help
|
||||
uv run ferro-dos <command> --help
|
||||
uv run ferro-test --help
|
||||
```
|
||||
|
||||
## 검증 규칙
|
||||
|
||||
- 개발 중 빠른 회귀 검사는 `uv run ferro-test run --through <milestone>`로
|
||||
DOSBox-X에서 수행한다.
|
||||
이 경로도 컴파일러 A를 DOS 내부 Open Watcom으로 매번 새로 빌드한다.
|
||||
- **마일스톤의 최종 공식 검증은 QEMU FreeDOS 내부에서만 한다.** DOSBox-X 결과는
|
||||
개발용 smoke test이며 완료 게이트를 대체하지 않는다.
|
||||
- 컴파일러 A와 생성 C 모두 VM 안의 Open Watcom으로 컴파일한다.
|
||||
컴파일러 A와 bits16은 `WCL`, bits32 생성 C는 `WCL386`.
|
||||
- **호스트에서 컴파일하지 않는다.** WSL이나 호스트 C 컴파일러 결과는 정식 검증으로
|
||||
인정하지 않는다. 호스트는 편집, diff, Git, 파일 전송에만 쓴다.
|
||||
- authoritative workspace는 VM의 `C:\FEC`다.
|
||||
- 마일스톤 완료 기준은 `C:\FEC\BUILD.OK`, `C:\FEC\TEST.OK`, `TEST-DOS.BAT` exit 0
|
||||
세 가지를 모두 확인하는 것이다. 테스트가 증명하지 않는 기능은 완료로 처리하지
|
||||
않는다.
|
||||
- VGA 데모처럼 멀티모달 수동 검증이 필요한 항목은 완료 게이트에서 제외한다.
|
||||
- 컴파일러와 생성 C는 DOSBox-X 내부의 고정된 Open Watcom으로 컴파일한다.
|
||||
컴파일러와 bits16은 `WCL`, bits32 생성 C는 `WCL386`을 쓴다.
|
||||
- 호스트 C 컴파일러 결과는 검증으로 인정하지 않는다. 호스트는 편집, Git, 다운로드,
|
||||
격리 작업공간 준비에만 쓴다.
|
||||
- 실행마다 만들어지는 authoritative workspace는 `C:\FEC`다. 호스트에서는
|
||||
`.dosboxx/runs/<run>/FEC`에 대응한다.
|
||||
- 완료하려는 기능을 직접 검사하는 pytest case가 통과해야 한다. 테스트가 증명하지
|
||||
않는 기능은 완료로 처리하지 않는다.
|
||||
- VGA 데모처럼 수동 검증이 필요한 항목은 자동 완료 게이트에서 제외한다.
|
||||
|
||||
## 빌드 함정
|
||||
|
||||
VM 안에서 반복해서 물렸던 것들. 어기면 원인 찾기 어려운 실패가 난다.
|
||||
|
||||
- **컴파일러 A는 16비트 large model로 빌드한다.** small model은 메모리 부족으로
|
||||
실패한다.
|
||||
- **링크는 `*.obj` 와일드카드로 한다.** DOS 명령줄 길이 제한 때문에 오브젝트를
|
||||
나열할 수 없다. 그래서 `fec/build-dos.bat`는 먼저 `C:\FEC\*.obj`를 지워
|
||||
stale 32비트 test object가 섞이지 않게 한다.
|
||||
- **M4 Watcom 테스트는 `-wx -wcd=202`를 쓴다.** 생성 C의 보수적 미사용 helper 때문에
|
||||
- 컴파일러는 16비트 large model로 빌드한다. small model은 메모리 부족으로 실패한다.
|
||||
- 링크는 `*.obj` 와일드카드로 한다. DOS 명령줄 길이 제한 때문에 오브젝트를 나열할
|
||||
수 없다. `fec/build-dos.bat`는 먼저 stale object를 지운다.
|
||||
- M4 Watcom 테스트는 `-wx -wcd=202`를 쓴다. 생성 C의 보수적 미사용 helper 때문에
|
||||
W202만 끄고 나머지 경고는 오류로 유지한다.
|
||||
- **fixture는 짧은 이름으로 전송한다.** DOS 8.3 파일명 때문에 긴 이름은
|
||||
`BAD-ARI.FE`, `TRY-FPR.FE`처럼 명시적으로 줄여야 한다.
|
||||
- **`D:`에서 빌드하지 않는다.** QEMU의 vvfat 뷰는 교환용이며, 과거 `D:`에서 빌드하다
|
||||
rename 처리 오류로 QEMU가 종료된 적이 있다. 호스트에서 편집한 파일은 `put`으로
|
||||
`C:`에 올린 뒤 컴파일한다.
|
||||
- 긴 DOS 배치가 멈추면 QEMU를 재시작하기 전에 Ctrl+C 주입을 먼저 시도한다.
|
||||
`Terminate batch file ... (Yes/No/All)?`가 뜨면 `y`, `ret`을 보내고 `ping` 복구를
|
||||
확인한다.
|
||||
- fixture는 DOS 8.3 이름으로 실행한다. 긴 이름은 registry에서 명시적으로 줄인다.
|
||||
- `R:`은 읽기 전용 저장소, `W:`은 읽기 전용 Watcom이다. 빌드 산출물은 반드시
|
||||
임시 `C:\FEC`에 쓴다.
|
||||
- 실패 분석이 필요하면 `ferro-test --keep-failed` 또는 `ferro-dos --keep`으로
|
||||
임시 작업공간을 보존한다.
|
||||
|
||||
## 작업 흐름
|
||||
|
||||
@@ -64,12 +50,9 @@ VM 안에서 반복해서 물렸던 것들. 어기면 원인 찾기 어려운
|
||||
구현이 명세와 다르면 둘 중 하나가 틀린 것이므로 그 자리에서 결론을 낸다.
|
||||
- 검증된 마일스톤마다 커밋하고 항상 `origin`에 푸시한다.
|
||||
- primary 브랜치는 `master`다.
|
||||
- `.qemu/*.png`, `.qemu/*.ppm`은 진단용이며 커밋하지 않는다.
|
||||
- `.dosboxx/`의 다운로드, 실행 작업공간, 로그는 커밋하지 않는다.
|
||||
|
||||
## 현재 상태
|
||||
|
||||
- M1~M5 완료 및 QEMU/Open Watcom 검증됨.
|
||||
- 다음은 M6(R1~R8 대여 검사)다. 착수할 때 소유권 로직을 `check.c`/`emit_c.c`에서
|
||||
`own.c/h`로 분리한다 (`SPEC.md` §11.3).
|
||||
- v0.1.6에서 R8(파생 반환), R6(마지막 사용까지 대여), R10(전역 대여 금지)이
|
||||
바뀌었다. 셋 다 own.c의 상태 기계를 건드리므로 분리 이후에 함께 구현한다.
|
||||
현재 구현 상태와 다음 마일스톤은 `SPEC.md`와 테스트 registry를 기준으로 판단한다.
|
||||
과거 VM 이미지나 호스트에 남은 바이너리를 근거로 완료 처리하지 않는다.
|
||||
|
||||
@@ -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
@@ -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
@@ -1,16 +1,14 @@
|
||||
[project]
|
||||
name = "ferrolang"
|
||||
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"
|
||||
dependencies = [
|
||||
"onnxruntime>=1.28.0",
|
||||
"pytest>=9.0.0",
|
||||
"rapidocr>=3.9.2",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
ferro-vm = "ferrolang_vm.cli:main"
|
||||
ferro-dos = "ferrolang_vm.dos_cli:main"
|
||||
ferro-test = "ferrolang_vm.test_cli:main"
|
||||
|
||||
[build-system]
|
||||
|
||||
@@ -1 +1 @@
|
||||
"""Windows-only QEMU and FreeDOS TCP-agent automation."""
|
||||
"""Reproducible DOS development tools for the Ferro compiler."""
|
||||
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
+16
-17
@@ -12,7 +12,7 @@ import zipfile
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .daemon import ROOT
|
||||
from .paths import ROOT
|
||||
from .suite import Case
|
||||
|
||||
|
||||
@@ -96,28 +96,25 @@ def setup(*, accept_watcom_license: bool = False) -> tuple[Path, Path]:
|
||||
raise DosboxError("the DOSBox-X development backend currently supports Windows only")
|
||||
lock = _lock()
|
||||
dosbox, watcom, watcom_required = _required_paths(lock)
|
||||
marker = CACHE / "SETUP.OK"
|
||||
lock_hash = _sha256(LOCK_PATH)
|
||||
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:
|
||||
tools_ready = dosbox.is_file() and all(path.is_file() for path in watcom_required)
|
||||
if not tools_ready and not accept_watcom_license:
|
||||
raise DosboxError(
|
||||
"Open Watcom is distributed under the Sybase Open Watcom Public License. "
|
||||
"Review tools/toolchains/dosboxx.lock.json and rerun "
|
||||
"`uv run ferro-test setup --accept-watcom-license`."
|
||||
"`uv run ferro-dos setup --accept-watcom-license`."
|
||||
)
|
||||
dosbox_spec = lock["dosboxx"]
|
||||
watcom_spec = lock["open_watcom"]
|
||||
assert isinstance(dosbox_spec, dict) and isinstance(watcom_spec, dict)
|
||||
_safe_extract(_download("DOSBox-X", dosbox_spec), CACHE / "dosbox-x")
|
||||
_safe_extract(_download("Open Watcom", watcom_spec), watcom)
|
||||
if not tools_ready:
|
||||
dosbox_spec = lock["dosboxx"]
|
||||
watcom_spec = lock["open_watcom"]
|
||||
assert isinstance(dosbox_spec, dict) and isinstance(watcom_spec, dict)
|
||||
_safe_extract(_download("DOSBox-X", dosbox_spec), CACHE / "dosbox-x")
|
||||
_safe_extract(_download("Open Watcom", watcom_spec), watcom)
|
||||
dosbox, watcom, watcom_required = _required_paths(lock)
|
||||
missing = [str(path.relative_to(CACHE)) for path in [dosbox, *watcom_required]
|
||||
if not path.is_file()]
|
||||
if 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
|
||||
|
||||
|
||||
@@ -125,7 +122,7 @@ def resolve_tools() -> tuple[Path, Path]:
|
||||
lock = _lock()
|
||||
dosbox, watcom, required = _required_paths(lock)
|
||||
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
|
||||
|
||||
|
||||
@@ -165,7 +162,8 @@ def _batch(cases: list[Case], *, show_dos: bool, trace_dos: bool) -> str:
|
||||
])
|
||||
lines.extend([
|
||||
"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)
|
||||
|
||||
@@ -236,7 +234,8 @@ def run_suite(cases: list[Case], *, keep: bool = False, show_dos: bool = False,
|
||||
command.append("-silent")
|
||||
command.extend([
|
||||
"-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",
|
||||
])
|
||||
completed = subprocess.run(command, check=False, timeout=300)
|
||||
|
||||
@@ -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
@@ -2,75 +2,54 @@
|
||||
|
||||
## Host support
|
||||
|
||||
Automation currently supports **Windows 10/11 only**. The fast development loop
|
||||
requires only `uv`; it downloads pinned DOSBox-X and Open Watcom DOS releases.
|
||||
The final milestone gate additionally requires QEMU with WHPX support and
|
||||
`ffmpeg.exe` on `PATH`. Other hosts are not supported yet.
|
||||
|
||||
## Getting started
|
||||
The automated DOS development environment currently supports Windows 10/11.
|
||||
The host only needs `uv`. The setup command downloads the pinned DOSBox-X and
|
||||
Open Watcom DOS archives, verifies their SHA-256 hashes, and installs them in the
|
||||
ignored `.dosboxx/` cache.
|
||||
|
||||
```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
|
||||
uv run ferro-test setup --accept-watcom-license
|
||||
uv run ferro-test run --through m6 -v
|
||||
```
|
||||
|
||||
`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 run --only m6 --dos-log
|
||||
uv run ferro-test --help
|
||||
```
|
||||
|
||||
Working rules, verification gates, and DOS build traps are in `AGENTS.md`.
|
||||
|
||||
## How it fits together
|
||||
|
||||
`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
|
||||
```
|
||||
`--keep-failed` preserves a failed workspace, `--dos-log` prints captured DOS
|
||||
output, `--trace-dos` disables per-command redirection, and `--show-dos` displays
|
||||
the GUI. Working rules and DOS/Open Watcom build traps are in `AGENTS.md`.
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -1,11 +0,0 @@
|
||||
-0
|
||||
-mc
|
||||
-DCFG_H="tcpagent.cfg"
|
||||
-oh
|
||||
-os
|
||||
-s
|
||||
-zp2
|
||||
-zpw
|
||||
-we
|
||||
-i=..\..\TCPINC
|
||||
-i=..\..\INCLUDE
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -2,152 +2,6 @@ version = 1
|
||||
revision = 1
|
||||
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]]
|
||||
name = "colorama"
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "ferrolang"
|
||||
version = "0.1.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "onnxruntime" },
|
||||
{ name = "pytest" },
|
||||
{ name = "rapidocr" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ 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 },
|
||||
]
|
||||
requires-dist = [{ name = "pytest", specifier = ">=9.0.0" }]
|
||||
|
||||
[[package]]
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "packaging"
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pluggy"
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pygments"
|
||||
version = "2.20.0"
|
||||
@@ -507,166 +73,3 @@ sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa
|
||||
wheels = [
|
||||
{ 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 },
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user