mirror of
https://github.com/adamhathcock/sharpcompress.git
synced 2026-07-09 02:26:47 +00:00
232 lines
13 KiB
Markdown
232 lines
13 KiB
Markdown
---
|
|
description: 'Guidelines for building SharpCompress - A C# compression library'
|
|
applyTo: '**/*.cs'
|
|
---
|
|
|
|
# SharpCompress Development
|
|
|
|
## About SharpCompress
|
|
SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard, Arc, Arj, Ace, LZW). The project currently targets .NET Framework 4.8, .NET Standard 2.0/2.1, .NET 6.0, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios.
|
|
|
|
## C# Instructions
|
|
- Use language features supported by the current project toolchain (`LangVersion=latest`) and existing codebase patterns.
|
|
- Add comments for non-obvious logic and important design decisions; avoid redundant comments.
|
|
- Follow the existing code style and patterns in the codebase.
|
|
|
|
## General Instructions
|
|
- **Do not commit or stage changes unless the user explicitly asks for it.**
|
|
- Make only high confidence suggestions when reviewing code changes.
|
|
- Write code with good maintainability practices, including comments on why certain design decisions were made.
|
|
- Handle edge cases and write clear exception handling.
|
|
- For libraries or external dependencies, mention their usage and purpose in comments.
|
|
- Preserve backward compatibility when making changes to public APIs.
|
|
|
|
### Workspace Hygiene
|
|
- Do not edit generated or machine-local files unless required for the task (for example: `bin/`, `obj/`, `*.csproj.user`).
|
|
- Avoid broad formatting-only diffs in unrelated files.
|
|
|
|
## Naming Conventions
|
|
|
|
- Follow PascalCase for component names, method names, and public members.
|
|
- Use camelCase for private fields and local variables.
|
|
- Prefix interface names with "I" (e.g., IUserService).
|
|
|
|
## Code Formatting
|
|
|
|
**Copilot agents: You MUST run the `format` task after making code changes to ensure consistency.**
|
|
|
|
- Use CSharpier for code formatting to ensure consistent style across the project
|
|
- CSharpier is configured as a local tool in `.config/dotnet-tools.json`
|
|
|
|
### Commands
|
|
|
|
1. **Restore tools** (first time only):
|
|
```bash
|
|
dotnet tool restore
|
|
```
|
|
|
|
2. **Check if files are formatted correctly** (doesn't modify files):
|
|
```bash
|
|
dotnet csharpier check .
|
|
```
|
|
- Exit code 0: All files are properly formatted
|
|
- Exit code 1: Some files need formatting (will show which files and differences)
|
|
|
|
3. **Format files** (modifies files):
|
|
```bash
|
|
dotnet csharpier format .
|
|
```
|
|
- Formats all files in the project to match CSharpier style
|
|
- Run from project root directory
|
|
|
|
4. **Configure your IDE** to format on save using CSharpier for the best experience
|
|
|
|
### Additional Notes
|
|
- The project also uses `.editorconfig` for editor settings (indentation, encoding, etc.)
|
|
- Let CSharpier handle code style while `.editorconfig` handles editor behavior
|
|
- Always run `dotnet csharpier check .` before committing to verify formatting
|
|
|
|
## Project Setup and Structure
|
|
|
|
- The project targets multiple frameworks: .NET Framework 4.8, .NET Standard 2.0/2.1, .NET 6.0, .NET 8.0, and .NET 10.0
|
|
- Main library is in `src/SharpCompress/`
|
|
- Tests are in `tests/SharpCompress.Test/`
|
|
- Performance tests are in `tests/SharpCompress.Performance/`
|
|
- Test archives are in `tests/TestArchives/`
|
|
- Build project is in `build/`
|
|
- Use `dotnet build` to build the solution
|
|
- Use `dotnet test` to run tests
|
|
- Solution file: `SharpCompress.slnx`
|
|
|
|
### Directory Structure
|
|
```
|
|
src/SharpCompress/
|
|
├── Archives/ # IArchive implementations (Zip, Tar, Rar, 7Zip, GZip)
|
|
├── Readers/ # IReader implementations (forward-only)
|
|
├── Writers/ # IWriter implementations (forward-only)
|
|
├── Compressors/ # Low-level compression streams (BZip2, Deflate, LZMA, etc.)
|
|
├── Factories/ # Format detection and factory pattern
|
|
├── Common/ # Shared types (ArchiveType, Entry, Options)
|
|
├── Crypto/ # Encryption implementations
|
|
└── IO/ # Stream utilities and wrappers
|
|
|
|
tests/SharpCompress.Test/
|
|
├── Zip/, Tar/, Rar/, SevenZip/, GZip/, BZip2/ # Format-specific tests
|
|
├── TestBase.cs # Base test class with helper methods
|
|
|
|
tests/
|
|
├── SharpCompress.Test/ # Unit/integration tests
|
|
├── SharpCompress.Performance/ # Benchmark tests
|
|
└── TestArchives/ # Test data archives
|
|
```
|
|
|
|
### Factory Pattern
|
|
Factory implementations can implement one or more interfaces (`IArchiveFactory`, `IReaderFactory`, `IWriterFactory`) depending on format capabilities:
|
|
- `ArchiveFactory.OpenArchive()` - Opens archive API objects from seekable streams/files
|
|
- `ArchiveFactory.OpenAsyncArchive()` - Opens async archive API objects for async archive use cases
|
|
- `ReaderFactory.OpenReader()` - Auto-detects and opens forward-only readers
|
|
- `ReaderFactory.OpenAsyncReader()` - Auto-detects and opens forward-only async readers
|
|
- `WriterFactory.OpenWriter()` - Creates a writer for a specified `ArchiveType`
|
|
- `WriterFactory.OpenAsyncWriter()` - Creates an async writer for async write scenarios
|
|
- Factories located in: `src/SharpCompress/Factories/`
|
|
|
|
## Nullable Reference Types
|
|
|
|
- Declare variables non-nullable, and check for `null` at entry points.
|
|
- Always use `is null` or `is not null` instead of `== null` or `!= null`.
|
|
- Trust the C# null annotations and don't add null checks when the type system says a value cannot be null.
|
|
|
|
## SharpCompress-Specific Guidelines
|
|
|
|
### Supported Formats
|
|
SharpCompress supports multiple archive and compression formats:
|
|
- **Archive Formats**: Zip, Tar, 7Zip, Rar (read-only), Ace (read-only), Arc (read-only), Arj (read-only), LZW (read-only)
|
|
- **Compression**: DEFLATE, BZip2, LZMA/LZMA2, PPMd, ZStandard, LZip, XZ (decompress only), Deflate64 (decompress only), legacy Zip/Arc/Arj/Ace methods (read-only as applicable)
|
|
- **Combined Formats**: Tar.GZip, Tar.BZip2, Tar.LZip, Tar.XZ (decompress only), Tar.ZStandard (decompress only), Tar.LZW (decompress only)
|
|
- **ZIP ZStandard**: ZIP supports ZStandard reading and writing; Tar.ZStandard is decompress-only.
|
|
- See [docs/FORMATS.md](docs/FORMATS.md) for complete format support matrix
|
|
|
|
### Stream Handling Rules
|
|
- **Disposal semantics**: The default `ReaderOptions.LeaveStreamOpen` value is `false`, but effective stream ownership depends on which API overload you call
|
|
- File-based overloads (e.g., `OpenArchive(string filePath)`) open the file internally and own that stream, so it is closed by default with the archive/reader
|
|
- Do **not** rely on a specific `ReaderOptions` preset being used internally; some implementations may use `ReaderOptions.ForFilePath`, while others may use default `ReaderOptions` with the same ownership semantics
|
|
- Several high-level overloads that accept a caller-provided `Stream` use external-stream semantics by default (for example, `ReaderFactory.OpenReader(Stream)` / `ArchiveFactory.OpenArchive(Stream)`), so the caller's stream is typically left open unless you opt into different ownership behavior
|
|
- Do **not** assume every stream-based overload behaves identically; some APIs require you to pass stream ownership options explicitly
|
|
- **For caller-provided streams**: When the overload accepts `ReaderOptions`, pass `ReaderOptions.ForExternalStream` or use `ReaderOptions` with `LeaveStreamOpen = true` whenever the caller must retain ownership of the stream
|
|
- Example: `var options = new ReaderOptions { LeaveStreamOpen = true };`
|
|
- Or: `var options = ReaderOptions.ForExternalStream;`
|
|
- **For file paths**: SharpCompress manages the stream lifecycle for the internally opened file stream; no manual disposal is needed beyond the archive/reader itself
|
|
- Use `NonDisposingStream` wrapper when working with compression streams directly to prevent disposal
|
|
- Always dispose of readers, writers, and archives in `using` / `await using` blocks
|
|
- For forward-only operations, use Reader/Writer APIs; for random access, use Archive APIs
|
|
|
|
### Async/Await Patterns
|
|
- All I/O operations support async/await with `CancellationToken`
|
|
- Async methods follow the naming convention: `MethodNameAsync`
|
|
- For async archive scenarios, prefer `ArchiveFactory.OpenAsyncArchive(...)` over sync `OpenArchive(...)`.
|
|
- For async forward-only read scenarios, prefer `ReaderFactory.OpenAsyncReader(...)` over sync `OpenReader(...)`.
|
|
- For async write scenarios, prefer `WriterFactory.OpenAsyncWriter(...)` over sync `OpenWriter(...)`.
|
|
- Key async methods:
|
|
- `WriteEntryToAsync` - Extract entry asynchronously
|
|
- `WriteAllToDirectoryAsync` - Extract all entries asynchronously
|
|
- `WriteAsync` - Write entry asynchronously
|
|
- `WriteAllAsync` - Write directory asynchronously
|
|
- `OpenEntryStreamAsync` - Open entry stream asynchronously
|
|
- Always provide `CancellationToken` parameter in async methods
|
|
|
|
### Archive APIs vs Reader/Writer APIs
|
|
- **Archive API**: Use for random access with seekable streams (e.g., `ZipArchive`, `TarArchive`)
|
|
- **Reader API**: Use for forward-only reading on non-seekable streams (e.g., `ZipReader`, `TarReader`)
|
|
- **Writer API**: Use for forward-only writing on streams (e.g., `ZipWriter`, `TarWriter`)
|
|
- 7Zip only supports Archive API due to format limitations
|
|
|
|
### Tar-Specific Considerations
|
|
- Tar format requires file size in the header
|
|
- If no size is specified to TarWriter and the stream is not seekable, an exception will be thrown
|
|
- Tar combined with compression is supported for reading with GZip, BZip2, LZip, XZ, ZStandard, and LZW
|
|
- Tar writing supports uncompressed Tar, GZip, BZip2, and LZip wrappers
|
|
|
|
### Zip-Specific Considerations
|
|
- Supports Zip64 for large files (seekable streams only)
|
|
- Supports PKWare and WinZip AES encryption
|
|
- Multiple compression methods: None, Shrink, Reduce, Implode, DEFLATE, Deflate64, BZip2, LZMA, PPMd
|
|
- Encrypted LZMA is not supported
|
|
|
|
### Performance Considerations
|
|
- For large files, use Reader/Writer APIs with non-seekable streams to avoid loading entire file in memory
|
|
- Leverage async I/O for better scalability
|
|
- Consider compression level trade-offs (speed vs. size)
|
|
- Use appropriate buffer sizes for stream operations
|
|
|
|
## Testing
|
|
|
|
- Always include test cases for critical paths of the application.
|
|
- Test with multiple archive formats when making changes to core functionality.
|
|
- Include tests for both Archive and Reader/Writer APIs when applicable.
|
|
- Test async operations with cancellation tokens.
|
|
- Do not emit "Act", "Arrange" or "Assert" comments.
|
|
- Copy existing style in nearby files for test method names and capitalization.
|
|
- Use test archives from `tests/TestArchives` directory for consistency.
|
|
- Test stream disposal and `LeaveStreamOpen` behavior.
|
|
- Test edge cases: empty archives, large files, corrupted archives, encrypted archives.
|
|
|
|
### Validation Expectations
|
|
- Run targeted tests for the changed area first.
|
|
- On non-Windows machines, avoid net48 test runs unless Mono is installed; use framework-specific validation such as `--framework net10.0` instead.
|
|
- Run `dotnet csharpier format .` after code edits.
|
|
- Run `dotnet csharpier check .` before handing off changes.
|
|
|
|
### Test Organization
|
|
- Base class: `TestBase` - Provides `TEST_ARCHIVES_PATH`, `SCRATCH_FILES_PATH`, temp directory management
|
|
- Framework: xUnit with AwesomeAssertions
|
|
- Test archives: `tests/TestArchives/` - Use existing archives, don't create new ones unnecessarily
|
|
- Match naming style of nearby test files
|
|
|
|
### Public API Change Checklist
|
|
- Preserve existing public method signatures and behavior when possible.
|
|
- If a breaking change is unavoidable, document it and provide a migration path.
|
|
- Add or update tests that cover backward compatibility expectations.
|
|
- Avoid exposing public `init` setters, positional records, `required` members, or other metadata that forces consumers onto newer C# language versions; validate older-consumer compatibility with tests when changing exported APIs.
|
|
|
|
### Public API Documentation Checklist
|
|
- When adding, removing, renaming, or changing public APIs, update the public docs in the same change.
|
|
- Check `docs/API.md` for new factory methods, options, interfaces, extension methods, enums, archive/reader/writer APIs, compression provider APIs, and examples.
|
|
- Check `docs/USAGE.md` when the API change affects recommended usage patterns or requires a new example.
|
|
- Check `docs/FORMATS.md` when the API change affects supported archive formats, compression methods, reader/archive/writer availability, or detection behavior.
|
|
- Check `README.md` when the change affects the top-level support summary, target frameworks, major capabilities, or user-facing feature list.
|
|
- For public API changes, verify examples compile conceptually against the actual public signatures and avoid documenting internal-only types.
|
|
|
|
### Stream Ownership and Position Checklist
|
|
- Verify `LeaveStreamOpen` behavior for externally owned streams.
|
|
- Validate behavior for both seekable and non-seekable streams.
|
|
- Ensure stream position assumptions are explicit and tested.
|
|
|
|
## Common Pitfalls
|
|
|
|
1. **Don't mix Archive and Reader APIs** - Archive needs seekable stream, Reader doesn't
|
|
2. **Don't mix sync and async open paths** - For async workflows use `OpenAsyncArchive`/`OpenAsyncReader`/`OpenAsyncWriter`, not `OpenArchive`/`OpenReader`/`OpenWriter`
|
|
3. **Solid archives (Rar, 7Zip)** - Use `ExtractAllEntries()` for best performance, not individual entry extraction
|
|
4. **Stream disposal** - Always set `LeaveStreamOpen` explicitly when needed (default is to close)
|
|
5. **Tar + non-seekable stream** - Must provide file size or it will throw
|
|
6. **Format detection** - Use `ReaderFactory.OpenReader()` / `ReaderFactory.OpenAsyncReader()` for auto-detection, test with actual archive files
|