Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e0cf05dea0 | ||
|
|
a3350b1a27 | ||
|
|
0c52a51a15 | ||
|
|
96a13fd29a | ||
|
|
6ae689d945 | ||
|
|
7ac5b57496 | ||
|
|
4e0f04e10a | ||
|
|
cc98287944 | ||
|
|
fa82ed95bb | ||
|
|
1878584aa0 | ||
|
|
8048393440 | ||
|
|
750fb36643 | ||
|
|
879c1166b4 | ||
|
|
aa1b218d4a | ||
|
|
96fc060aeb | ||
|
|
e2fca92d4b | ||
|
|
08e3bdc849 | ||
|
|
a48631159f | ||
|
|
1f4b77ffc7 | ||
|
|
66fbe5852b | ||
|
|
0eb219965a | ||
|
|
13f14c847a | ||
|
|
40c7724bcb | ||
|
|
a316a8ac2e | ||
|
|
9e741b6f8f | ||
|
|
988cdc0306 | ||
|
|
c872ca623a | ||
|
|
d82f82bd5d | ||
|
|
20cfb855c8 | ||
|
|
662d84da27 | ||
|
|
81c92e3a33 | ||
|
|
37103bf869 | ||
|
|
7c4459dc5a | ||
|
|
1f3e9ef76e | ||
|
|
5ecb10411f | ||
|
|
2cb2b9eaa7 | ||
|
|
e5ad0336f3 | ||
|
|
6442ba91f3 | ||
|
|
e5f6fd2bc4 | ||
|
|
ff6c321f0d | ||
|
|
1fc8a85c64 | ||
|
|
0aae913613 | ||
|
|
4987bcd468 | ||
|
|
fd11edd877 | ||
|
|
aca7b5f42c | ||
|
|
e454f79e18 | ||
|
|
bcfb299bfb | ||
|
|
171c00c8ce |
No files matched your search
@@ -1 +1,2 @@
|
||||
custom: https://xi-xu.me/#sponsorships
|
||||
buy_me_a_coffee: xixu
|
||||
+21
-17
@@ -1,5 +1,8 @@
|
||||
name: CI/CD
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
@@ -31,10 +34,10 @@ jobs:
|
||||
python-version: ["3.12", "3.13"]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -68,10 +71,10 @@ jobs:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -87,7 +90,7 @@ jobs:
|
||||
run: twine check dist/*
|
||||
|
||||
- name: Upload build artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
@@ -104,7 +107,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist
|
||||
path: dist/
|
||||
@@ -141,21 +144,21 @@ jobs:
|
||||
python-version: "3.12"
|
||||
cross_compile: true
|
||||
# macOS architectures
|
||||
- os: macos-13 # Intel-based runner
|
||||
- os: macos-15-intel # Intel-based runner
|
||||
os_name: darwin
|
||||
arch: amd64
|
||||
python-version: "3.12"
|
||||
- os: macos-14 # ARM-based runner (M1/M2)
|
||||
- os: macos-15 # ARM-based runner (M1/M2)
|
||||
os_name: darwin
|
||||
arch: arm64
|
||||
python-version: "3.12"
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@@ -194,7 +197,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Build binary for ARM64 on macOS
|
||||
if: matrix.os == 'macos-14' && matrix.arch == 'arm64'
|
||||
if: matrix.os_name == 'darwin' && matrix.arch == 'arm64'
|
||||
run: |
|
||||
python -m PyInstaller --onefile --name tzst --console --target-arch arm64 --icon docs/_static/favicon.ico src/main.py
|
||||
|
||||
@@ -245,7 +248,7 @@ jobs:
|
||||
Compress-Archive -Path * -DestinationPath ../tzst-${{ steps.version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}.zip
|
||||
|
||||
- name: Upload binary artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: binary-${{ matrix.os_name }}-${{ matrix.arch }}
|
||||
path: tzst-${{ steps.version.outputs.version }}-${{ matrix.os_name }}-${{ matrix.arch }}.zip
|
||||
@@ -258,7 +261,7 @@ jobs:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Extract version from tag
|
||||
id: version
|
||||
@@ -267,7 +270,7 @@ jobs:
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Download all binary artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
pattern: binary-*
|
||||
merge-multiple: true
|
||||
@@ -311,12 +314,13 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: main
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -351,7 +355,7 @@ jobs:
|
||||
git config --local user.name "GitHub Action"
|
||||
|
||||
- name: Commit and push changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
uses: stefanzweifel/git-auto-commit-action@v7
|
||||
with:
|
||||
commit_message: "Update badges [skip ci]"
|
||||
file_pattern: README.md
|
||||
|
||||
@@ -26,17 +26,17 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Cache pip dependencies
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-docs-${{ hashFiles('docs/requirements.txt') }}
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
python -m sphinx -b html . _build -W --keep-going
|
||||
|
||||
- name: Upload documentation artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: documentation
|
||||
path: docs/_build/
|
||||
@@ -80,10 +80,10 @@ jobs:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
tzst is a next-generation Python library for modern archive management using Zstandard compression. It provides both a Python API and a command-line interface for creating, extracting, listing, and testing .tzst/.tar.zst archives. The library focuses on performance, security, and reliability with features like atomic operations, streaming mode for large archives, and security filtering for extraction.
|
||||
|
||||
## Architecture
|
||||
|
||||
The codebase is structured as follows:
|
||||
|
||||
- `src/tzst/core.py` - Core implementation containing the `TzstArchive` class and convenience functions
|
||||
- `src/tzst/cli.py` - Command-line interface built with argparse
|
||||
- `src/tzst/exceptions.py` - Custom exception classes
|
||||
- `src/tzst/__init__.py` - Public API exports
|
||||
|
||||
The library wraps Python's tarfile module with Zstandard compression/decompression streams. It supports both buffered and streaming modes for memory efficiency with large archives.
|
||||
|
||||
## Commands
|
||||
|
||||
### Development Environment
|
||||
|
||||
```bash
|
||||
# Development installation with dev dependencies
|
||||
pip install -e .[dev]
|
||||
|
||||
# Run tests
|
||||
pytest
|
||||
pytest --cov=tzst --cov-report=html # with coverage
|
||||
```
|
||||
|
||||
### Code Quality
|
||||
|
||||
```bash
|
||||
# Check code quality
|
||||
ruff check src tests
|
||||
|
||||
# Format code
|
||||
ruff format src tests
|
||||
```
|
||||
|
||||
### Building and Distribution
|
||||
|
||||
```bash
|
||||
# Build package
|
||||
python -m build
|
||||
|
||||
# Upload to PyPI (requires credentials)
|
||||
python -m twine upload dist/*
|
||||
```
|
||||
|
||||
## Key Features to Consider
|
||||
|
||||
1. **Streaming Mode**: For archives >100MB, always recommend using streaming mode to reduce memory usage
|
||||
2. **Security Filters**: Use 'data' filter by default for extraction to prevent path traversal attacks
|
||||
3. **Atomic Operations**: Archive creation uses temporary files by default for safety
|
||||
4. **Conflict Resolution**: During extraction, implement proper file conflict resolution strategies
|
||||
5. **Compression Levels**: Default to level 3 unless the user has specific performance/size requirements
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
The project uses pytest with comprehensive test coverage including:
|
||||
|
||||
- Unit tests in `tests/unit/`
|
||||
- Integration tests in `tests/integration/`
|
||||
- CLI-specific tests in `tests/cli/`
|
||||
- Edge case handling in `tests/test_core_edge_cases.py`
|
||||
|
||||
## Common Tasks
|
||||
|
||||
- To add functionality: work in `src/tzst/core.py` following existing patterns
|
||||
- To modify CLI behavior: update `src/tzst/cli.py` and adjust argument parsing as needed
|
||||
- To add new command: extend `create_parser()` in cli.py and add appropriate handler function
|
||||
- For error handling: use appropriate TzstError subclasses from exceptions.py
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
i@xi-xu.me.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -17,6 +17,8 @@
|
||||
|
||||
**tzst** هي مكتبة Python من الجيل التالي مُطورة لإدارة الأرشيف الحديث، تستفيد من ضغط Zstandard المتطور لتقديم أداء وأمان وموثوقية فائقة. مبنية حصرياً لـ Python 3.12+، هذا الحل على مستوى المؤسسة يدمج العمليات الذرية وكفاءة التدفق ووواجهة برمجة التطبيقات المصممة بعناية فائقة لإعادة تعريف كيفية تعامل المطورين مع أرشيف `.tzst`/`.tar.zst` في بيئات الإنتاج. 🚀
|
||||
|
||||
تم نشر مقال التحليل الفني المتعمق: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ الميزات
|
||||
|
||||
- **🗜️ ضغط عالي**: ضغط Zstandard لنسب ضغط وسرعة ممتازة
|
||||
@@ -65,10 +67,18 @@
|
||||
|
||||
### 📦 من PyPI
|
||||
|
||||
استخدام pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
أو استخدام uv (موصى به):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 من المصدر
|
||||
|
||||
```bash
|
||||
@@ -91,8 +101,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 استخدام سطر الأوامر
|
||||
|
||||
> **ملاحظة**: تحميل [الملف الثنائي المستقل](#من-إصدارات-github) للحصول على أفضل أداء وعدم الاعتماد على Python. بدلاً من ذلك، استخدم `uvx tzst` للتشغيل دون تثبيت. راجع [وثائق uv](https://docs.astral.sh/uv/) للتفاصيل.
|
||||
|
||||
```bash
|
||||
# 📁 إنشاء أرشيف
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -514,7 +522,7 @@ python -m pytest tests/
|
||||
|
||||
## 📄 الترخيص
|
||||
|
||||
حقوق النشر © 2025 [شي شو](https://xi-xu.me). جميع الحقوق محفوظة.
|
||||
حقوق النشر © [شي شو](https://xi-xu.me). جميع الحقوق محفوظة.
|
||||
|
||||
مرخص تحت ترخيص [BSD 3-Clause](LICENSE).
|
||||
|
||||
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** ist eine Python-Bibliothek der nächsten Generation, die für modernes Archivmanagement entwickelt wurde und hochmoderne Zstandard-Komprimierung nutzt, um überlegene Leistung, Sicherheit und Zuverlässigkeit zu bieten. Ausschließlich für Python 3.12+ entwickelt, kombiniert diese Unternehmenslösung atomare Operationen, Streaming-Effizienz und eine sorgfältig erstellte API, um die Art und Weise neu zu definieren, wie Entwickler mit `.tzst`/`.tar.zst`-Archiven in Produktionsumgebungen umgehen. 🚀
|
||||
|
||||
Veröffentlichter ausführlicher technischer Analyseartikel: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Funktionen
|
||||
|
||||
- **🗜️ Hohe Komprimierung**: Zstandard-Komprimierung für ausgezeichnete Komprimierungsraten und Geschwindigkeit
|
||||
@@ -63,10 +65,18 @@ Lade eigenständige ausführbare Dateien herunter, die keine Python-Installation
|
||||
|
||||
### 📦 Von PyPI
|
||||
|
||||
Mit pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
Oder mit uv (empfohlen):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 Aus dem Quellcode
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Kommandozeilennutzung
|
||||
|
||||
> **Hinweis**: Lade die [eigenständige Binärdatei](#von-github-releases) für beste Leistung und keine Python-Abhängigkeit herunter. Alternativ verwende `uvx tzst` für die Ausführung ohne Installation. Siehe [uv-Dokumentation](https://docs.astral.sh/uv/) für Details.
|
||||
|
||||
```bash
|
||||
# 📁 Archiv erstellen
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 Lizenz
|
||||
|
||||
Urheberrecht © 2025 [Xi Xu](https://xi-xu.me). Alle Rechte vorbehalten.
|
||||
Urheberrecht © [Xi Xu](https://xi-xu.me). Alle Rechte vorbehalten.
|
||||
|
||||
Lizenziert unter der [BSD 3-Clause](LICENSE) Lizenz.
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** es una biblioteca de Python de próxima generación diseñada para la gestión moderna de archivos, aprovechando la compresión Zstandard de vanguardia para ofrecer un rendimiento, seguridad y fiabilidad superiores. Construida exclusivamente para Python 3.12+, esta solución de nivel empresarial combina operaciones atómicas, eficiencia de transmisión (streaming) y una API meticulosamente elaborada para redefinir cómo los desarrolladores manejan los archivos `.tzst`/`.tar.zst` en entornos de producción. 🚀
|
||||
|
||||
Artículo de análisis técnico en profundidad publicado: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Características
|
||||
|
||||
- **🗜️ Alta Compresión**: Compresión Zstandard para excelentes ratios de compresión y velocidad.
|
||||
@@ -63,10 +65,18 @@ Descarga ejecutables independientes que no requieren instalación de Python:
|
||||
|
||||
### 📦 Desde PyPI
|
||||
|
||||
Usando pip:
|
||||
|
||||
```
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
O usando uv (recomendado):
|
||||
|
||||
```
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 Desde el Código Fuente
|
||||
|
||||
```
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Uso desde la Línea de Comandos
|
||||
|
||||
> **Nota**: Descarga el [binario independiente](#desde-los-lanzamientos-de-github) para obtener el mejor rendimiento y no depender de Python. Alternativamente, usa `uvx tzst` para ejecutar sin instalación. Consulta la [documentación de uv](https://docs.astral.sh/uv/) para más detalles.
|
||||
|
||||
```
|
||||
# 📁 Crear un archivo
|
||||
tzst a archivo.tzst archivo1.txt archivo2.txt directorio/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 Licencia
|
||||
|
||||
Copyright © 2025 [Xi Xu](https://xi-xu.me). Todos los derechos reservados.
|
||||
Copyright © [Xi Xu](https://xi-xu.me). Todos los derechos reservados.
|
||||
|
||||
Licenciado bajo la licencia [BSD 3-Clause](LICENSE).
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** est une bibliothèque Python de nouvelle génération conçue pour la gestion moderne d'archives, exploitant la compression Zstandard de pointe pour offrir des performances, une sécurité et une fiabilité supérieures. Construite exclusivement pour Python 3.12+, cette solution de niveau entreprise combine des opérations atomiques, l'efficacité du streaming et une API méticuleusement conçue pour redéfinir la façon dont les développeurs gèrent les archives `.tzst`/`.tar.zst` dans les environnements de production. 🚀
|
||||
|
||||
Article d'analyse technique approfondie publié : **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Fonctionnalités
|
||||
|
||||
- **🗜️ Compression élevée** : Compression Zstandard pour d'excellents taux de compression et une vitesse remarquable
|
||||
@@ -63,10 +65,18 @@ Téléchargez des exécutables autonomes qui ne nécessitent pas d'installation
|
||||
|
||||
### 📦 Depuis PyPI
|
||||
|
||||
Avec pip :
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
Ou avec uv (recommandé) :
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 Depuis le code source
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Utilisation en ligne de commande
|
||||
|
||||
> **Note** : Téléchargez le [binaire autonome](#depuis-les-releases-github) pour les meilleures performances et aucune dépendance Python. Alternativement, utilisez `uvx tzst` pour exécuter sans installation. Voir la [documentation uv](https://docs.astral.sh/uv/) pour les détails.
|
||||
|
||||
```bash
|
||||
# 📁 Créer une archive
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 Licence
|
||||
|
||||
Droits d'auteur © 2025 [Xi Xu](https://xi-xu.me). Tous droits réservés.
|
||||
Droits d'auteur © [Xi Xu](https://xi-xu.me). Tous droits réservés.
|
||||
|
||||
Sous licence [BSD 3-Clause](LICENSE).
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** は、最新の Zstandard 圧縮技術を活用した次世代 Python ライブラリで、優れたパフォーマンス、セキュリティ、信頼性を提供するモダンなアーカイブ管理を実現します。 Python 3.12+ 専用に構築されたこのエンタープライズグレードのソリューションは、アトミック操作、ストリーミング効率、厳密に設計された API を組み合わせ、本番環境における `.tzst` / `.tar.zst` アーカイブの扱い方を再定義します。 🚀
|
||||
|
||||
技術詳細分析記事が公開されました: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**。
|
||||
|
||||
## ✨ 特徴
|
||||
|
||||
- **🗜️ 高圧縮率**: Zstandard 圧縮による優れた圧縮率と速度
|
||||
@@ -63,10 +65,18 @@ Python インストール不要のスタンドアロン実行ファイルをダ
|
||||
|
||||
### 📦 PyPI から
|
||||
|
||||
pip を使用:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
または uv を使用(推奨):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 ソースから
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 コマンドラインの使い方
|
||||
|
||||
> **注**: 最高のパフォーマンスと Python 依存なしを実現するには[スタンドアロンバイナリ](#github-リリースから)をダウンロードしてください。または、インストールなしで実行するには `uvx tzst` を使用します。詳細は [uv ドキュメント](https://docs.astral.sh/uv/)を参照。
|
||||
|
||||
```bash
|
||||
# 📁 アーカイブ作成
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 ライセンス
|
||||
|
||||
著作権 © 2025 [Xi Xu](https://xi-xu.me)。全著作権を保留します。
|
||||
著作権 © [Xi Xu](https://xi-xu.me)。全著作権を保留します。
|
||||
|
||||
[BSD 3-Clause](LICENSE) ライセンスのもとで公開されています。
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst**는 최신 Zstandard 압축 기술을 활용하여 우수한 성능, 보안 및 신뢰성을 제공하는 차세대 Python 라이브러리입니다. Python 3.12+ 전용으로 제작된 이 엔터프라이즈급 솔루션은 원자적 작업, 스트리밍 효율성 및 정교하게 설계된 API를 결합하여 `.tzst`/`.tar.zst` 아카이브를 프로덕션 환경에서 처리하는 방식을 재정의합니다. 🚀
|
||||
|
||||
심층 기술 분석 기사가 게시되었습니다: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ 기능
|
||||
|
||||
- **🗜️ 고압축률**: 우수한 압축률과 속도를 위한 Zstandard 압축
|
||||
@@ -63,10 +65,18 @@ Python 설치가 필요 없는 독립형 실행 파일 다운로드:
|
||||
|
||||
### 📦 PyPI에서
|
||||
|
||||
pip 사용:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
또는 uv 사용 (권장):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 소스에서
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 명령줄 사용법
|
||||
|
||||
> **참고**: 최상의 성능과 Python 의존성 없이 사용하려면 [독립형 바이너리](#github-릴리스에서)를 다운로드하세요. 또는 설치 없이 실행하려면 `uvx tzst`를 사용하세요. 자세한 내용은 [uv 문서](https://docs.astral.sh/uv/) 참조.
|
||||
|
||||
```bash
|
||||
# 📁 아카이브 생성
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 라이선스
|
||||
|
||||
저작권 © 2025 [시 쉬](https://xi-xu.me). 모든 권리 보유.
|
||||
저작권 © [시 쉬](https://xi-xu.me). 모든 권리 보유.
|
||||
|
||||
[BSD 3-Clause](LICENSE) 라이선스로 사용이 허가되었습니다.
|
||||
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** is a next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability. Built exclusively for Python 3.12+, this enterprise-grade solution combines atomic operations, streaming efficiency, and a meticulously crafted API to redefine how developers handle `.tzst`/`.tar.zst` archives in production environments. 🚀
|
||||
|
||||
In-depth technical analysis article published: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- **🗜️ High Compression**: Zstandard compression for excellent compression ratios and speed
|
||||
@@ -63,10 +65,18 @@ Download standalone executables that don't require Python installation:
|
||||
|
||||
### 📦 From PyPI
|
||||
|
||||
Using pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
Or using uv (recommended):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 From Source
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Command Line Usage
|
||||
|
||||
> **Note**: Download the [standalone binary](#from-github-releases) for the best performance and no Python dependency. Alternatively, use `uvx tzst` for running without installation. See [uv documentation](https://docs.astral.sh/uv/) for details.
|
||||
|
||||
```bash
|
||||
# 📁 Create an archive
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 License
|
||||
|
||||
Copyright © 2025 [Xi Xu](https://xi-xu.me). All rights reserved.
|
||||
Copyright © [Xi Xu](https://xi-xu.me). All rights reserved.
|
||||
|
||||
Licensed under the [BSD 3-Clause](LICENSE) license.
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** é uma biblioteca Python de próxima geração projetada para gerenciamento moderno de arquivos, aproveitando a compressão Zstandard de ponta para oferecer desempenho, segurança e confiabilidade superiores. Construída exclusivamente para Python 3.12+, esta solução corporativa combina operações atômicas, eficiência de streaming e uma API meticulosamente elaborada para redefinir como os desenvolvedores lidam com arquivos `.tzst`/`.tar.zst` em ambientes de produção. 🚀
|
||||
|
||||
Artigo de análise técnica aprofundada publicado: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Recursos
|
||||
|
||||
- **🗜️ Alta Compressão**: Compressão Zstandard para excelentes taxas de compressão e velocidade
|
||||
@@ -63,10 +65,18 @@ Baixe executáveis independentes que não requerem instalação do Python:
|
||||
|
||||
### 📦 Do PyPI
|
||||
|
||||
Usando pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
Ou usando uv (recomendado):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 Do Código Fonte
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Uso da Linha de Comando
|
||||
|
||||
> **Nota**: Baixe o [binário independente](#dos-releases-do-github) para melhor desempenho e sem dependência do Python. Alternativamente, use `uvx tzst` para executar sem instalação. Veja a [documentação do uv](https://docs.astral.sh/uv/) para detalhes.
|
||||
|
||||
```bash
|
||||
# 📁 Criar um arquivo
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 Licença
|
||||
|
||||
Direitos autorais © 2025 [Xi Xu](https://xi-xu.me). Todos os direitos reservados.
|
||||
Direitos autorais © [Xi Xu](https://xi-xu.me). Todos os direitos reservados.
|
||||
|
||||
Licenciado sob a licença [BSD 3-Clause](LICENSE).
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** — это библиотека Python нового поколения, разработанная для современного управления архивами, использующая передовое сжатие Zstandard для обеспечения превосходной производительности, безопасности и надёжности. Созданная исключительно для Python 3.12+, это корпоративное решение объединяет атомарные операции, эффективность потоковой передачи и тщательно разработанный API для переосмысления того, как разработчики работают с архивами `.tzst`/`.tar.zst` в производственных средах. 🚀
|
||||
|
||||
Опубликована статья с углублённым техническим анализом: **[Deep Dive into tzst: A Modern Python Archiving Library Based on Zstandard](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst-en.html)**.
|
||||
|
||||
## ✨ Особенности
|
||||
|
||||
- **🗜️ Высокое сжатие**: Сжатие Zstandard для отличных коэффициентов сжатия и скорости
|
||||
@@ -63,10 +65,18 @@
|
||||
|
||||
### 📦 Из PyPI
|
||||
|
||||
Используя pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
Или используя uv (рекомендуется):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 Из исходного кода
|
||||
|
||||
```bash
|
||||
@@ -89,8 +99,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 Использование командной строки
|
||||
|
||||
> **Примечание**: Скачайте [автономный бинарный файл](#из-релизов-github) для лучшей производительности и отсутствия зависимости от Python. Альтернативно, используйте `uvx tzst` для запуска без установки. Смотрите [документацию uv](https://docs.astral.sh/uv/) для деталей.
|
||||
|
||||
```bash
|
||||
# 📁 Создать архив
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -512,6 +520,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Авторские права © 2025 [Си Сюй](https://xi-xu.me). Все права защищены.
|
||||
Авторские права © [Си Сюй](https://xi-xu.me). Все права защищены.
|
||||
|
||||
Лицензировано под лицензией [BSD 3-Clause](LICENSE).
|
||||
+12
-4
@@ -6,7 +6,7 @@
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
[](https://tzst.xi-xu.me)
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
**tzst** 是一个面向现代归档管理的新一代 Python 库,利用前沿的 Zstandard 压缩技术,提供卓越的性能、安全性和可靠性。专为 Python 3.12+ 打造,这个企业级解决方案结合原子操作、流式处理效率和精心设计的 API,重新定义了开发者在生产环境中处理 `.tzst`/`.tar.zst` 归档文件的方式。🚀
|
||||
|
||||
技术深度解析文章已发布:**[《深入解析 tzst:一个基于 Zstandard 的现代 Python 归档库》](https://blog.xi-xu.me/2025/11/01/deep-dive-into-tzst.html)**。
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
- **🗜️ 高效压缩**:采用 Zstandard 压缩算法,实现优异的压缩率和速度
|
||||
@@ -63,10 +65,18 @@
|
||||
|
||||
### 📦 通过 PyPI 安装
|
||||
|
||||
使用 pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
或使用 uv(推荐):
|
||||
|
||||
```bash
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
### 🔧 从源码安装
|
||||
|
||||
```bash
|
||||
@@ -87,8 +97,6 @@ pip install -e .[dev]
|
||||
|
||||
### 💻 命令行使用
|
||||
|
||||
> **注意**:下载[独立二进制文件](#从-github-releases-安装)可获得最佳性能且无需 Python 环境。也可使用 `uvx tzst` 免安装运行,详见 [uv 文档](https://docs.astral.sh/uv/)。
|
||||
|
||||
```bash
|
||||
# 📁 创建归档
|
||||
tzst a archive.tzst file1.txt file2.txt directory/
|
||||
@@ -508,6 +516,6 @@ python -m pytest tests/
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
版权所有 © 2025 [Xi Xu](https://xi-xu.me)。保留所有权利。
|
||||
版权所有 © [Xi Xu](https://xi-xu.me)。保留所有权利。
|
||||
|
||||
采用 [BSD 3-Clause](LICENSE) 许可证授权。
|
||||
+313
@@ -0,0 +1,313 @@
|
||||
# Security Policy
|
||||
|
||||
## Overview
|
||||
|
||||
The tzst project takes security seriously and is committed to providing a secure archive management library. This document outlines our security policies, supported versions, vulnerability reporting procedures, and security best practices.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
|---------|-----------|
|
||||
| 1.x.x | ✅ Yes |
|
||||
| < 1.0 | ❌ No |
|
||||
|
||||
We provide security updates for the latest major version. Users are strongly encouraged to keep their installations up to date.
|
||||
|
||||
## Security Features
|
||||
|
||||
### Built-in Security by Default
|
||||
|
||||
tzst is designed with security as a primary concern and implements multiple layers of protection:
|
||||
|
||||
#### 🔒 Secure Extraction Filters
|
||||
|
||||
tzst provides three security filter levels for extraction operations:
|
||||
|
||||
- **`data` (default)**: Maximum security level
|
||||
- Blocks dangerous files (device files, named pipes, etc.)
|
||||
- Prevents absolute path extraction
|
||||
- Blocks directory traversal attacks (`../` sequences)
|
||||
- Restricts extraction to the specified directory
|
||||
- **Recommended for untrusted archives**
|
||||
|
||||
- **`tar`**: Standard tar compatibility
|
||||
- Blocks absolute paths
|
||||
- Prevents directory traversal
|
||||
- Allows Unix-specific features (symlinks, permissions)
|
||||
- **Use for trusted archives requiring tar features**
|
||||
|
||||
- **`fully_trusted`**: No security restrictions
|
||||
- Allows all archive features
|
||||
- **Only use with completely trusted archives**
|
||||
- ⚠️ **Warning**: Can be dangerous with untrusted content
|
||||
|
||||
#### ⚡ Atomic Operations
|
||||
|
||||
All file creation operations use atomic file operations by default:
|
||||
|
||||
- Archives are created in temporary files first, then atomically moved
|
||||
- Automatic cleanup if the process is interrupted
|
||||
- Prevents corrupted or incomplete archives
|
||||
- Cross-platform compatibility
|
||||
- Use `use_temp_file=False` only when necessary (not recommended)
|
||||
|
||||
#### 🛡️ Path Traversal Protection
|
||||
|
||||
- Validates all file paths before extraction
|
||||
- Normalizes paths to prevent directory traversal
|
||||
- Blocks extraction outside target directory
|
||||
- Handles edge cases across different operating systems
|
||||
|
||||
#### 🔍 Input Validation
|
||||
|
||||
- Validates compression levels (1-22)
|
||||
- Validates archive formats
|
||||
- Validates file paths and names
|
||||
- Comprehensive error handling with clear messages
|
||||
|
||||
## Best Practices for Users
|
||||
|
||||
### 1. Always Use Secure Defaults
|
||||
|
||||
```python
|
||||
from tzst import extract_archive
|
||||
|
||||
# ✅ Good: Uses secure 'data' filter by default
|
||||
extract_archive("untrusted.tzst", "output/")
|
||||
|
||||
# ✅ Good: Explicitly specify secure filter
|
||||
extract_archive("untrusted.tzst", "output/", filter="data")
|
||||
```
|
||||
|
||||
### 2. Choose Appropriate Security Filters
|
||||
|
||||
```python
|
||||
# For untrusted archives (recommended)
|
||||
extract_archive("untrusted.tzst", "output/", filter="data")
|
||||
|
||||
# For trusted archives needing tar features
|
||||
extract_archive("trusted.tzst", "output/", filter="tar")
|
||||
|
||||
# Only for completely trusted archives (use with caution)
|
||||
extract_archive("internal.tzst", "output/", filter="fully_trusted")
|
||||
```
|
||||
|
||||
### 3. Validate Archive Sources
|
||||
|
||||
- Only process archives from trusted sources
|
||||
- Verify archive integrity before extraction
|
||||
- Use appropriate security filters based on trust level
|
||||
- Consider implementing additional validation layers
|
||||
|
||||
### 4. Use Safe Extraction Practices
|
||||
|
||||
```python
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from tzst import extract_archive, test_archive
|
||||
|
||||
def safe_extract(archive_path, trust_level="untrusted"):
|
||||
"""Safely extract an archive with appropriate security measures."""
|
||||
|
||||
# Test archive integrity first
|
||||
if not test_archive(archive_path):
|
||||
raise ValueError("Archive integrity check failed")
|
||||
|
||||
# Choose security filter based on trust level
|
||||
filters = {
|
||||
"untrusted": "data",
|
||||
"trusted": "tar",
|
||||
"internal": "fully_trusted"
|
||||
}
|
||||
|
||||
security_filter = filters.get(trust_level, "data")
|
||||
|
||||
# Extract to temporary directory first
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
extract_archive(
|
||||
archive_path,
|
||||
temp_dir,
|
||||
filter=security_filter
|
||||
)
|
||||
# Process extracted files safely
|
||||
# Move to final destination if validation passes
|
||||
```
|
||||
|
||||
### 5. Error Handling
|
||||
|
||||
```python
|
||||
from tzst import TzstArchiveError, TzstDecompressionError
|
||||
|
||||
try:
|
||||
extract_archive("archive.tzst", "output/")
|
||||
except TzstDecompressionError:
|
||||
# Handle corrupted or invalid archives
|
||||
print("Archive appears to be corrupted")
|
||||
except TzstArchiveError as e:
|
||||
# Handle general archive errors
|
||||
print(f"Archive operation failed: {e}")
|
||||
except PermissionError:
|
||||
# Handle permission issues
|
||||
print("Insufficient permissions")
|
||||
```
|
||||
|
||||
## Security Considerations for Different Use Cases
|
||||
|
||||
### Processing Untrusted Archives
|
||||
|
||||
When processing archives from untrusted sources (internet downloads, user uploads, etc.):
|
||||
|
||||
1. **Always use `data` filter** (default behavior)
|
||||
2. **Extract to isolated directory** with limited permissions
|
||||
3. **Validate extracted content** before use
|
||||
4. **Use resource limits** to prevent DoS attacks
|
||||
5. **Run in sandboxed environment** when possible
|
||||
|
||||
### Enterprise/Internal Use
|
||||
|
||||
For trusted internal archives:
|
||||
|
||||
1. Use `tar` filter for standard compatibility
|
||||
2. Implement organizational security policies
|
||||
3. Use secure transport channels
|
||||
4. Maintain audit logs of archive operations
|
||||
5. Regular security assessments
|
||||
|
||||
### Development/Testing
|
||||
|
||||
Even in development:
|
||||
|
||||
1. Use secure defaults
|
||||
2. Don't disable security features without understanding implications
|
||||
3. Test with malicious archives (in isolated environments)
|
||||
4. Validate security assumptions
|
||||
|
||||
## Reporting Security Vulnerabilities
|
||||
|
||||
We take security vulnerabilities seriously and appreciate responsible disclosure.
|
||||
|
||||
### How to Report
|
||||
|
||||
**DO NOT** report security vulnerabilities through public GitHub issues.
|
||||
|
||||
Instead, please report security vulnerabilities via email to:
|
||||
|
||||
📧 **[i@xi-xu.me](mailto:i@xi-xu.me)**
|
||||
|
||||
### Information to Include
|
||||
|
||||
Please include as much of the following information as possible:
|
||||
|
||||
1. **Description** of the vulnerability
|
||||
2. **Steps to reproduce** the issue
|
||||
3. **Potential impact** and attack scenarios
|
||||
4. **Affected versions** (if known)
|
||||
5. **Suggested fix** (if you have one)
|
||||
6. **Your contact information** for follow-up
|
||||
|
||||
### What to Expect
|
||||
|
||||
1. **Acknowledgment**: We'll acknowledge receipt within 48 hours
|
||||
2. **Initial Assessment**: We'll provide an initial assessment within 5 business days
|
||||
3. **Communication**: We'll keep you informed throughout the investigation
|
||||
4. **Resolution**: We'll work to resolve confirmed vulnerabilities promptly
|
||||
5. **Credit**: We'll credit you in security advisories (unless you prefer anonymity)
|
||||
|
||||
### Response Timeline
|
||||
|
||||
- **Critical vulnerabilities**: Patch within 7 days
|
||||
- **High-severity vulnerabilities**: Patch within 14 days
|
||||
- **Medium/Low-severity vulnerabilities**: Patch within 30 days
|
||||
|
||||
## Security Updates and Advisories
|
||||
|
||||
### How We Communicate Security Issues
|
||||
|
||||
1. **GitHub Security Advisories**: For confirmed vulnerabilities
|
||||
2. **Release Notes**: Security fixes are prominently mentioned
|
||||
3. **PyPI**: Updated packages with security fixes
|
||||
4. **Documentation**: Security best practices updates
|
||||
|
||||
### Staying Informed
|
||||
|
||||
To stay informed about security updates:
|
||||
|
||||
1. **Watch the repository** for release notifications
|
||||
2. **Subscribe to GitHub Security Advisories**
|
||||
3. **Follow our release notes** for security mentions
|
||||
4. **Use dependency scanners** to identify outdated versions
|
||||
|
||||
## Development Security Practices
|
||||
|
||||
### Code Review Process
|
||||
|
||||
- All changes undergo security-focused code review
|
||||
- Security-sensitive changes require additional review
|
||||
- Automated security scanning in CI/CD pipeline
|
||||
- Regular dependency vulnerability scanning
|
||||
|
||||
### Testing
|
||||
|
||||
- Comprehensive security test suite
|
||||
- Fuzzing with malformed archives
|
||||
- Path traversal attack simulations
|
||||
- Permission and access control testing
|
||||
|
||||
### Dependencies
|
||||
|
||||
- Minimal dependency footprint
|
||||
- Regular dependency updates
|
||||
- Automated vulnerability scanning
|
||||
- Pinned versions for reproducible builds
|
||||
|
||||
## Known Security Considerations
|
||||
|
||||
### Archive Bombs
|
||||
|
||||
While tzst includes basic protections, be aware of:
|
||||
|
||||
- **Zip bombs**: Archives with extreme compression ratios
|
||||
- **Memory exhaustion**: Very large expanded archives
|
||||
- **Resource consumption**: Processing time attacks
|
||||
|
||||
**Mitigation**: Use streaming mode for large archives and implement resource limits.
|
||||
|
||||
### Symbolic Links
|
||||
|
||||
Different security filters handle symbolic links differently:
|
||||
|
||||
- `data` filter: Generally restricts symbolic links
|
||||
- `tar` filter: Preserves symbolic links with basic safety checks
|
||||
- `fully_trusted` filter: Allows all symbolic link operations
|
||||
|
||||
**Recommendation**: Use `data` filter for untrusted content.
|
||||
|
||||
### File Permissions
|
||||
|
||||
Extracted file permissions depend on:
|
||||
|
||||
- Source archive permissions
|
||||
- Extraction filter settings
|
||||
- Operating system capabilities
|
||||
- User privileges
|
||||
|
||||
**Recommendation**: Review and validate extracted file permissions.
|
||||
|
||||
## Contact
|
||||
|
||||
For security-related questions or concerns:
|
||||
|
||||
- **Security issues**: [i@xi-xu.me](mailto:i@xi-xu.me) (private)
|
||||
- **General questions**: [GitHub Discussions](https://github.com/xixu-me/tzst/discussions)
|
||||
- **Documentation**: [Project Documentation](https://tzst.xi-xu.me)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
We thank the security research community for their responsible disclosure of vulnerabilities and continuous efforts to improve software security.
|
||||
|
||||
---
|
||||
|
||||
**Last Updated**: June 2025
|
||||
**Version**: 1.0
|
||||
|
||||
For the most current security information, please check our [GitHub repository](https://github.com/xixu-me/tzst) and [official documentation](https://tzst.xi-xu.me).
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
# robots.txt for tzst documentation
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Sitemap location
|
||||
Sitemap: https://tzst.xi-xu.me/sitemap.xml
|
||||
|
||||
# Disallow build artifacts and internal directories
|
||||
Disallow: /_sources/
|
||||
Disallow: /_static/*.js$
|
||||
Disallow: /_images/
|
||||
|
||||
# Allow static assets like CSS and images
|
||||
Allow: /_static/*.css$
|
||||
Allow: /_static/*.png$
|
||||
Allow: /_static/*.jpg$
|
||||
Allow: /_static/*.ico$
|
||||
Allow: /_static/*.svg$
|
||||
|
||||
# Crawl delay (optional, considerate to search engines)
|
||||
Crawl-delay: 1
|
||||
Vendored
+161
-3
@@ -1,7 +1,11 @@
|
||||
{% extends "!layout.html" %} {% block extrahead %} {{ super() }}
|
||||
{% extends "!layout.html" %}
|
||||
{% block extrahead %}
|
||||
{{ super() }}
|
||||
<!-- Additional SEO and social meta tags -->
|
||||
<meta name="application-name" content="tzst" />
|
||||
<meta name="generator" content="Sphinx {{ sphinx_version }}" />
|
||||
<meta name="rating" content="General" />
|
||||
<meta name="revisit-after" content="7 days" />
|
||||
|
||||
<!-- Schema.org markup for search engines -->
|
||||
<script type="application/ld+json">
|
||||
@@ -13,21 +17,173 @@
|
||||
"applicationCategory": "DeveloperApplication",
|
||||
"operatingSystem": "Cross-platform",
|
||||
"programmingLanguage": "Python",
|
||||
"license": "https://opensource.org/licenses/MIT",
|
||||
"license": "https://opensource.org/licenses/BSD-3-Clause",
|
||||
"url": "https://tzst.xi-xu.me/",
|
||||
"downloadUrl": "https://pypi.org/project/tzst/",
|
||||
"codeRepository": "https://github.com/xixu-me/tzst",
|
||||
"softwareVersion": "{{ version }}",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Xi Xu"
|
||||
"name": "Xi Xu",
|
||||
"url": "https://xi-xu.me"
|
||||
},
|
||||
"offers": {
|
||||
"@type": "Offer",
|
||||
"price": "0",
|
||||
"priceCurrency": "USD"
|
||||
},
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingValue": "5",
|
||||
"reviewCount": "1"
|
||||
},
|
||||
"keywords": "tzst, tar, zstandard, compression, archive, python, extraction, backup"
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Breadcrumb Schema -->
|
||||
{% if pagename != 'index' %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
"itemListElement": [
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1,
|
||||
"name": "Home",
|
||||
"item": "https://tzst.xi-xu.me/"
|
||||
},
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 2,
|
||||
"name": "{{ title|striptags }}",
|
||||
"item": "https://tzst.xi-xu.me/{{ pagename }}.html"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Article/TechArticle Schema for documentation pages -->
|
||||
{% if pagename in ['quickstart', 'examples', 'performance', 'development'] %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "TechArticle",
|
||||
"headline": "{{ title|striptags }}",
|
||||
"description": "{{ metatags|striptags }}",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Xi Xu",
|
||||
"url": "https://xi-xu.me"
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "Person",
|
||||
"name": "Xi Xu"
|
||||
},
|
||||
"datePublished": "2025-01-01",
|
||||
"dateModified": "2025-01-12",
|
||||
"url": "https://tzst.xi-xu.me/{{ pagename }}.html",
|
||||
"inLanguage": "en-US",
|
||||
"about": {
|
||||
"@type": "SoftwareApplication",
|
||||
"name": "tzst"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<!-- FAQ Schema for pages with common questions -->
|
||||
{% if pagename == 'quickstart' %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "FAQPage",
|
||||
"mainEntity": [
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "How do I install tzst?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "You can install tzst using pip (pip install tzst), download standalone binaries from GitHub Releases, use uvx for no-installation usage (uvx tzst), or install from source."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "What compression levels does tzst support?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "tzst supports compression levels from 1 to 22. Level 1 is fastest with lower compression, level 3 is the default balance, and level 22 provides maximum compression but is slower."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "Is tzst secure for extracting untrusted archives?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Yes, tzst uses the 'data' security filter by default, which protects against path traversal attacks and blocks dangerous files. This makes it safe for extracting untrusted archives."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "When should I use streaming mode?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "Use streaming mode for archives larger than 100MB to reduce memory usage. Streaming mode is memory-efficient but has limitations such as no random access or specific file extraction."
|
||||
}
|
||||
},
|
||||
{
|
||||
"@type": "Question",
|
||||
"name": "What file extensions does tzst support?",
|
||||
"acceptedAnswer": {
|
||||
"@type": "Answer",
|
||||
"text": "tzst supports both .tzst and .tar.zst file extensions. The library automatically handles extension detection and normalization when creating or opening archives."
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<!-- HowTo Schema for examples page -->
|
||||
{% if pagename == 'examples' %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "HowTo",
|
||||
"name": "How to use tzst for archive management",
|
||||
"description": "Comprehensive examples of using tzst for creating, extracting, and managing tar.zst archives",
|
||||
"image": "https://tzst.xi-xu.me/_static/tzst-logo.png",
|
||||
"step": [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"name": "Create an archive",
|
||||
"text": "Use create_archive() to create a new tzst archive with your files and directories",
|
||||
"url": "https://tzst.xi-xu.me/examples.html#basic-operations"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"name": "Extract an archive",
|
||||
"text": "Use extract_archive() to safely extract files from a tzst archive with security filters",
|
||||
"url": "https://tzst.xi-xu.me/examples.html#flexible-extraction"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"name": "List archive contents",
|
||||
"text": "Use list_archive() to view the contents of an archive without extracting",
|
||||
"url": "https://tzst.xi-xu.me/examples.html#basic-operations"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"name": "Test archive integrity",
|
||||
"text": "Use test_archive() to verify the integrity of your archive files",
|
||||
"url": "https://tzst.xi-xu.me/examples.html#basic-operations"
|
||||
}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
<!-- Canonical URL for better SEO -->
|
||||
{% if pagename != 'index' %}
|
||||
@@ -39,4 +195,6 @@
|
||||
<!-- Preconnect to external domains for performance -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" />
|
||||
<link rel="dns-prefetch" href="https://pypi.org" />
|
||||
<link rel="dns-prefetch" href="https://github.com" />
|
||||
{% endblock %}
|
||||
+27
-1
@@ -2,6 +2,7 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
# Add the source directory to the Python path
|
||||
sys.path.insert(0, os.path.abspath("../src"))
|
||||
@@ -11,7 +12,7 @@ from tzst import __version__
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
project = "tzst"
|
||||
copyright = "2025, Xi Xu"
|
||||
copyright = f"{datetime.now().year}, Xi Xu"
|
||||
author = "Xi Xu"
|
||||
release = __version__
|
||||
version = __version__
|
||||
@@ -25,11 +26,19 @@ extensions = [
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.coverage",
|
||||
"myst_parser",
|
||||
"sphinx_sitemap",
|
||||
]
|
||||
|
||||
templates_path = ["_templates"]
|
||||
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
|
||||
|
||||
# Base URL for sitemap generation
|
||||
html_baseurl = "https://tzst.xi-xu.me/"
|
||||
|
||||
# Sitemap configuration
|
||||
sitemap_url_scheme = "{link}"
|
||||
sitemap_filename = "sitemap.xml"
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
@@ -51,10 +60,16 @@ html_meta = {
|
||||
"og:type": "website",
|
||||
"og:url": "https://tzst.xi-xu.me/",
|
||||
"og:image": "https://tzst.xi-xu.me/_static/tzst-logo.png",
|
||||
"og:site_name": "tzst Documentation",
|
||||
"og:locale": "en_US",
|
||||
"twitter:card": "summary_large_image",
|
||||
"twitter:title": "tzst Documentation",
|
||||
"twitter:description": "tzst - A Python library for creating and extracting tar.zst archives with high performance and comprehensive features",
|
||||
"twitter:image": "https://tzst.xi-xu.me/_static/tzst-logo.png",
|
||||
"twitter:site": "@xixu_me",
|
||||
"twitter:creator": "@xixu_me",
|
||||
"article:author": "Xi Xu",
|
||||
"article:publisher": "https://xi-xu.me",
|
||||
}
|
||||
|
||||
# Theme options
|
||||
@@ -123,3 +138,14 @@ myst_enable_extensions = [
|
||||
"substitution",
|
||||
"tasklist",
|
||||
]
|
||||
|
||||
# SEO optimization settings
|
||||
html_copy_source = False # Don't copy source files to _sources (reduces crawl)
|
||||
html_show_sourcelink = False # Hide "View page source" links
|
||||
html_show_sphinx = False # Don't show "Created using Sphinx" in footer
|
||||
|
||||
# Additional HTML files to include (robots.txt will be copied from _static)
|
||||
html_extra_path = []
|
||||
|
||||
# Language for content autogenerated by Sphinx
|
||||
language = "en"
|
||||
@@ -1,3 +1,19 @@
|
||||
---
|
||||
myst:
|
||||
html_meta:
|
||||
description: "Complete development guide for tzst - Setup, testing, contribution guidelines, and best practices"
|
||||
keywords: "tzst development, Python development, contributing to tzst, testing guide, documentation"
|
||||
og:title: "tzst Development Guide"
|
||||
og:description: "Complete development guide for tzst - Setup, testing, contribution guidelines, and best practices"
|
||||
twitter:title: "tzst Development Guide"
|
||||
twitter:description: "Complete development guide for tzst - Setup, testing, contribution guidelines, and best practices"
|
||||
og:type: "website"
|
||||
og:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
|
||||
og:url: "https://tzst.xi-xu.me/development.html"
|
||||
twitter:card: "summary_large_image"
|
||||
twitter:image: "https://tzst.xi-xu.me/_static/tzst-square-logo.png"
|
||||
---
|
||||
|
||||
# Development Guide
|
||||
|
||||
This guide provides comprehensive information for developers contributing to or working with the tzst library.
|
||||
|
||||
+6
-60
@@ -20,7 +20,7 @@ myst:
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/github-code-scanning/codeql)
|
||||
[](https://github.com/xixu-me/tzst/actions/workflows/ci.yml)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypi.org/project/tzst/)
|
||||
[](https://pypistats.org/packages/tzst)
|
||||
[](https://github.com/xixu-me/tzst/blob/main/LICENSE)
|
||||
[](https://xi-xu.me/#sponsorships)
|
||||
|
||||
@@ -101,74 +101,20 @@ with TzstArchive("data.tzst", "r") as archive:
|
||||
|
||||
## Installation
|
||||
|
||||
### From PyPI
|
||||
For detailed installation instructions, including standalone binaries and source installation, please refer to the {doc}`quickstart` guide.
|
||||
|
||||
```bash
|
||||
# Install from PyPI
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
### From GitHub Releases
|
||||
|
||||
Download platform-specific standalone executables from [GitHub Releases](https://github.com/xixu-me/tzst/releases) - no Python installation required!
|
||||
|
||||
#### Supported Platforms
|
||||
|
||||
| Platform | Architecture | File |
|
||||
|----------|-------------|------|
|
||||
| **Linux** | x86_64 | `tzst-{version}-linux-amd64.zip` |
|
||||
| **Linux** | ARM64 | `tzst-{version}-linux-arm64.zip` |
|
||||
| **Windows** | x64 | `tzst-{version}-windows-amd64.zip` |
|
||||
| **Windows** | ARM64 | `tzst-{version}-windows-arm64.zip` |
|
||||
| **macOS** | Intel | `tzst-{version}-darwin-amd64.zip` |
|
||||
| **macOS** | Apple Silicon | `tzst-{version}-darwin-arm64.zip` |
|
||||
|
||||
#### Installation Steps
|
||||
|
||||
1. **Download** the appropriate archive for your platform from the [latest releases page](https://github.com/xixu-me/tzst/releases/latest)
|
||||
2. **Extract** the archive to get the `tzst` executable (or `tzst.exe` on Windows)
|
||||
3. **Move** the executable to a directory in your PATH:
|
||||
- **Linux/macOS**: `sudo mv tzst /usr/local/bin/`
|
||||
- **Windows**: Add the directory containing `tzst.exe` to your PATH environment variable
|
||||
4. **Verify** installation: `tzst --help`
|
||||
|
||||
#### Benefits of Binary Installation
|
||||
|
||||
- **No Python required** - Standalone executable
|
||||
- **Faster startup** - No Python interpreter overhead
|
||||
- **Easy deployment** - Single file distribution
|
||||
- **Consistent behavior** - Bundled dependencies
|
||||
|
||||
### Using uvx (No Installation)
|
||||
|
||||
Run tzst directly without installation using [uvx](https://docs.astral.sh/uv/):
|
||||
|
||||
```bash
|
||||
uvx tzst --help
|
||||
uvx tzst a archive.tzst file1.txt file2.txt directory/
|
||||
uvx tzst x archive.tzst
|
||||
```
|
||||
|
||||
Perfect for one-time usage, testing, CI/CD pipelines, and isolated environments.
|
||||
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/xixu-me/tzst.git
|
||||
cd tzst
|
||||
pip install .
|
||||
# Or using uv (recommended)
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
For a quick introduction, see the {doc}`quickstart` guide. For comprehensive usage examples, explore the {doc}`examples` section.
|
||||
|
||||
### Installation Options
|
||||
|
||||
1. **PyPI Installation**: `pip install tzst`
|
||||
2. **Standalone Binaries**: Download from [GitHub Releases](https://github.com/xixu-me/tzst/releases)
|
||||
3. **uvx (No Installation)**: Run directly with `uvx tzst`
|
||||
4. **From Source**: Clone and install from repository
|
||||
|
||||
### API Documentation
|
||||
|
||||
Complete API documentation is available in the {doc}`api/index` section, covering:
|
||||
@@ -222,7 +168,7 @@ We welcome contributions! Please read our [Contributing Guide](https://github.co
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2025 [Xi Xu](https://xi-xu.me). All rights reserved.
|
||||
Copyright © [Xi Xu](https://xi-xu.me). All rights reserved.
|
||||
|
||||
Licensed under the [BSD 3-Clause](https://github.com/xixu-me/tzst/blob/main/LICENSE) license.
|
||||
|
||||
|
||||
+47
-30
@@ -24,45 +24,52 @@ This guide will get you up and running with tzst in just a few minutes.
|
||||
|
||||
Choose your preferred installation method:
|
||||
|
||||
### Option 1: PyPI
|
||||
### From GitHub Releases
|
||||
|
||||
Download standalone executables that don't require Python installation:
|
||||
|
||||
#### Supported Platforms
|
||||
|
||||
| Platform | Architecture | File |
|
||||
|----------|-------------|------|
|
||||
| **🐧 Linux** | x86_64 | `tzst-{version}-linux-amd64.zip` |
|
||||
| **🐧 Linux** | ARM64 | `tzst-{version}-linux-arm64.zip` |
|
||||
| **🪟 Windows** | x64 | `tzst-{version}-windows-amd64.zip` |
|
||||
| **🪟 Windows** | ARM64 | `tzst-{version}-windows-arm64.zip` |
|
||||
| **🍎 macOS** | Intel | `tzst-{version}-darwin-amd64.zip` |
|
||||
| **🍎 macOS** | Apple Silicon | `tzst-{version}-darwin-arm64.zip` |
|
||||
|
||||
#### 🛠️ Installation Steps
|
||||
|
||||
1. **📥 Download** the appropriate archive for your platform from the [latest releases page](https://github.com/xixu-me/tzst/releases/latest)
|
||||
2. **📦 Extract** the archive to get the `tzst` executable (or `tzst.exe` on Windows)
|
||||
3. **📂 Move** the executable to a directory in your PATH:
|
||||
- **🐧 Linux/macOS**: `sudo mv tzst /usr/local/bin/`
|
||||
- **🪟 Windows**: Add the directory containing `tzst.exe` to your PATH environment variable
|
||||
4. **✅ Verify** installation: `tzst --help`
|
||||
|
||||
#### 🎯 Benefits of Binary Installation
|
||||
|
||||
- ✅ **No Python required** - Standalone executable
|
||||
- ✅ **Faster startup** - No Python interpreter overhead
|
||||
- ✅ **Easy deployment** - Single file distribution
|
||||
- ✅ **Consistent behavior** - Bundled dependencies
|
||||
|
||||
### From PyPI
|
||||
|
||||
Using pip:
|
||||
|
||||
```bash
|
||||
pip install tzst
|
||||
```
|
||||
|
||||
### Option 2: Standalone Binary
|
||||
|
||||
Download the appropriate executable from [GitHub Releases](https://github.com/xixu-me/tzst/releases):
|
||||
|
||||
| Platform | Architecture | Download |
|
||||
|----------|--------------|----------|
|
||||
| **Linux** | x86_64 | `tzst-{version}-linux-amd64.zip` |
|
||||
| **Linux** | ARM64 | `tzst-{version}-linux-arm64.zip` |
|
||||
| **Windows** | x64 | `tzst-{version}-windows-amd64.zip` |
|
||||
| **Windows** | ARM64 | `tzst-{version}-windows-arm64.zip` |
|
||||
| **macOS** | Intel | `tzst-{version}-darwin-amd64.zip` |
|
||||
| **macOS** | Apple Silicon | `tzst-{version}-darwin-arm64.zip` |
|
||||
|
||||
Extract the archive and add the executable to your PATH.
|
||||
|
||||
### Option 3: Using uvx (No Installation)
|
||||
|
||||
Run tzst directly without installation using [uvx](https://docs.astral.sh/uv/):
|
||||
Or using uv (recommended):
|
||||
|
||||
```bash
|
||||
uvx tzst --help
|
||||
uvx tzst a archive.tzst file1.txt file2.txt directory/
|
||||
uvx tzst x archive.tzst
|
||||
uv tool install tzst
|
||||
```
|
||||
|
||||
This option is perfect for:
|
||||
|
||||
- **One-time usage** - No permanent installation needed
|
||||
- **Testing** - Try tzst without committing to installation
|
||||
- **CI/CD pipelines** - Use tzst in automated workflows
|
||||
- **Isolated environments** - Avoid dependency conflicts
|
||||
|
||||
### Option 4: From Source
|
||||
### From Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/xixu-me/tzst.git
|
||||
@@ -70,6 +77,16 @@ cd tzst
|
||||
pip install .
|
||||
```
|
||||
|
||||
### Development Installation
|
||||
|
||||
This project uses modern Python packaging standards:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/xixu-me/tzst.git
|
||||
cd tzst
|
||||
pip install -e .[dev]
|
||||
```
|
||||
|
||||
(basic-usage)=
|
||||
|
||||
## Basic Usage
|
||||
|
||||
@@ -10,6 +10,7 @@ sphinx-autobuild>=2021.3.14
|
||||
sphinx-copybutton>=0.5.2
|
||||
sphinxext-opengraph>=0.9.0
|
||||
sphinx-autodoc-typehints>=1.25.0
|
||||
sphinx-sitemap>=2.6.0
|
||||
|
||||
# Alternative modern theme (optional)
|
||||
furo>=2024.1.29
|
||||
|
||||
@@ -4,7 +4,7 @@ Leveraging cutting-edge Zstandard compression to deliver superior performance,
|
||||
security, and reliability.
|
||||
"""
|
||||
|
||||
__version__ = "1.2.8"
|
||||
__version__ = "1.3.2"
|
||||
|
||||
from .core import (
|
||||
TzstArchive,
|
||||
|
||||
+370
-108
@@ -1,9 +1,10 @@
|
||||
"""Command-line interface for tzst."""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Literal, cast
|
||||
from typing import Any, Literal, cast
|
||||
|
||||
from . import __version__
|
||||
from .core import (
|
||||
@@ -92,10 +93,70 @@ def print_banner() -> None:
|
||||
None
|
||||
"""
|
||||
print()
|
||||
print(f"tzst {__version__} : Copyright (c) 2025 Xi Xu")
|
||||
print(f"tzst {__version__} : Copyright (c) Xi Xu")
|
||||
print()
|
||||
|
||||
|
||||
def _wants_json_output(args) -> bool:
|
||||
"""Return True when the caller requested machine-readable output."""
|
||||
return bool(getattr(args, "json_output", False))
|
||||
|
||||
|
||||
def _emit_json(payload: dict[str, Any], *, to_stderr: bool = False) -> None:
|
||||
"""Emit a JSON payload to stdout or stderr."""
|
||||
stream = sys.stderr if to_stderr else sys.stdout
|
||||
print(json.dumps(payload, ensure_ascii=True), file=stream)
|
||||
|
||||
|
||||
def _emit_error(
|
||||
args,
|
||||
message: str,
|
||||
*,
|
||||
error_type: str,
|
||||
exit_code: int = 1,
|
||||
details: dict[str, Any] | None = None,
|
||||
) -> int:
|
||||
"""Emit an error in text or JSON format and return the exit code."""
|
||||
if _wants_json_output(args):
|
||||
payload: dict[str, Any] = {
|
||||
"ok": False,
|
||||
"error": {"type": error_type, "message": message},
|
||||
}
|
||||
if details:
|
||||
payload["error"]["details"] = details
|
||||
_emit_json(payload, to_stderr=True)
|
||||
else:
|
||||
print(message, file=sys.stderr)
|
||||
return exit_code
|
||||
|
||||
|
||||
def _summarize_listing(contents: list[dict[str, Any]]) -> dict[str, int | str]:
|
||||
"""Build the summary block used by list output."""
|
||||
total_files = 0
|
||||
total_dirs = 0
|
||||
total_size = 0
|
||||
|
||||
for item in contents:
|
||||
if item["is_file"]:
|
||||
total_files += 1
|
||||
total_size += item["size"]
|
||||
elif item["is_dir"]:
|
||||
total_dirs += 1
|
||||
|
||||
return {
|
||||
"files": total_files,
|
||||
"directories": total_dirs,
|
||||
"total_size_bytes": total_size,
|
||||
"total_size_human": format_size(total_size),
|
||||
}
|
||||
|
||||
|
||||
def _should_print_banner(argv: list[str] | None) -> bool:
|
||||
"""Determine whether the human-facing banner should be displayed."""
|
||||
cli_args = argv if argv is not None else sys.argv[1:]
|
||||
return "--json" not in cli_args and "--no-banner" not in cli_args
|
||||
|
||||
|
||||
def format_size(size: int) -> str:
|
||||
"""Format file size in human-readable format.
|
||||
|
||||
@@ -220,18 +281,23 @@ def _prepare_archive_creation(args) -> tuple[Path, list[Path], int, bool] | int:
|
||||
# Validate files
|
||||
missing_files = _validate_files(files)
|
||||
if missing_files:
|
||||
print(
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Files not found - {', '.join(map(str, missing_files))}",
|
||||
file=sys.stderr,
|
||||
error_type="files_not_found",
|
||||
details={"missing_files": [str(path) for path in missing_files]},
|
||||
)
|
||||
return 1
|
||||
|
||||
compression_level, use_temp_file = _extract_add_params(args)
|
||||
return archive_path, files, compression_level, use_temp_file
|
||||
|
||||
|
||||
def _execute_archive_creation(
|
||||
archive_path: Path, files: list[Path], compression_level: int, use_temp_file: bool
|
||||
args,
|
||||
archive_path: Path,
|
||||
files: list[Path],
|
||||
compression_level: int,
|
||||
use_temp_file: bool,
|
||||
) -> int:
|
||||
"""Execute the archive creation process.
|
||||
|
||||
@@ -247,18 +313,34 @@ def _execute_archive_creation(
|
||||
# Normalize archive path to show the correct final filename
|
||||
normalized_archive_path = _normalize_archive_path(archive_path)
|
||||
|
||||
print(f"Creating archive: {normalized_archive_path}")
|
||||
for file_path in files:
|
||||
print(f" Adding: {file_path}")
|
||||
if not _wants_json_output(args):
|
||||
print(f"Creating archive: {normalized_archive_path}")
|
||||
for file_path in files:
|
||||
print(f" Adding: {file_path}")
|
||||
|
||||
# Use atomic file operations by default for better reliability
|
||||
# This creates the archive in a temporary file first, then moves it
|
||||
create_archive(archive_path, files, compression_level, use_temp_file=use_temp_file)
|
||||
print(f"Archive created successfully - {normalized_archive_path}")
|
||||
|
||||
if _wants_json_output(args):
|
||||
_emit_json(
|
||||
{
|
||||
"ok": True,
|
||||
"command": "add",
|
||||
"archive": str(archive_path),
|
||||
"normalized_archive": str(normalized_archive_path),
|
||||
"added": [str(file_path) for file_path in files],
|
||||
"compression_level": compression_level,
|
||||
"atomic": use_temp_file,
|
||||
}
|
||||
)
|
||||
else:
|
||||
print(f"Archive created successfully - {normalized_archive_path}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def _handle_archive_creation_exceptions(func, *args, **kwargs) -> int:
|
||||
def _handle_archive_creation_exceptions(command_args, func, *args, **kwargs) -> int:
|
||||
"""Handle exceptions during archive creation.
|
||||
|
||||
Args:
|
||||
@@ -274,19 +356,32 @@ def _handle_archive_creation_exceptions(func, *args, **kwargs) -> int:
|
||||
except OSError:
|
||||
return 1 # Error already printed in _validate_files
|
||||
except ValueError as e:
|
||||
print(f"Error: Invalid parameter - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
command_args,
|
||||
f"Error: Invalid parameter - {e}",
|
||||
error_type="invalid_parameter",
|
||||
)
|
||||
except TzstArchiveError as e:
|
||||
print(f"Error: Archive operation failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
command_args,
|
||||
f"Error: Archive operation failed - {e}",
|
||||
error_type="archive_operation_failed",
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation interrupted by user", file=sys.stderr)
|
||||
return _emit_error(
|
||||
command_args,
|
||||
"Operation interrupted by user",
|
||||
error_type="interrupted",
|
||||
exit_code=130,
|
||||
)
|
||||
# Clean up any partial files - the atomic operations in create_archive
|
||||
# handle this
|
||||
return 130 # Standard exit code for SIGINT
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to create archive - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
command_args,
|
||||
f"Error: Failed to create archive - {e}",
|
||||
error_type="create_failed",
|
||||
)
|
||||
|
||||
|
||||
def cmd_add(args) -> int:
|
||||
@@ -326,10 +421,10 @@ def cmd_add(args) -> int:
|
||||
|
||||
archive_path, files, compression_level, use_temp_file = preparation_result
|
||||
return _execute_archive_creation(
|
||||
archive_path, files, compression_level, use_temp_file
|
||||
args, archive_path, files, compression_level, use_temp_file
|
||||
)
|
||||
|
||||
return _handle_archive_creation_exceptions(_create_archive_workflow)
|
||||
return _handle_archive_creation_exceptions(args, _create_archive_workflow)
|
||||
|
||||
|
||||
def cmd_extract_full(args) -> int:
|
||||
@@ -364,8 +459,12 @@ def cmd_extract_full(args) -> int:
|
||||
try:
|
||||
archive_path = Path(args.archive)
|
||||
if not archive_path.exists():
|
||||
print(f"Error: Archive not found - {archive_path}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive not found - {archive_path}",
|
||||
error_type="archive_not_found",
|
||||
details={"archive": str(archive_path)},
|
||||
)
|
||||
|
||||
output_dir = Path(args.output) if args.output else Path.cwd()
|
||||
members = args.files if hasattr(args, "files") and args.files else None
|
||||
@@ -385,19 +484,27 @@ def cmd_extract_full(args) -> int:
|
||||
# Convert string to ConflictResolution enum
|
||||
conflict_resolution = ConflictResolution(conflict_resolution_str)
|
||||
|
||||
if _wants_json_output(args) and conflict_resolution == ConflictResolution.ASK:
|
||||
return _emit_error(
|
||||
args,
|
||||
"Error: JSON mode does not support interactive conflict prompts",
|
||||
error_type="interactive_conflict_not_supported",
|
||||
)
|
||||
|
||||
# Set up interactive callback if needed
|
||||
interactive_callback = None
|
||||
if conflict_resolution == ConflictResolution.ASK:
|
||||
interactive_callback = _interactive_conflict_callback
|
||||
|
||||
print(f"Extracting from: {archive_path}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
if filter_type != "data":
|
||||
print(f"Using security filter: {filter_type}")
|
||||
if conflict_resolution != ConflictResolution.REPLACE:
|
||||
print(f"Conflict resolution: {conflict_resolution.value}")
|
||||
if not _wants_json_output(args):
|
||||
print(f"Extracting from: {archive_path}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
if filter_type != "data":
|
||||
print(f"Using security filter: {filter_type}")
|
||||
if conflict_resolution != ConflictResolution.REPLACE:
|
||||
print(f"Conflict resolution: {conflict_resolution.value}")
|
||||
|
||||
extract_archive(
|
||||
archive_path,
|
||||
@@ -409,24 +516,56 @@ def cmd_extract_full(args) -> int:
|
||||
conflict_resolution=conflict_resolution,
|
||||
interactive_callback=interactive_callback,
|
||||
)
|
||||
print("Extraction completed successfully")
|
||||
|
||||
if _wants_json_output(args):
|
||||
_emit_json(
|
||||
{
|
||||
"ok": True,
|
||||
"command": "extract",
|
||||
"archive": str(archive_path),
|
||||
"output_dir": str(output_dir),
|
||||
"members": members or [],
|
||||
"flatten": False,
|
||||
"streaming": streaming,
|
||||
"filter": filter_type,
|
||||
"conflict_resolution": conflict_resolution.value,
|
||||
}
|
||||
)
|
||||
else:
|
||||
print("Extraction completed successfully")
|
||||
return 0
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: File not found - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: File not found - {e}",
|
||||
error_type="file_not_found",
|
||||
)
|
||||
except TzstDecompressionError as e:
|
||||
print(f"Error: Archive decompression failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive decompression failed - {e}",
|
||||
error_type="decompression_failed",
|
||||
)
|
||||
except TzstArchiveError as e:
|
||||
print(f"Error: Archive operation failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive operation failed - {e}",
|
||||
error_type="archive_operation_failed",
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation interrupted by user", file=sys.stderr)
|
||||
return 130
|
||||
return _emit_error(
|
||||
args,
|
||||
"Operation interrupted by user",
|
||||
error_type="interrupted",
|
||||
exit_code=130,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to extract archive - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Failed to extract archive - {e}",
|
||||
error_type="extract_failed",
|
||||
)
|
||||
|
||||
|
||||
def cmd_extract_flat(args) -> int:
|
||||
@@ -462,8 +601,12 @@ def cmd_extract_flat(args) -> int:
|
||||
try:
|
||||
archive_path = Path(args.archive)
|
||||
if not archive_path.exists():
|
||||
print(f"Error: Archive not found - {archive_path}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive not found - {archive_path}",
|
||||
error_type="archive_not_found",
|
||||
details={"archive": str(archive_path)},
|
||||
)
|
||||
|
||||
output_dir = Path(args.output) if args.output else Path.cwd()
|
||||
members = args.files if hasattr(args, "files") and args.files else None
|
||||
@@ -483,17 +626,25 @@ def cmd_extract_flat(args) -> int:
|
||||
# Convert string to ConflictResolution enum
|
||||
conflict_resolution = ConflictResolution(conflict_resolution_str)
|
||||
|
||||
if _wants_json_output(args) and conflict_resolution == ConflictResolution.ASK:
|
||||
return _emit_error(
|
||||
args,
|
||||
"Error: JSON mode does not support interactive conflict prompts",
|
||||
error_type="interactive_conflict_not_supported",
|
||||
)
|
||||
|
||||
# Set up interactive callback if needed
|
||||
interactive_callback = None
|
||||
if conflict_resolution == ConflictResolution.ASK:
|
||||
interactive_callback = _interactive_conflict_callback
|
||||
|
||||
print(f"Extracting from: {archive_path}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
if filter_type != "data":
|
||||
print(f"Using security filter: {filter_type}")
|
||||
if conflict_resolution != ConflictResolution.REPLACE:
|
||||
print(f"Conflict resolution: {conflict_resolution.value}")
|
||||
if not _wants_json_output(args):
|
||||
print(f"Extracting from: {archive_path}")
|
||||
print(f"Output directory: {output_dir}")
|
||||
if filter_type != "data":
|
||||
print(f"Using security filter: {filter_type}")
|
||||
if conflict_resolution != ConflictResolution.REPLACE:
|
||||
print(f"Conflict resolution: {conflict_resolution.value}")
|
||||
|
||||
extract_archive(
|
||||
archive_path,
|
||||
@@ -505,21 +656,49 @@ def cmd_extract_flat(args) -> int:
|
||||
conflict_resolution=conflict_resolution,
|
||||
interactive_callback=interactive_callback,
|
||||
)
|
||||
print("Extraction completed successfully")
|
||||
|
||||
if _wants_json_output(args):
|
||||
_emit_json(
|
||||
{
|
||||
"ok": True,
|
||||
"command": "extract-flat",
|
||||
"archive": str(archive_path),
|
||||
"output_dir": str(output_dir),
|
||||
"members": members or [],
|
||||
"flatten": True,
|
||||
"streaming": streaming,
|
||||
"filter": filter_type,
|
||||
"conflict_resolution": conflict_resolution.value,
|
||||
}
|
||||
)
|
||||
else:
|
||||
print("Extraction completed successfully")
|
||||
return 0
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: File not found - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: File not found - {e}",
|
||||
error_type="file_not_found",
|
||||
)
|
||||
except TzstDecompressionError as e:
|
||||
print(f"Error: Archive decompression failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive decompression failed - {e}",
|
||||
error_type="decompression_failed",
|
||||
)
|
||||
except TzstArchiveError as e:
|
||||
print(f"Error: Archive operation failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive operation failed - {e}",
|
||||
error_type="archive_operation_failed",
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to extract archive - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Failed to extract archive - {e}",
|
||||
error_type="extract_failed",
|
||||
)
|
||||
|
||||
|
||||
def _print_verbose_listing(contents: list) -> None:
|
||||
@@ -543,22 +722,14 @@ def _print_simple_listing(contents: list) -> None:
|
||||
Args:
|
||||
contents: List of archive items
|
||||
"""
|
||||
total_files = 0
|
||||
total_dirs = 0
|
||||
total_size = 0
|
||||
|
||||
for item in contents:
|
||||
if item["is_file"]:
|
||||
total_files += 1
|
||||
total_size += item["size"]
|
||||
elif item["is_dir"]:
|
||||
total_dirs += 1
|
||||
print(item["name"])
|
||||
|
||||
print()
|
||||
summary = _summarize_listing(contents)
|
||||
total_msg = (
|
||||
f"Total: {total_files} files, {total_dirs} directories, "
|
||||
f"{format_size(total_size)}"
|
||||
f"Total: {summary['files']} files, {summary['directories']} directories, "
|
||||
f"{summary['total_size_human']}"
|
||||
)
|
||||
print(total_msg)
|
||||
|
||||
@@ -592,20 +763,37 @@ def cmd_list(args) -> int:
|
||||
try:
|
||||
archive_path = Path(args.archive)
|
||||
if not archive_path.exists():
|
||||
print(f"Error: Archive not found - {archive_path}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive not found - {archive_path}",
|
||||
error_type="archive_not_found",
|
||||
details={"archive": str(archive_path)},
|
||||
)
|
||||
|
||||
verbose = getattr(args, "verbose", False)
|
||||
streaming = getattr(args, "streaming", False)
|
||||
|
||||
print(f"Listing contents of: {archive_path}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
print()
|
||||
if not _wants_json_output(args):
|
||||
print(f"Listing contents of: {archive_path}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
print()
|
||||
|
||||
contents = list_archive(archive_path, verbose=verbose, streaming=streaming)
|
||||
|
||||
if verbose:
|
||||
if _wants_json_output(args):
|
||||
_emit_json(
|
||||
{
|
||||
"ok": True,
|
||||
"command": "list",
|
||||
"archive": str(archive_path),
|
||||
"verbose": verbose,
|
||||
"streaming": streaming,
|
||||
"contents": contents,
|
||||
"summary": _summarize_listing(contents),
|
||||
}
|
||||
)
|
||||
elif verbose:
|
||||
_print_verbose_listing(contents)
|
||||
else:
|
||||
_print_simple_listing(contents)
|
||||
@@ -613,20 +801,36 @@ def cmd_list(args) -> int:
|
||||
return 0
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: File not found - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: File not found - {e}",
|
||||
error_type="file_not_found",
|
||||
)
|
||||
except TzstDecompressionError as e:
|
||||
print(f"Error: Archive decompression failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive decompression failed - {e}",
|
||||
error_type="decompression_failed",
|
||||
)
|
||||
except TzstArchiveError as e:
|
||||
print(f"Error: Archive operation failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive operation failed - {e}",
|
||||
error_type="archive_operation_failed",
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation interrupted by user", file=sys.stderr)
|
||||
return 130
|
||||
return _emit_error(
|
||||
args,
|
||||
"Operation interrupted by user",
|
||||
error_type="interrupted",
|
||||
exit_code=130,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to list archive - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Failed to list archive - {e}",
|
||||
error_type="list_failed",
|
||||
)
|
||||
|
||||
|
||||
def cmd_test(args) -> int:
|
||||
@@ -658,37 +862,79 @@ def cmd_test(args) -> int:
|
||||
try:
|
||||
archive_path = Path(args.archive)
|
||||
if not archive_path.exists():
|
||||
print(f"Error: Archive not found - {archive_path}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive not found - {archive_path}",
|
||||
error_type="archive_not_found",
|
||||
details={"archive": str(archive_path)},
|
||||
)
|
||||
|
||||
streaming = getattr(args, "streaming", False)
|
||||
|
||||
print(f"Testing archive: {archive_path}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
if not _wants_json_output(args):
|
||||
print(f"Testing archive: {archive_path}")
|
||||
if streaming:
|
||||
print("Using streaming mode (memory efficient)")
|
||||
|
||||
if test_archive(archive_path, streaming=streaming):
|
||||
print("Archive test passed - no errors detected")
|
||||
healthy = test_archive(archive_path, streaming=streaming)
|
||||
if healthy:
|
||||
if _wants_json_output(args):
|
||||
_emit_json(
|
||||
{
|
||||
"ok": True,
|
||||
"command": "test",
|
||||
"archive": str(archive_path),
|
||||
"streaming": streaming,
|
||||
"healthy": True,
|
||||
}
|
||||
)
|
||||
else:
|
||||
print("Archive test passed - no errors detected")
|
||||
return 0
|
||||
else:
|
||||
print("Archive test failed - errors detected", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
"Archive test failed - errors detected",
|
||||
error_type="integrity_check_failed",
|
||||
details={
|
||||
"command": "test",
|
||||
"archive": str(archive_path),
|
||||
"streaming": streaming,
|
||||
"healthy": False,
|
||||
},
|
||||
)
|
||||
|
||||
except FileNotFoundError as e:
|
||||
print(f"Error: File not found - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: File not found - {e}",
|
||||
error_type="file_not_found",
|
||||
)
|
||||
except TzstDecompressionError as e:
|
||||
print(f"Error: Archive decompression failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive decompression failed - {e}",
|
||||
error_type="decompression_failed",
|
||||
)
|
||||
except TzstArchiveError as e:
|
||||
print(f"Error: Archive operation failed - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Archive operation failed - {e}",
|
||||
error_type="archive_operation_failed",
|
||||
)
|
||||
except KeyboardInterrupt:
|
||||
print("\nOperation interrupted by user", file=sys.stderr)
|
||||
return 130
|
||||
return _emit_error(
|
||||
args,
|
||||
"Operation interrupted by user",
|
||||
error_type="interrupted",
|
||||
exit_code=130,
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Error: Failed to test archive - {e}", file=sys.stderr)
|
||||
return 1
|
||||
return _emit_error(
|
||||
args,
|
||||
f"Error: Failed to test archive - {e}",
|
||||
error_type="test_failed",
|
||||
)
|
||||
|
||||
|
||||
def cmd_version(args) -> int:
|
||||
@@ -697,7 +943,11 @@ def cmd_version(args) -> int:
|
||||
Returns:
|
||||
int: Exit code (always 0)
|
||||
"""
|
||||
# Version is already printed in print_banner(), so just exit
|
||||
if _wants_json_output(args):
|
||||
_emit_json({"ok": True, "command": "version", "version": __version__})
|
||||
elif getattr(args, "no_banner", False):
|
||||
print(f"tzst {__version__}")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
@@ -763,6 +1013,17 @@ documentation:
|
||||
parser.add_argument(
|
||||
"--version", action="store_true", help="show version information and exit"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
dest="json_output",
|
||||
action="store_true",
|
||||
help="emit machine-readable JSON output",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-banner",
|
||||
action="store_true",
|
||||
help="suppress the startup banner",
|
||||
)
|
||||
|
||||
# Add global arguments
|
||||
subparsers = parser.add_subparsers(
|
||||
@@ -1151,7 +1412,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||
See Also:
|
||||
:func:`create_parser`: Creates the argument parser used by this function
|
||||
"""
|
||||
print_banner()
|
||||
if _should_print_banner(argv):
|
||||
print_banner()
|
||||
|
||||
parser = create_parser()
|
||||
args, error_code = _parse_arguments(parser, argv)
|
||||
|
||||
+66
-1
@@ -5,6 +5,8 @@ and provide a single source of truth for CLI testing.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -96,7 +98,8 @@ class TestUtilityFunctions:
|
||||
validate_compression_level("abc")
|
||||
|
||||
with pytest.raises(
|
||||
argparse.ArgumentTypeError, match="Invalid compression level: '1.5'"
|
||||
argparse.ArgumentTypeError,
|
||||
match=r"Invalid compression level: '1\.5'\. Must be an integer between 1 and 22\.",
|
||||
):
|
||||
validate_compression_level("1.5")
|
||||
|
||||
@@ -145,6 +148,15 @@ class TestCLIParser:
|
||||
assert args.command == "t"
|
||||
assert args.archive == "test.tzst"
|
||||
|
||||
def test_global_machine_readable_flags(self):
|
||||
"""Test parsing of machine-readable output flags."""
|
||||
parser = create_parser()
|
||||
args = parser.parse_args(["--json", "--no-banner", "l", "test.tzst"])
|
||||
|
||||
assert args.json_output is True
|
||||
assert args.no_banner is True
|
||||
assert args.command == "l"
|
||||
|
||||
|
||||
class TestCLICommands:
|
||||
"""Test CLI command execution."""
|
||||
@@ -201,6 +213,59 @@ class TestCLICommands:
|
||||
|
||||
assert result == 0
|
||||
|
||||
def test_add_command_json_output(self, sample_files, temp_dir, capsys):
|
||||
"""Test add command JSON output for sidecar integrations."""
|
||||
archive_path = temp_dir / "json-add.tzst"
|
||||
file_paths = [str(f) for f in sample_files if f.is_file()]
|
||||
expected_added = [str(Path(path).resolve()) for path in file_paths]
|
||||
|
||||
result = main(["--json", "a", str(archive_path), *file_paths])
|
||||
|
||||
assert result == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["ok"] is True
|
||||
assert payload["command"] == "add"
|
||||
assert payload["normalized_archive"] == str(archive_path)
|
||||
assert payload["added"] == expected_added
|
||||
|
||||
def test_list_command_json_output(self, sample_files, temp_dir, capsys):
|
||||
"""Test list command JSON output for the desktop app."""
|
||||
archive_path = temp_dir / "json-list.tzst"
|
||||
file_paths = [str(f) for f in sample_files if f.is_file()]
|
||||
|
||||
create_result = main(["--no-banner", "a", str(archive_path), *file_paths])
|
||||
assert create_result == 0
|
||||
capsys.readouterr()
|
||||
|
||||
result = main(["--json", "l", str(archive_path)])
|
||||
|
||||
assert result == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["ok"] is True
|
||||
assert payload["command"] == "list"
|
||||
assert payload["summary"]["files"] >= 1
|
||||
assert isinstance(payload["contents"], list)
|
||||
|
||||
def test_missing_archive_json_error(self, temp_dir, capsys):
|
||||
"""Test JSON-formatted error output."""
|
||||
fake_archive = temp_dir / "missing.tzst"
|
||||
|
||||
result = main(["--json", "l", str(fake_archive)])
|
||||
|
||||
assert result == 1
|
||||
payload = json.loads(capsys.readouterr().err)
|
||||
assert payload["ok"] is False
|
||||
assert payload["error"]["type"] == "archive_not_found"
|
||||
|
||||
def test_version_json_output(self, capsys):
|
||||
"""Test JSON version output without the startup banner."""
|
||||
result = main(["--json", "--version"])
|
||||
|
||||
assert result == 0
|
||||
payload = json.loads(capsys.readouterr().out)
|
||||
assert payload["ok"] is True
|
||||
assert payload["command"] == "version"
|
||||
|
||||
|
||||
class TestCLIErrorHandling:
|
||||
"""Test CLI error handling."""
|
||||
|
||||
Reference in new issue
Block a user