commit 89feb571a2c88884eb8d932b3586ffb28215b3bb Author: github-actions[bot] Date: Tue Aug 18 03:30:05 2026 +0000 Deploy documentation from 1efdd4c091ff84523116dab22199f4c376e83102 1efdd4c091ff84523116dab22199f4c376e83102 diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 0000000..f6419eb --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: fba96becba878414a2a6dc74e49e11a4 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.doctrees/404.doctree b/.doctrees/404.doctree new file mode 100644 index 0000000..90f28a2 Binary files /dev/null and b/.doctrees/404.doctree differ diff --git a/.doctrees/README.doctree b/.doctrees/README.doctree new file mode 100644 index 0000000..60e09f6 Binary files /dev/null and b/.doctrees/README.doctree differ diff --git a/.doctrees/__intersphinx_cache__/python_objects.inv b/.doctrees/__intersphinx_cache__/python_objects.inv new file mode 100644 index 0000000..3cafd67 Binary files /dev/null and b/.doctrees/__intersphinx_cache__/python_objects.inv differ diff --git a/.doctrees/__intersphinx_cache__/zstandard_objects.inv b/.doctrees/__intersphinx_cache__/zstandard_objects.inv new file mode 100644 index 0000000..66b254e Binary files /dev/null and b/.doctrees/__intersphinx_cache__/zstandard_objects.inv differ diff --git a/.doctrees/api/cli.doctree b/.doctrees/api/cli.doctree new file mode 100644 index 0000000..4aa4a44 Binary files /dev/null and b/.doctrees/api/cli.doctree differ diff --git a/.doctrees/api/core.doctree b/.doctrees/api/core.doctree new file mode 100644 index 0000000..e8072a3 Binary files /dev/null and b/.doctrees/api/core.doctree differ diff --git a/.doctrees/api/exceptions.doctree b/.doctrees/api/exceptions.doctree new file mode 100644 index 0000000..508b382 Binary files /dev/null and b/.doctrees/api/exceptions.doctree differ diff --git a/.doctrees/api/index.doctree b/.doctrees/api/index.doctree new file mode 100644 index 0000000..9591dda Binary files /dev/null and b/.doctrees/api/index.doctree differ diff --git a/.doctrees/development.doctree b/.doctrees/development.doctree new file mode 100644 index 0000000..394c2dc Binary files /dev/null and b/.doctrees/development.doctree differ diff --git a/.doctrees/environment.pickle b/.doctrees/environment.pickle new file mode 100644 index 0000000..8d29cea Binary files /dev/null and b/.doctrees/environment.pickle differ diff --git a/.doctrees/examples.doctree b/.doctrees/examples.doctree new file mode 100644 index 0000000..7b0da77 Binary files /dev/null and b/.doctrees/examples.doctree differ diff --git a/.doctrees/index.doctree b/.doctrees/index.doctree new file mode 100644 index 0000000..b6dead0 Binary files /dev/null and b/.doctrees/index.doctree differ diff --git a/.doctrees/performance.doctree b/.doctrees/performance.doctree new file mode 100644 index 0000000..cc65c50 Binary files /dev/null and b/.doctrees/performance.doctree differ diff --git a/.doctrees/quickstart.doctree b/.doctrees/quickstart.doctree new file mode 100644 index 0000000..7bd0136 Binary files /dev/null and b/.doctrees/quickstart.doctree differ diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..1d0ec1c --- /dev/null +++ b/404.html @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + 404 - Page Not Found — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

404 - Page Not Found

+
+

Oops! The page you’re looking for doesn’t exist

+

The URL you requested could not be found in the tzst documentation. This might happen if:

+
    +
  • The page has been moved or renamed

  • +
  • You followed a broken link

  • +
  • There’s a typo in the URL

  • +
  • The page has been removed

  • +
+
+
+

Where would you like to go?

+ +
+

Quick Navigation

+ +
+
+
+

Search Documentation

+

Use the search box in the top navigation to find what you’re looking for, or browse through these sections:

+
+

Core Features

+ +
+
+

Examples & Tutorials

+ +
+
+
+

Additional Resources

+ +
+
+

Report an Issue

+

If you believe this is a broken link within our documentation, please report it on GitHub.

+
+

Need help? Check our Quick Start Guide guide or browse the Examples for common use cases.

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..9739d17 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +tzst.xi-xu.me diff --git a/README.html b/README.html new file mode 100644 index 0000000..f6600de --- /dev/null +++ b/README.html @@ -0,0 +1,378 @@ + + + + + + + + + Documentation — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Documentation

+

This directory contains the Sphinx documentation for the tzst library.

+
+

Setup

+
    +
  1. Install documentation dependencies:

    +
    pip install -r requirements.txt
    +
    +
    +
  2. +
  3. Install the tzst package in development mode (required for autodoc):

    +
    pip install -e ..
    +
    +
    +
  4. +
+
+
+

Building Documentation

+
+

Local Development

+

Build the documentation locally:

+
# On Unix/macOS
+make html
+
+# On Windows
+make.bat html
+
+
+

The built documentation will be in _build/html/. Open _build/html/index.html in your browser.

+
+ +
+

Other Build Targets

+
# Check documentation coverage
+make coverage
+
+# Check for broken links
+make linkcheck
+
+# Build PDF (requires LaTeX)
+make latexpdf
+
+# Clean build directory
+make clean
+
+
+
+
+
+

Documentation Structure

+
    +
  • index.md - Main documentation homepage

  • +
  • quickstart.md - Quick start guide for new users

  • +
  • examples.md - Practical examples and use cases

  • +
  • api/ - API reference documentation

    +
      +
    • index.md - API overview

    • +
    • core.md - Core functionality documentation

    • +
    • cli.md - CLI documentation

    • +
    • exceptions.md - Exception classes documentation

    • +
    +
  • +
+
+
+

Writing Documentation

+
+

Markdown vs reStructuredText

+

This documentation uses MyST parser, which allows you to write in Markdown with some reStructuredText features. You can use either .md or .rst files.

+
+
+

Adding New Pages

+
    +
  1. Create a new .md file in the appropriate directory

  2. +
  3. Add it to the relevant toctree directive in the parent index file

  4. +
  5. Use proper Markdown headers and cross-references

  6. +
+
+
+

API Documentation

+

API documentation is automatically generated from docstrings using Sphinx autodoc. To document a new module:

+
    +
  1. Add the module to the appropriate API file (e.g., api/core.md)

  2. +
  3. Use autodoc directives like automodule, autoclass, autofunction

  4. +
+
+
+

Code Examples

+

Use fenced code blocks with language specification:

+
```python
+from tzst import TzstArchive
+
+with TzstArchive("example.tzst", "w") as archive:
+    archive.add("file.txt")
+```
+
+
+
+
+

Cross-References

+

Link to other documentation pages:

+
See the {doc}`quickstart` guide for more information.
+
+
+

Link to API documentation:

+
Use the {class}`tzst.TzstArchive` class.
+
+
+
+
+
+

Automated Deployment

+

Documentation is automatically built and deployed to GitHub Pages when changes are pushed to the main branch. The workflow is defined in .github/workflows/publish_docs.yml.

+
+

Local Testing of Deployment

+

To test the deployment process locally:

+
    +
  1. Build the documentation: make html

  2. +
  3. Serve the built files: python -m http.server 8000 -d _build/html

  4. +
  5. Visit http://localhost:8000

  6. +
+
+
+
+

Troubleshooting

+
+

Import Errors

+

If you get import errors when building documentation:

+
    +
  1. Make sure the tzst package is installed: pip install -e ..

  2. +
  3. Check that all dependencies are installed: pip install -r requirements.txt

  4. +
  5. Verify your Python path includes the src directory

  6. +
+
+
+

Theme Issues

+

If the RTD theme isn’t working:

+
    +
  1. Install the theme: pip install sphinx-rtd-theme

  2. +
  3. Check that it’s listed in requirements.txt

  4. +
  5. Verify the theme configuration in conf.py

  6. +
+
+
+

Build Warnings

+

Address all Sphinx warnings to ensure high-quality documentation:

+
    +
  • Fix broken cross-references

  • +
  • Add missing docstrings

  • +
  • Resolve autodoc import issues

  • +
  • Fix malformed markup

  • +
+
+
+
+

Contributing

+

When contributing to documentation:

+
    +
  1. Follow the existing style and structure

  2. +
  3. Test your changes locally before submitting

  4. +
  5. Add examples for new features

  6. +
  7. Update the changelog if appropriate

  8. +
  9. Ensure all links work correctly

  10. +
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 0000000..4424085 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,196 @@ + + + + + + + + Overview: module code — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

All modules for which code is available

+ + +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/tzst/cli.html b/_modules/tzst/cli.html new file mode 100644 index 0000000..8e50a53 --- /dev/null +++ b/_modules/tzst/cli.html @@ -0,0 +1,1655 @@ + + + + + + + + tzst.cli — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for tzst.cli

+"""Command-line interface for tzst."""
+
+import argparse
+import json
+import sys
+from pathlib import Path
+from typing import Any, Literal, cast
+
+from . import __version__
+from .core import (
+    ConflictResolution,
+    create_archive,
+    extract_archive,
+    list_archive,
+    test_archive,
+)
+from .exceptions import TzstArchiveError, TzstDecompressionError
+
+
+def _normalize_archive_path(archive_path: Path) -> Path:
+    """Normalize archive path by ensuring correct extension.
+
+    This exactly mirrors the logic in core.py's create_archive function to show
+    the correct final path in CLI output.
+
+    Args:
+        archive_path: Input archive path
+
+    Returns:
+        Path: Normalized path with correct extension
+    """
+    # Convert to Path if it's not already
+    archive_path = Path(archive_path)
+
+    # Ensure archive has correct extension - this logic exactly matches core.py
+    if archive_path.suffix.lower() not in [".tzst", ".zst"]:
+        if archive_path.suffix.lower() == ".tar":
+            archive_path = archive_path.with_suffix(".tar.zst")
+        else:
+            archive_path = archive_path.with_suffix(archive_path.suffix + ".tzst")
+
+    return archive_path
+
+
+def _interactive_conflict_callback(target_path: Path) -> ConflictResolution:
+    """Interactive callback for handling file conflicts in CLI.
+
+    Args:
+        target_path: Path of the conflicting file
+
+    Returns:
+        ConflictResolution: User's choice for handling the conflict
+    """
+    print(f"\nFile already exists: {target_path}")
+    print("Choose an action:")
+    print("  [R] Replace")
+    print("  [N] Do not replace (skip)")
+    print("  [A] Replace all")
+    print("  [S] Skip all")
+    print("  [U] Auto-rename all")
+    print("  [X] Exit")
+
+    while True:
+        try:
+            choice = input("Enter choice [R/N/A/S/U/X]: ").strip().upper()
+
+            if choice == "R":
+                return ConflictResolution.REPLACE
+            elif choice == "N":
+                return ConflictResolution.SKIP
+            elif choice == "A":
+                return ConflictResolution.REPLACE_ALL
+            elif choice == "S":
+                return ConflictResolution.SKIP_ALL
+            elif choice == "U":
+                return ConflictResolution.AUTO_RENAME_ALL
+            elif choice == "X":
+                return ConflictResolution.EXIT
+            else:
+                print("Invalid choice. Please enter R, N, A, S, U, or X.")
+        except (EOFError, KeyboardInterrupt):
+            print("\nOperation cancelled by user")
+            return ConflictResolution.EXIT
+
+
+
+
+
+
+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
+
+
+
+[docs] +def format_size(size: int) -> str: + """Format file size in human-readable format. + + Converts byte values to human-readable format using standard units + (B, KB, MB, GB, TB, PB) with appropriate decimal places. + + Args: + size (int): Size in bytes to format + + Returns: + str: Formatted size string with units (e.g., "1.5 KB", "2.3 GB") + + Examples: + >>> format_size(1024) + ' 1.0 KB' + >>> format_size(1536) + ' 1.5 KB' + >>> format_size(2048576) + ' 2.0 MB' + """ + size_float = float(size) + for unit in ["B", "KB", "MB", "GB", "TB"]: + if size_float < 1024.0: + return f"{size_float:6.1f} {unit}" + size_float /= 1024.0 + return f"{size_float:6.1f} PB"
+ + + +
+[docs] +def validate_compression_level(value: str) -> int: + """Validate and return compression level. + + Args: + value: String value from command line + + Returns: + int: Valid compression level (1-22) + + Raises: + argparse.ArgumentTypeError: If value is not a valid compression level + """ + try: + level = int(value) + if not 1 <= level <= 22: + raise argparse.ArgumentTypeError( + f"Invalid compression level: {level}. Must be between 1 and 22." + ) + return level + except ValueError: + raise argparse.ArgumentTypeError( + f"Invalid compression level: '{value}'. " + f"Must be an integer between 1 and 22." + ) from None
+ + + +def _process_file_paths(file_args: list[str]) -> list[Path]: + """Process file arguments into resolved Path objects. + + Args: + file_args: List of file path strings from command line + + Returns: + list[Path]: List of resolved Path objects + """ + files: list[Path] = [] + for file_arg in file_args: + file_path = Path(file_arg).resolve() + files.append(file_path) + return files + + +def _validate_files(files: list[Path]) -> list[Path]: + """Validate that files exist and are accessible. + + Args: + files: List of Path objects to validate + + Returns: + list[Path]: List of missing files (empty if all files exist) + + Raises: + OSError: If a file cannot be accessed due to permissions or invalid characters + """ + missing_files = [] + for f in files: + try: + if not f.exists(): + missing_files.append(f) + except OSError as e: + # Handle path issues (invalid characters, permissions, etc.) + print(f"Error: Cannot access file '{f}' - {e}", file=sys.stderr) + raise + return missing_files + + +def _extract_add_params(args) -> tuple[int, bool]: + """Extract compression parameters from arguments. + + Args: + args: Parsed command line arguments + + Returns: + tuple[int, bool]: (compression_level, use_temp_file) + """ + compression_level = getattr(args, "compression_level", 3) + use_temp_file = not getattr(args, "no_atomic", False) + return compression_level, use_temp_file + + +def _prepare_archive_creation(args) -> tuple[Path, list[Path], int, bool] | int: + """Prepare and validate inputs for archive creation. + + Args: + args: Parsed command line arguments + + Returns: + tuple or int: Either (archive_path, files, compression_level, use_temp_file) + or error code if validation fails + """ + archive_path = Path(args.archive) + files = _process_file_paths(args.files) + + # Validate files + missing_files = _validate_files(files) + if missing_files: + return _emit_error( + args, + f"Error: Files not found - {', '.join(map(str, missing_files))}", + error_type="files_not_found", + details={"missing_files": [str(path) for path in missing_files]}, + ) + + compression_level, use_temp_file = _extract_add_params(args) + return archive_path, files, compression_level, use_temp_file + + +def _execute_archive_creation( + args, + archive_path: Path, + files: list[Path], + compression_level: int, + use_temp_file: bool, +) -> int: + """Execute the archive creation process. + + Args: + archive_path: Path where archive will be created + files: List of files to add to archive + compression_level: Compression level to use + use_temp_file: Whether to use atomic file operations + + Returns: + int: Exit code (0 for success, non-zero for failure) + """ + # Normalize archive path to show the correct final filename + normalized_archive_path = _normalize_archive_path(archive_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) + + 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(command_args, func, *args, **kwargs) -> int: + """Handle exceptions during archive creation. + + Args: + func: Function to execute + *args: Positional arguments for func + **kwargs: Keyword arguments for func + + Returns: + int: Exit code based on exception type + """ + try: + return func(*args, **kwargs) + except OSError: + return 1 # Error already printed in _validate_files + except ValueError as e: + return _emit_error( + command_args, + f"Error: Invalid parameter - {e}", + error_type="invalid_parameter", + ) + except TzstArchiveError as e: + return _emit_error( + command_args, + f"Error: Archive operation failed - {e}", + error_type="archive_operation_failed", + ) + except KeyboardInterrupt: + 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 + except Exception as e: + return _emit_error( + command_args, + f"Error: Failed to create archive - {e}", + error_type="create_failed", + ) + + +
+[docs] +def cmd_add(args) -> int: + """Command handler for creating/adding to archives. + + Processes the 'add', 'create', or 'a' CLI commands to create new tzst archives + with the specified files and directories. Uses atomic file operations by + default to ensure data integrity. + + Args: + args: Parsed command line arguments containing: + - archive (str): Path to the archive file to create + - files (list[str]): List of files/directories to add + - compression_level (int, optional): Compression level 1-22 + - no_atomic (bool, optional): Disable atomic file operations + + Returns: + int: Exit code (0 for success, non-zero for failure) + - 0: Success + - 1: File not found, invalid parameters, or archive operation failed + - 130: Operation interrupted by user (Ctrl+C) + + Note: + This function uses atomic file operations by default, creating the + archive in a temporary file first, then atomically moving it to the + final location to prevent incomplete archives. + + See Also: + :func:`tzst.create_archive`: The underlying function for archive creation + :meth:`TzstArchive.add`: The core method for adding files to archives + """ + + def _create_archive_workflow(): + preparation_result = _prepare_archive_creation(args) + if isinstance(preparation_result, int): + return preparation_result + + archive_path, files, compression_level, use_temp_file = preparation_result + return _execute_archive_creation( + args, archive_path, files, compression_level, use_temp_file + ) + + return _handle_archive_creation_exceptions(args, _create_archive_workflow)
+ + + +
+[docs] +def cmd_extract_full(args) -> int: + """Command handler for extracting archives with full directory structure. + + Processes the 'extract' or 'x' CLI commands to extract files from tzst + archives while preserving the original directory structure. + + Args: + args: Parsed command line arguments containing: + - archive (str): Path to the archive file to extract + - output (str, optional): Output directory path + - files (list[str], optional): Specific files to extract + - streaming (bool, optional): Use streaming mode for large archives + - filter (str, optional): Security filter ('data', 'tar', 'fully_trusted') + + Returns: + int: Exit code (0 for success, non-zero for failure) + - 0: Success + - 1: File not found, decompression failed, or archive operation failed + - 130: Operation interrupted by user (Ctrl+C) + + Note: + Uses the 'data' security filter by default for safe extraction from + untrusted sources. Streaming mode is recommended for archives > 100MB. + + See Also: + :func:`tzst.extract_archive`: The underlying function for extraction + :meth:`TzstArchive.extract`: The core method for extracting from archives + :func:`cmd_extract_flat`: For flat extraction without directory structure + """ + try: + archive_path = Path(args.archive) + if not archive_path.exists(): + 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 + streaming = getattr(args, "streaming", False) + filter_type = cast( + Literal["data", "tar", "fully_trusted"], getattr(args, "filter", "data") + ) + + # Handle conflict resolution parameters + conflict_resolution_str = getattr(args, "conflict_resolution", "ask") + interactive_flag = getattr(args, "interactive", False) + + # If --interactive is specified, use "ask" regardless of --conflict-resolution + if interactive_flag: + conflict_resolution_str = "ask" + + # 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 + + 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, + output_dir, + members, + flatten=False, + streaming=streaming, + filter=filter_type, + conflict_resolution=conflict_resolution, + interactive_callback=interactive_callback, + ) + + 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: + return _emit_error( + args, + f"Error: File not found - {e}", + error_type="file_not_found", + ) + except TzstDecompressionError as e: + return _emit_error( + args, + f"Error: Archive decompression failed - {e}", + error_type="decompression_failed", + ) + except TzstArchiveError as e: + return _emit_error( + args, + f"Error: Archive operation failed - {e}", + error_type="archive_operation_failed", + ) + except KeyboardInterrupt: + return _emit_error( + args, + "Operation interrupted by user", + error_type="interrupted", + exit_code=130, + ) + except Exception as e: + return _emit_error( + args, + f"Error: Failed to extract archive - {e}", + error_type="extract_failed", + )
+ + + +
+[docs] +def cmd_extract_flat(args) -> int: + """Command handler for flat extraction without directory structure. + + Processes the 'extract-flat' or 'e' CLI commands to extract files from + tzst archives without preserving directory structure (all files extracted + to a single directory). + + Args: + args: Parsed command line arguments containing: + - archive (str): Path to the archive file to extract + - output (str, optional): Output directory path + - files (list[str], optional): Specific files to extract + - streaming (bool, optional): Use streaming mode for large archives + - filter (str, optional): Security filter ('data', 'tar', 'fully_trusted') + + Returns: + int: Exit code (0 for success, non-zero for failure) + - 0: Success + - 1: File not found, decompression failed, or archive operation failed + - 130: Operation interrupted by user (Ctrl+C) + + Warning: + Flat extraction may cause filename conflicts if multiple files have + the same name but are in different directories within the archive. + + See Also: + :func:`tzst.extract_archive`: The underlying function for extraction + :meth:`TzstArchive.extract`: The core method for extracting from archives + :func:`cmd_extract_full`: For extraction with directory structure + """ + try: + archive_path = Path(args.archive) + if not archive_path.exists(): + 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 + streaming = getattr(args, "streaming", False) + filter_type = cast( + Literal["data", "tar", "fully_trusted"], getattr(args, "filter", "data") + ) + + # Handle conflict resolution parameters + conflict_resolution_str = getattr(args, "conflict_resolution", "ask") + interactive_flag = getattr(args, "interactive", False) + + # If --interactive is specified, use "ask" regardless of --conflict-resolution + if interactive_flag: + conflict_resolution_str = "ask" + + # 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 + + 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, + output_dir, + members, + flatten=True, + streaming=streaming, + filter=filter_type, + conflict_resolution=conflict_resolution, + interactive_callback=interactive_callback, + ) + + 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: + return _emit_error( + args, + f"Error: File not found - {e}", + error_type="file_not_found", + ) + except TzstDecompressionError as e: + return _emit_error( + args, + f"Error: Archive decompression failed - {e}", + error_type="decompression_failed", + ) + except TzstArchiveError as e: + return _emit_error( + args, + f"Error: Archive operation failed - {e}", + error_type="archive_operation_failed", + ) + except Exception as e: + return _emit_error( + args, + f"Error: Failed to extract archive - {e}", + error_type="extract_failed", + )
+ + + +def _print_verbose_listing(contents: list) -> None: + """Print detailed listing of archive contents. + + Args: + contents: List of archive items with metadata + """ + print(f"{'Mode':<10} {'Size':<10} {'Modified':<20} {'Name'}") + print("-" * 60) + for item in contents: + mode_str = oct(item.get("mode", 0))[-4:] if item.get("mode") else "----" + size_str = format_size(item["size"]) if item["is_file"] else "<DIR>" + mtime_str = item.get("mtime_str", "")[:19] if item.get("mtime_str") else "" + print(f"{mode_str:<10} {size_str:<10} {mtime_str:<20} {item['name']}") + + +def _print_simple_listing(contents: list) -> None: + """Print simple listing of archive contents with summary. + + Args: + contents: List of archive items + """ + for item in contents: + print(item["name"]) + + print() + summary = _summarize_listing(contents) + total_msg = ( + f"Total: {summary['files']} files, {summary['directories']} directories, " + f"{summary['total_size_human']}" + ) + print(total_msg) + + +
+[docs] +def cmd_list(args) -> int: + """Command handler for listing archive contents. + + Processes the 'list' or 'l' CLI commands to display the contents of tzst + archives. Supports both simple and verbose listing modes. + + Args: + args: Parsed command line arguments containing: + - archive (str): Path to the archive file to list + - verbose (bool, optional): Show detailed file information + - streaming (bool, optional): Use streaming mode for large archives + + Returns: + int: Exit code (0 for success, non-zero for failure) + - 0: Success + - 1: File not found, decompression failed, or archive operation failed + - 130: Operation interrupted by user (Ctrl+C) + + Note: + Verbose mode displays file permissions, sizes, modification times, + and other metadata. Streaming mode is recommended for archives > 100MB. + + See Also: + :func:`tzst.list_archive`: The underlying function for listing contents + :meth:`TzstArchive.list`: The core method for listing archive contents + """ + try: + archive_path = Path(args.archive) + if not archive_path.exists(): + 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) + + 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 _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) + + return 0 + + except FileNotFoundError as e: + return _emit_error( + args, + f"Error: File not found - {e}", + error_type="file_not_found", + ) + except TzstDecompressionError as e: + return _emit_error( + args, + f"Error: Archive decompression failed - {e}", + error_type="decompression_failed", + ) + except TzstArchiveError as e: + return _emit_error( + args, + f"Error: Archive operation failed - {e}", + error_type="archive_operation_failed", + ) + except KeyboardInterrupt: + return _emit_error( + args, + "Operation interrupted by user", + error_type="interrupted", + exit_code=130, + ) + except Exception as e: + return _emit_error( + args, + f"Error: Failed to list archive - {e}", + error_type="list_failed", + )
+ + + +
+[docs] +def cmd_test(args) -> int: + """Command handler for testing archive integrity. + + Processes the 'test' or 't' CLI commands to verify the integrity of tzst + archives by attempting to read all files and checking for corruption. + + Args: + args: Parsed command line arguments containing: + - archive (str): Path to the archive file to test + - streaming (bool, optional): Use streaming mode for large archives + + Returns: + int: Exit code (0 for success, non-zero for failure) + - 0: Archive passed integrity test + - 1: Archive failed integrity test, file not found, or operation failed + - 130: Operation interrupted by user (Ctrl+C) + + Note: + This command verifies that the archive can be read and all files + can be decompressed without errors. Streaming mode is recommended + for archives > 100MB to reduce memory usage. + + See Also: + :func:`tzst.test_archive`: The underlying function for integrity testing + :meth:`TzstArchive.test`: The core method for testing archive integrity + """ + try: + archive_path = Path(args.archive) + if not archive_path.exists(): + 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) + + if not _wants_json_output(args): + print(f"Testing archive: {archive_path}") + if streaming: + print("Using streaming mode (memory efficient)") + + 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: + 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: + return _emit_error( + args, + f"Error: File not found - {e}", + error_type="file_not_found", + ) + except TzstDecompressionError as e: + return _emit_error( + args, + f"Error: Archive decompression failed - {e}", + error_type="decompression_failed", + ) + except TzstArchiveError as e: + return _emit_error( + args, + f"Error: Archive operation failed - {e}", + error_type="archive_operation_failed", + ) + except KeyboardInterrupt: + return _emit_error( + args, + "Operation interrupted by user", + error_type="interrupted", + exit_code=130, + ) + except Exception as e: + return _emit_error( + args, + f"Error: Failed to test archive - {e}", + error_type="test_failed", + )
+ + + +
+[docs] +def cmd_version(args) -> int: + """Command handler for version display. + + Returns: + int: Exit code (always 0) + """ + 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
+ + + +
+[docs] +def create_parser() -> argparse.ArgumentParser: + """Create and configure the command-line argument parser. + + Sets up the argparse ArgumentParser with all subcommands and their + respective arguments for the tzst CLI interface. Includes comprehensive + help text and command reference documentation. + + Returns: + argparse.ArgumentParser: Configured parser ready for argument parsing + + Note: + The parser is configured with RawDescriptionHelpFormatter to preserve + formatting in the epilog help text, and includes detailed command + reference and security notes. + + Commands Created: + - a, add, create: Archive creation with compression levels + - x, extract: Full extraction with directory structure + - e, extract-flat: Flat extraction without directories + - l, list: Archive content listing + - t, test: Archive integrity testing + + See Also: + :func:`main`: The main entry point that uses this parser + """ + epilog = """ +command reference: + archive: + a, add, create tzst a archive.tzst files... [-l LEVEL] [--no-atomic] + + extract: + x, extract tzst x archive.tzst [files...] [-o DIR] [--streaming] [--filter FILTER] + e, extract-flat tzst e archive.tzst [files...] [-o DIR] [--streaming] [--filter FILTER] + + manage: + l, list tzst l archive.tzst [-v] [--streaming] + t, test tzst t archive.tzst [--streaming] + +arguments: + -l, --level LEVEL compression level (1-22, default: 3) + -o, --output DIR output directory (default: current directory) + -v, --verbose show detailed information + --streaming use streaming mode for memory efficiency with large archives + --filter FILTER security filter for extraction: data (safest, default), tar, fully_trusted + --no-atomic disable atomic file operations (not recommended) + +security note: + always use --filter=data (default) when extracting archives from untrusted sources + never use --filter=fully_trusted unless you completely trust the archive source + +documentation: + https://tzst.xi-xu.me +""" + parser = argparse.ArgumentParser( + prog="tzst", + epilog=epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + add_help=True, + ) + 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( + dest="command", title="commands", metavar="COMMAND" + ) + + # Add/Create command + parser_add = subparsers.add_parser( + "a", aliases=["add", "create"], help="add files to archive" + ) + parser_add.add_argument("archive", help="archive file path") + parser_add.add_argument("files", nargs="+", help="files/directories to add") + parser_add.add_argument( + "-c", + "-l", + "--level", + dest="compression_level", + type=validate_compression_level, + default=3, + metavar="LEVEL", + help="compression level (1-22, default: 3)", + ) + parser_add.add_argument( + "--no-atomic", + action="store_true", + help=( + "Disable atomic file operations (not recommended - creates archive " + "directly without temporary file)" + ), + ) + parser_add.set_defaults(func=cmd_add) + + # Extract with full paths command + parser_extract = subparsers.add_parser( + "x", aliases=["extract"], help="eXtract files with full paths" + ) + parser_extract.add_argument("archive", help="archive file path") + parser_extract.add_argument("files", nargs="*", help="specific files to extract") + parser_extract.add_argument( + "-o", "--output", help="output directory (default: current directory)" + ) + parser_extract.add_argument( + "--streaming", + action="store_true", + help="use streaming mode for memory efficiency with large archives", + ) + parser_extract.add_argument( + "--filter", + choices=["data", "tar", "fully_trusted"], + default="data", + help=( + "Extraction filter for security (default: data). 'data' is safest " + "for untrusted archives, 'tar' honors most tar features, " + "'fully_trusted' honors all metadata" + ), + ) + parser_extract.add_argument( + "--conflict-resolution", + choices=[ + "replace", + "skip", + "replace_all", + "skip_all", + "auto_rename", + "auto_rename_all", + "ask", + ], + default="ask", + help=( + "How to handle file conflicts during extraction (default: ask). " + "'ask' prompts for each conflict, 'replace' overwrites existing files, " + "'skip' skips existing files, 'auto_rename' creates new names. " + "Adding '_all' applies the action to all subsequent conflicts." + ), + ) + parser_extract.set_defaults(func=cmd_extract_full) + + # Extract flat command + parser_extract_flat = subparsers.add_parser( + "e", + aliases=["extract-flat"], + help="extract files from archive (without using directory names)", + ) + parser_extract_flat.add_argument("archive", help="archive file path") + parser_extract_flat.add_argument( + "files", nargs="*", help="specific files to extract" + ) + parser_extract_flat.add_argument( + "-o", "--output", help="output directory (default: current directory)" + ) + parser_extract_flat.add_argument( + "--streaming", + action="store_true", + help="use streaming mode for memory efficiency with large archives", + ) + parser_extract_flat.add_argument( + "--filter", + choices=["data", "tar", "fully_trusted"], + default="data", + help=( + "Extraction filter for security (default: data). 'data' is safest " + "for untrusted archives, 'tar' honors most tar features, " + "'fully_trusted' honors all metadata" + ), + ) + parser_extract_flat.add_argument( + "--conflict-resolution", + choices=[ + "replace", + "skip", + "replace_all", + "skip_all", + "auto_rename", + "auto_rename_all", + "ask", + ], + default="ask", + help=( + "How to handle file conflicts during extraction (default: ask). " + "'ask' prompts for each conflict, 'replace' overwrites existing files, " + "'skip' skips existing files, 'auto_rename' creates new names. " + "Adding '_all' applies the action to all subsequent conflicts." + ), + ) + parser_extract_flat.set_defaults(func=cmd_extract_flat) + + # List command + parser_list = subparsers.add_parser( + "l", aliases=["list"], help="list contents of archive" + ) + parser_list.add_argument("archive", help="archive file path") + parser_list.add_argument( + "-v", "--verbose", action="store_true", help="show detailed information" + ) + parser_list.add_argument( + "--streaming", + action="store_true", + help="use streaming mode for memory efficiency with large archives", + ) + parser_list.set_defaults(func=cmd_list) + + # Test command + parser_test = subparsers.add_parser( + "t", aliases=["test"], help="test integrity of archive" + ) + parser_test.add_argument("archive", help="archive file path") + parser_test.add_argument( + "--streaming", + action="store_true", + help="use streaming mode for memory efficiency with large archives", + ) + parser_test.set_defaults(func=cmd_test) + + return parser
+ + + +def _validate_compression_level_in_argv(argv: list[str]) -> bool: + """Check for compression level validation errors in argv. + + Args: + argv: Command line arguments + + Returns: + bool: True if an error was found and handled, False otherwise + """ + if "-c" not in argv and "-l" not in argv and "--level" not in argv: + return False + + try: + level_index = -1 + if "-c" in argv: + level_index = argv.index("-c") + elif "-l" in argv: + level_index = argv.index("-l") + else: + level_index = argv.index("--level") + if level_index + 1 < len(argv): + level_value = argv[level_index + 1] + try: + level = int(level_value) + if not 1 <= level <= 22: + print( + f"Invalid compression level: {level}. " + f"Must be between 1 and 22.", + file=sys.stderr, + ) + return True + except ValueError: + print( + f"Invalid compression level: '{level_value}'. " + f"Must be an integer between 1 and 22.", + file=sys.stderr, + ) + return True + except (ValueError, IndexError): + pass + return False + + +def _validate_filter_in_argv(argv: list[str]) -> bool: + """Check for filter validation errors in argv. + + Args: + argv: Command line arguments + + Returns: + bool: True if an error was found and handled, False otherwise + """ + if "--filter" not in argv: + return False + + try: + filter_index = argv.index("--filter") + if filter_index + 1 < len(argv): + filter_value = argv[filter_index + 1] + valid_filters = ["data", "tar", "fully_trusted"] + if filter_value not in valid_filters: + print( + f"Invalid filter specified: {filter_value}. " + f"Must be one of: {', '.join(valid_filters)}", + file=sys.stderr, + ) + return True + except (ValueError, IndexError): + pass + return False + + +def _validate_command_in_argv(argv: list[str]) -> bool: + """Check for invalid command errors in argv. + + Args: + argv: Command line arguments + + Returns: + bool: True if an invalid command was found and handled, False otherwise + """ + if not argv: + return False + + # Valid commands and their aliases + valid_commands = { + "a", + "add", + "create", + "x", + "extract", + "e", + "extract-flat", + "l", + "list", + "t", + "test", + } + + # Find the first argument that's not a flag (doesn't start with -) + for arg in argv: + if not arg.startswith("-"): + if arg not in valid_commands: + print( + f"Invalid command: '{arg}'. " + f"Valid commands are: {', '.join(sorted(valid_commands))}", + file=sys.stderr, + ) + return True + break + + return False + + +def _is_extreme_compression_level_in_argv(argv: list[str]) -> bool: + """Check for extreme compression level values that warrant special handling. + + Args: + argv: Command line arguments + + Returns: + bool: True if an extreme compression level value is found, False otherwise + """ + if "-c" not in argv and "-l" not in argv and "--level" not in argv: + return False + + try: + level_index = -1 + if "-c" in argv: + level_index = argv.index("-c") + elif "-l" in argv: + level_index = argv.index("-l") + else: + level_index = argv.index("--level") + if level_index + 1 < len(argv): + level_value = argv[level_index + 1] + try: + level = int(level_value) + # Only consider extreme values (>=1000) for special handling + return level >= 1000 + except ValueError: + pass + except (ValueError, IndexError): + pass + return False + + +def _handle_parsing_errors(e: SystemExit, argv: list[str] | None) -> int: + """Handle SystemExit exceptions from argument parsing. + + Args: + e: SystemExit exception from argparse + argv: Command line arguments, if any + + Returns: + int: Appropriate exit code + """ # Help was requested + if e.code == 0: + return 0 + elif e.code == 2 and argv: + # For now, keep standard argparse behavior (exit code 2) + # Future versions may convert specific validation errors to exit code 1 + pass + + # Return the original exit code for other cases + return int(e.code) if e.code is not None else 1 + + +def _parse_arguments(parser, argv: list[str] | None): + """Parse command line arguments with error handling. + + Args: + parser: The argument parser + argv: Command line arguments + + Returns: + tuple: (args, error_code) where error_code is None for success + """ + try: + args = parser.parse_args(argv) + return args, None + except SystemExit as e: + return None, _handle_parsing_errors(e, argv) + + +def _execute_command(args, parser) -> int: + """Execute the parsed command. + + Args: + args: Parsed command line arguments + parser: The argument parser for help display + + Returns: + int: Exit code from command execution + """ + # Handle --version flag + if hasattr(args, "version") and args.version: + return cmd_version(args) + + if not hasattr(args, "func"): + parser.print_help() + return 1 + + return args.func(args) + + +
+[docs] +def main(argv: list[str] | None = None) -> int: + """Main entry point for the tzst command-line interface. + + Processes command-line arguments and dispatches to appropriate command + handlers. Displays the version banner and provides error handling for + the overall CLI execution. + + Args: + argv (list[str] | None, optional): Command line arguments to parse. + If None, uses sys.argv. Defaults to None. + + Returns: + int: Exit code for the program + - 0: Success + - 1: Invalid compression level, filter, or command error + - 2: Argument parsing error (help, unknown options) + - Other codes: Specific to individual command handlers + + Note: + This function serves as the console script entry point defined in + pyproject.toml. It displays the version banner before executing + any commands. + + See Also: + :func:`create_parser`: Creates the argument parser used by this function + """ + if _should_print_banner(argv): + print_banner() + + parser = create_parser() + args, error_code = _parse_arguments(parser, argv) + + if error_code is not None: + return error_code + + return _execute_command(args, parser)
+ + + +if __name__ == "__main__": + sys.exit(main()) +
+ +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/tzst/core.html b/_modules/tzst/core.html new file mode 100644 index 0000000..9515063 --- /dev/null +++ b/_modules/tzst/core.html @@ -0,0 +1,1270 @@ + + + + + + + + tzst.core — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for tzst.core

+"""Core functionality for tzst archives."""
+
+import io
+import os
+import tarfile
+import tempfile
+import time
+from collections.abc import Callable, Sequence
+from enum import Enum
+from pathlib import Path
+from typing import BinaryIO
+
+import zstandard as zstd
+
+from .exceptions import TzstArchiveError, TzstDecompressionError
+
+
+
+[docs] +class ConflictResolution(Enum): + """Enum for conflict resolution strategies.""" + + REPLACE = "replace" + SKIP = "skip" + REPLACE_ALL = "replace_all" + SKIP_ALL = "skip_all" + AUTO_RENAME = "auto_rename" + AUTO_RENAME_ALL = "auto_rename_all" + EXIT = "exit" + ASK = "ask"
+ + + +
+[docs] +class ConflictResolutionState: + """State management for conflict resolution during extraction.""" + +
+[docs] + def __init__(self, initial_resolution: ConflictResolution | None = None): + self.continue_extraction = True + self.global_resolution = initial_resolution + # If initial resolution is EXIT, set continue_extraction to False + if initial_resolution == ConflictResolution.EXIT: + self.continue_extraction = False
+ + + @property + def current_resolution(self) -> ConflictResolution | None: + """Get the current resolution state.""" + return self.global_resolution + +
+[docs] + def should_continue(self) -> bool: + """Check if extraction should continue.""" + return self.continue_extraction
+ + + @property + def apply_to_all(self) -> bool: + """Check if the current resolution applies to all future conflicts.""" + return self.global_resolution in ( + ConflictResolution.REPLACE_ALL, + ConflictResolution.SKIP_ALL, + ConflictResolution.AUTO_RENAME_ALL, + ) + +
+[docs] + def update_resolution(self, resolution: ConflictResolution) -> None: + """Update the global resolution state.""" + if resolution == ConflictResolution.EXIT: + self.continue_extraction = False + self.global_resolution = resolution + elif resolution in ( + ConflictResolution.REPLACE_ALL, + ConflictResolution.SKIP_ALL, + ConflictResolution.AUTO_RENAME_ALL, + ): + self.global_resolution = resolution
+
+ + + +def _get_unique_filename(file_path: Path) -> Path: + """Generate a unique filename by appending a number if the file exists.""" + if not file_path.exists(): + return file_path + + parent = file_path.parent + stem = file_path.stem + suffix = file_path.suffix + + counter = 1 + while True: + new_name = f"{stem}_{counter}{suffix}" + new_path = parent / new_name + if not new_path.exists(): + return new_path + counter += 1 + + +def _move_file_cross_platform(src: Path, dst: Path) -> None: + """Move a file from src to dst, handling cross-drive moves on Windows.""" + try: + # Try the fast rename operation first + src.rename(dst) + except OSError as e: + # On Windows, rename fails across drives with error 17 + # Fall back to copy + delete for cross-drive moves + import shutil + + try: + shutil.copy2(src, dst) + src.unlink() + except Exception: + # If copy also fails, re-raise the original rename error + raise e from None + + +def _handle_file_conflict( + target_path: Path, + resolution: ConflictResolution | str, + interactive_callback: Callable[[Path], ConflictResolution] | None = None, +) -> tuple[ConflictResolution, Path | None]: + """ + Handle file conflicts during extraction. + + Args: + target_path: The path where a conflict occurred + resolution: The conflict resolution strategy + interactive_callback: Optional callback for interactive resolution + + Returns: + Tuple of (actual_resolution, final_path)""" + # Convert string resolution to enum if needed + if isinstance(resolution, str): + try: + resolution = ConflictResolution(resolution) + except ValueError: + # Invalid string, fallback to ASK for interactive handling + resolution = ConflictResolution.ASK + + if resolution == ConflictResolution.ASK: + if interactive_callback: + resolution = interactive_callback(target_path) + else: + # No callback provided, default to REPLACE for consistency with tests + resolution = ConflictResolution.REPLACE + + if resolution in (ConflictResolution.REPLACE, ConflictResolution.REPLACE_ALL): + return resolution, target_path + elif resolution in (ConflictResolution.SKIP, ConflictResolution.SKIP_ALL): + return resolution, None + elif resolution in ( + ConflictResolution.AUTO_RENAME, + ConflictResolution.AUTO_RENAME_ALL, + ): + unique_path = _get_unique_filename(target_path) + return resolution, unique_path + elif resolution == ConflictResolution.EXIT: + return resolution, None + else: + # Unknown resolution, default to REPLACE for robustness + return ConflictResolution.REPLACE, target_path + + +
+[docs] +class TzstArchive: + """A class for handling .tzst/.tar.zst archives.""" + +
+[docs] + def __init__( + self, + filename: str | Path, + mode: str = "r", + compression_level: int = 3, + streaming: bool = False, + ): + """ + Initialize a TzstArchive. + + Args: + filename: Path to the archive file + mode: Open mode ('r', 'w', 'a') + compression_level: Zstandard compression level (1-22) + streaming: If True, use streaming mode for reading (reduces memory usage + for very large archives but may limit some tarfile operations + that require seeking. Recommended for archives > 100MB) + """ + self.filename = Path(filename) + self.mode = mode + self.compression_level = compression_level + self.streaming = streaming + self._tarfile: tarfile.TarFile | None = None + self._fileobj: BinaryIO | None = None + self._compressed_stream: ( + zstd.ZstdCompressionWriter + | zstd.ZstdDecompressionReader + | io.BytesIO + | None + ) = None + + # Validate mode + valid_modes = ["r", "w", "a"] + if mode not in valid_modes: + raise ValueError( + f"Invalid mode '{mode}'. Must be one of: {', '.join(valid_modes)}" + ) + + # Validate compression level + if not 1 <= compression_level <= 22: + raise ValueError( + f"Invalid compression level '{compression_level}'. Must be between 1 and 22." + ) + + # Check for unsupported modes immediately - provide clear documentation + if mode.startswith("a"): + raise NotImplementedError( + "Append mode is not currently supported for .tzst/.tar.zst archives. " + "This would require decompressing the entire archive, adding new files, " + "and recompressing, which is complex and potentially slow for large archives. " + "Alternatives: 1) Create multiple archives, 2) Recreate the archive with all files, " + "3) Use standard tar format for append operations, then compress separately." + )
+ + +
+[docs] + def __enter__(self): + """Enter context manager.""" + self.open() + return self
+ + +
+[docs] + def __exit__(self, exc_type, exc_val, exc_tb): + """Exit context manager.""" + try: + self.close() + except Exception: + # Suppress exceptions during cleanup to avoid masking original exceptions + pass
+ + +
+[docs] + def open(self): + """Open the archive. + + See Also: + :meth:`close`: Method to close the archive + """ + try: + if self.mode.startswith("r"): + # Read mode + self._fileobj = open(self.filename, "rb") + dctx = zstd.ZstdDecompressor() + + if self.streaming: + # Streaming mode - use stream reader directly (memory efficient) + # Note: This may limit some tarfile operations that require seeking + self._compressed_stream = dctx.stream_reader(self._fileobj) + self._tarfile = tarfile.open( + fileobj=self._compressed_stream, mode="r|" + ) + else: + # Buffer mode - decompress to memory buffer for random access + # Better compatibility but higher memory usage for large archives + decompressed_chunks = [] + with dctx.stream_reader(self._fileobj) as reader: + while True: + chunk = reader.read(8192) + if not chunk: + break + decompressed_chunks.append(chunk) + decompressed_data = b"".join(decompressed_chunks) + self._compressed_stream = io.BytesIO(decompressed_data) + self._tarfile = tarfile.open( + fileobj=self._compressed_stream, mode="r" + ) + + elif self.mode.startswith("w"): + # Write mode - use streaming compression + self._fileobj = open(self.filename, "wb") + cctx = zstd.ZstdCompressor( + level=self.compression_level, write_content_size=True + ) + self._compressed_stream = cctx.stream_writer(self._fileobj) + self._tarfile = tarfile.open(fileobj=self._compressed_stream, mode="w|") + elif self.mode.startswith("a"): + # Append mode - for tar.zst, this is complex as we need to decompress, + # add files, and recompress. For simplicity, we'll raise an error for now. + raise NotImplementedError( + "Append mode is not currently supported for .tzst/.tar.zst archives. " + "This would require decompressing the entire archive, adding new files, " + "and recompressing, which is complex and potentially slow for large archives. " + "Alternatives: 1) Create multiple archives, 2) Recreate the archive with all files, " + "3) Use standard tar format for append operations, then compress separately." + ) + else: + raise ValueError(f"Invalid mode: {self.mode}") + except Exception as e: + self.close() + if "zstd" in str(e).lower(): + raise TzstDecompressionError(f"Failed to open archive: {e}") from e + else: + raise TzstArchiveError(f"Failed to open archive: {e}") from e
+ + +
+[docs] + def close(self): + """Close the archive.""" + if self._tarfile: + try: + self._tarfile.close() + except Exception: + pass + self._tarfile = None + + if self._compressed_stream: + try: + self._compressed_stream.close() + except Exception: + pass + self._compressed_stream = None + + if self._fileobj: + try: + self._fileobj.close() + except Exception: + pass + self._fileobj = None
+ + +
+[docs] + def add( + self, + name: str | Path, + arcname: str | None = None, + recursive: bool = True, + ): + """ + Add a file or directory to the archive. + + Args: + name: Path to file or directory to add + arcname: Alternative name for the file in the archive + recursive: If True, add directories recursively + + See Also: + :func:`create_archive`: Convenience function for creating archives + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("w"): + raise RuntimeError("Archive not open for writing") + + path = Path(name) + if not path.exists(): + raise FileNotFoundError(f"File not found: {name}") + + try: + self._tarfile.add(str(path), arcname=arcname, recursive=recursive) + except PermissionError as e: + raise TzstArchiveError(f"Failed to add {name}: {e}") from e
+ + +
+[docs] + def extract( + self, + member: str | None = None, + path: str | Path = ".", + set_attrs: bool = True, + numeric_owner: bool = False, + filter: str | Callable | None = "data", + ): + """ + Extract files from the archive. + + Args: + member: Specific member to extract (None for all) + path: Destination directory + set_attrs: Whether to set file attributes + numeric_owner: Whether to use numeric owner + filter: Extraction filter for security. Can be: + - 'data': Safe filter for cross-platform data archives (default, recommended) + - 'tar': Honor most tar features but block dangerous ones + - 'fully_trusted': Honor all metadata (use only for trusted archives) + - None: Use default behavior (may show deprecation warning in Python 3.12+) + - callable: Custom filter function + + Warning: + Never extract archives from untrusted sources without proper filtering. + The 'data' filter is recommended for most use cases as it prevents + dangerous security issues like path traversal attacks. + + Note: + In streaming mode, extracting specific members is not supported. + Some extraction operations may be limited due to the sequential + nature of streaming mode. + + See Also: + :func:`extract_archive`: Convenience function for extracting archives + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + extract_path = Path(path) + extract_path.mkdir(parents=True, exist_ok=True) + + if self.streaming and member: + # Specific member extraction not supported in streaming mode + raise RuntimeError( + "Extracting specific members is not supported in streaming mode. " + "Please use non-streaming mode for selective extraction, or extract all files." + ) # Prepare extraction arguments - different parameters for extract vs extractall + try: + if member: + # extract() accepts set_attrs, numeric_owner, and filter + extract_kwargs = { + "set_attrs": set_attrs, + "numeric_owner": numeric_owner, + "filter": filter, + } + self._tarfile.extract(member, path=extract_path, **extract_kwargs) + else: + # extractall() only accepts numeric_owner and filter (no set_attrs) + extractall_kwargs = { + "numeric_owner": numeric_owner, + "filter": filter, + } + self._tarfile.extractall(path=extract_path, **extractall_kwargs) + except (tarfile.StreamError, OSError) as e: + if self.streaming and ( + "seeking" in str(e).lower() or "stream" in str(e).lower() + ): + raise RuntimeError( + "Extraction failed in streaming mode due to archive structure limitations. " + "This archive may require non-streaming mode for extraction. " + f"Original error: {e}" + ) from e + else: + raise
+ + +
+[docs] + def extractfile(self, member: str | tarfile.TarInfo): + """ + Extract a file-like object from the archive. + + Args: + member: Member name or TarInfo object + + Returns: + File-like object or None if member is not a file + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + return self._tarfile.extractfile(member)
+ + +
+[docs] + def extractall( + self, + path: str | Path = ".", + members: list[tarfile.TarInfo] | None = None, + *, + numeric_owner: bool = False, + filter: str | Callable | None = "data", + ): + """ + Extract all members from the archive. + + Args: + path: Destination directory (default: current directory) + members: Specific members to extract (None for all) + numeric_owner: Whether to use numeric owner IDs + filter: Extraction filter for security. Can be: + - 'data': Safe filter for cross-platform data archives + - 'tar': Honor most tar features but block dangerous ones + - 'fully_trusted': Honor all metadata (trusted archives only) + - None: Use default behavior (may show deprecation warning) + - callable: Custom filter function + + Warning: + Never extract archives from untrusted sources without proper filtering. + The 'data' filter is recommended for most use cases as it prevents + dangerous security issues like path traversal attacks. + + Note: + In streaming mode, extracting specific members is not supported. + Some extraction operations may be limited due to the sequential + nature of streaming mode. + + See Also: + :meth:`extract`: Extract a single member from the archive + :func:`extract_archive`: Convenience function for extracting archives + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + if self.streaming and members is not None: + # Specific member extraction not supported in streaming mode + raise RuntimeError( + "Extracting specific members is not supported in streaming mode. " + "Please use non-streaming mode for selective extraction, " + "or extract all files." + ) + + extract_path = Path(path) + extract_path.mkdir(parents=True, exist_ok=True) + + try: + # extractall() accepts numeric_owner, filter, and members parameters + extractall_kwargs = { + "numeric_owner": numeric_owner, + "filter": filter, + } + if members is not None: + extractall_kwargs["members"] = members + + self._tarfile.extractall(path=extract_path, **extractall_kwargs) + except (tarfile.StreamError, OSError) as e: + if self.streaming and ( + "seeking" in str(e).lower() or "stream" in str(e).lower() + ): + raise RuntimeError( + "Extraction failed in streaming mode due to archive " + "structure limitations. This archive may require " + "non-streaming mode for extraction. " + f"Original error: {e}" + ) from e + else: + raise
+ + +
+[docs] + def getmembers(self) -> list[tarfile.TarInfo]: + """Get list of all members in the archive.""" + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + return self._tarfile.getmembers()
+ + +
+[docs] + def getnames(self) -> list[str]: + """Get list of all member names in the archive.""" + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + return self._tarfile.getnames()
+ + +
+[docs] + def list(self, verbose: bool = False) -> list[dict]: + """ + List contents of the archive. + + Args: + verbose: Include detailed information + + Returns: + List of file information dictionaries + + See Also: + :meth:`getmembers`: Get TarInfo objects for all archive members + :meth:`getnames`: Get names of all archive members + :func:`list_archive`: Convenience function for listing archives + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + members = self.getmembers() + result = [] + + for member in members: + info = { + "name": member.name, + "size": member.size, + "is_file": member.isfile(), + "is_dir": member.isdir(), + "is_link": member.islnk(), + "is_symlink": member.issym(), + } + + if verbose: + info.update( + { + "mode": member.mode, + "uid": member.uid, + "gid": member.gid, + "mtime": member.mtime, + "mtime_str": time.strftime( + "%Y-%m-%d %H:%M:%S", time.localtime(member.mtime) + ), + "linkname": member.linkname, + "uname": member.uname, + "gname": member.gname, + } + ) + + result.append(info) + + return result
+ + +
+[docs] + def test(self) -> bool: + """ + Test the integrity of the archive. + + Returns: + True if archive is valid, False otherwise + + See Also: + :func:`test_archive`: Convenience function for testing archive integrity + """ + if not self._tarfile: + raise RuntimeError("Archive not open") + if not self.mode.startswith("r"): + raise RuntimeError("Archive not open for reading") + + try: + # Try to iterate through all members and read file contents + for member in self.getmembers(): + if member.isfile(): + # Try to extract each file to verify integrity + fileobj = self.extractfile(member) + if fileobj: + # Read the entire file to verify decompression + while True: + chunk = fileobj.read(8192) + if not chunk: + break + return True + except Exception: + return False
+
+ + + +# Convenience functions + + +
+[docs] +def create_archive( + archive_path: str | Path, + files: Sequence[str | Path], + compression_level: int = 3, + use_temp_file: bool = True, +) -> None: + """ + Create a new .tzst archive with atomic file operations. + + Args: + archive_path: Path for the new archive + files: List of files/directories to add + compression_level: Zstandard compression level (1-22) + use_temp_file: If True, create archive in temporary file first, then move + to final location for atomic operation + + See Also: + :meth:`TzstArchive.add`: Method for adding files to an open archive + """ + # Validate compression level + if not 1 <= compression_level <= 22: + raise ValueError( + f"Invalid compression level '{compression_level}'. Must be between 1 and 22." + ) + + archive_path = Path(archive_path) + + # Ensure archive has correct extension + if archive_path.suffix.lower() not in [".tzst", ".zst"]: + if archive_path.suffix.lower() == ".tar": + archive_path = archive_path.with_suffix(".tar.zst") + else: + archive_path = archive_path.with_suffix(archive_path.suffix + ".tzst") + + # Use temporary file for atomic operation if requested + if use_temp_file: + temp_fd = None + temp_path = None + try: + # Create temporary file in same directory as target for atomic move + temp_fd, temp_path_str = tempfile.mkstemp( + suffix=".tmp", prefix=f".{archive_path.name}.", dir=archive_path.parent + ) + os.close(temp_fd) # Close file descriptor, we'll open with TzstArchive + temp_path = Path(temp_path_str) + + # Create archive in temporary location + _create_archive_impl(temp_path, files, compression_level) + + # Atomic move to final location + temp_path.replace(archive_path) + + except Exception: + # Clean up temporary file on error + if temp_path and temp_path.exists(): + try: + temp_path.unlink() + except Exception: + pass + raise + else: + # Direct creation (non-atomic) + _create_archive_impl(archive_path, files, compression_level)
+ + + +def _create_archive_impl( + archive_path: Path, + files: Sequence[str | Path], + compression_level: int, +) -> None: + """Internal implementation for creating archives.""" + # Find common parent directory for relative paths + if files: + file_paths = [Path(f) for f in files if Path(f).exists()] + if file_paths: # Special handling for current directory "." + current_dir = Path.cwd().resolve() + if len(file_paths) == 1 and ( + str(file_paths[0]) == "." or file_paths[0].resolve() == current_dir + ): # When adding current directory, add its contents without "./" prefix + with TzstArchive(archive_path, "w", compression_level) as archive: + # Add all items in current directory, excluding archive and temp files + archive_abs_path = archive_path.resolve() + archive_name = archive_path.name + for item in Path(".").iterdir(): + item_abs_path = item.resolve() + # Skip archives and temp files for consistency + if ( + item_abs_path == archive_abs_path + or item.name == archive_name + or ( + item.name.startswith(".") and item.name.endswith(".tmp") + ) + or item.suffix.lower() in [".tzst", ".zst"] + or item.name.lower().endswith(".tar.zst") + ): + continue + # Use item name as archive name to avoid "./" prefix + item_name = str(item.name).replace("\\", "/") + archive.add(str(item), arcname=item_name) + else: + # Find the common parent directory + try: + common_parent = Path( + os.path.commonpath([p.parent for p in file_paths]) + ) + except ValueError: + # No common path, use parent of first file + common_parent = file_paths[ + 0 + ].parent # Change to common parent directory to get relative paths + original_cwd = Path.cwd() + # Convert archive path to absolute to avoid issues when changing working directory + absolute_archive_path = archive_path.resolve() + try: + os.chdir(common_parent) + with TzstArchive( + absolute_archive_path, "w", compression_level + ) as archive: + for file_path in file_paths: + # Calculate relative path from common parent + relative_path = file_path.relative_to(common_parent) + # Normalize path separators and remove Windows prefixes + path_str = str(relative_path).replace("\\", "/") + if path_str.startswith("./") or path_str.startswith(".\\"): + path_str = path_str[2:] + # Use arcname to control the name in the archive + archive.add(str(relative_path), arcname=path_str) + finally: + os.chdir(original_cwd) + else: + raise FileNotFoundError("No valid files found") + else: + # Empty archive + with TzstArchive(archive_path, "w", compression_level) as archive: + pass + + +
+[docs] +def extract_archive( + archive_path: str | Path, + extract_path: str | Path = ".", + members: list[str] | None = None, + flatten: bool = False, + streaming: bool = False, + filter: str | Callable | None = "data", + conflict_resolution: ConflictResolution | str = ConflictResolution.REPLACE, + interactive_callback: Callable[[Path], ConflictResolution] | None = None, +) -> None: + """ + Extract files from a .tzst archive. + + Args: + archive_path: Path to the archive + extract_path: Destination directory + members: Specific members to extract (None for all) + flatten: If True, extract without directory structure + streaming: If True, use streaming mode (memory efficient for large archives) + filter: Extraction filter for security. Can be: + - 'data': Safe filter for cross-platform data archives (default) + - 'tar': Honor most tar features but block dangerous ones + - 'fully_trusted': Honor all metadata (use only for trusted archives) + - None: Use default behavior (may show deprecation warning) + - callable: Custom filter function + conflict_resolution: How to handle file conflicts during extraction + interactive_callback: Function to call for interactive conflict resolution + + Warning: + Never extract archives from untrusted sources without proper filtering. The 'data' filter is recommended for most use cases as it prevents + dangerous security issues like path traversal attacks. + + See Also: + :meth:`TzstArchive.extract`: Method for extracting from an open archive + """ + with TzstArchive(archive_path, "r", streaming=streaming) as archive: + # Convert string resolution to enum if needed + if isinstance(conflict_resolution, str): + try: + conflict_resolution = ConflictResolution(conflict_resolution) + except ValueError: + conflict_resolution = ConflictResolution.REPLACE + + state = ConflictResolutionState(conflict_resolution) + + if flatten: + # Extract files without directory structure + extract_dir = Path(extract_path) + extract_dir.mkdir(parents=True, exist_ok=True) + + if members: + member_list = [m for m in archive.getmembers() if m.name in members] + else: + member_list = archive.getmembers() + + for member in member_list: + if not state.should_continue(): + break + + if member.isfile(): + # Extract to flat directory + filename = Path(member.name).name + target_path = extract_dir / filename + + # Handle conflicts + if target_path.exists(): + current_resolution = ( + state.global_resolution or conflict_resolution + ) + actual_resolution, final_path = _handle_file_conflict( + target_path, current_resolution, interactive_callback + ) + state.update_resolution(actual_resolution) + + if actual_resolution in ( + ConflictResolution.SKIP, + ConflictResolution.SKIP_ALL, + ): + continue + elif actual_resolution == ConflictResolution.EXIT: + break + target_path = final_path + + fileobj = archive.extractfile(member) + if fileobj: + with open(target_path, "wb") as f: + f.write(fileobj.read()) + else: + # Extract with full directory structure + if members: + for member in members: + if not state.should_continue(): + break + + target_path = Path(extract_path) / member + + # Handle conflicts + if target_path.exists(): + current_resolution = ( + state.global_resolution or conflict_resolution + ) + actual_resolution, final_path = _handle_file_conflict( + target_path, current_resolution, interactive_callback + ) + state.update_resolution(actual_resolution) + + if actual_resolution in ( + ConflictResolution.SKIP, + ConflictResolution.SKIP_ALL, + ): + continue + elif actual_resolution == ConflictResolution.EXIT: + break # For AUTO_RENAME, we need to adjust the member path + if actual_resolution in ( + ConflictResolution.AUTO_RENAME, + ConflictResolution.AUTO_RENAME_ALL, + ): + # Create parent directories for renamed file + if final_path: + final_path.parent.mkdir(parents=True, exist_ok=True) + # Extract to temporary location, then move + temp_extract_path = Path(tempfile.mkdtemp()) + try: + archive.extract( + member, temp_extract_path, filter=filter + ) + temp_file = temp_extract_path / member + if final_path: + _move_file_cross_platform(temp_file, final_path) + finally: + # Clean up temp directory + import shutil + + shutil.rmtree(temp_extract_path, ignore_errors=True) + else: + archive.extract(member, extract_path, filter=filter) + else: + archive.extract(member, extract_path, filter=filter) + else: + # For extractall, we need a different approach + # We'll extract to a temp location and handle conflicts file by file + temp_extract_path = Path(tempfile.mkdtemp()) + try: + archive.extractall(temp_extract_path, filter=filter) + + # Move files with conflict resolution + for temp_file in temp_extract_path.rglob("*"): + if not state.should_continue(): + break + + if temp_file.is_file(): + rel_path = temp_file.relative_to(temp_extract_path) + target_path = Path(extract_path) / rel_path + + # Create parent directories + target_path.parent.mkdir( + parents=True, exist_ok=True + ) # Handle conflicts + if target_path.exists(): + current_resolution = ( + state.global_resolution or conflict_resolution + ) + actual_resolution, final_path = _handle_file_conflict( + target_path, + current_resolution, + interactive_callback, + ) + state.update_resolution(actual_resolution) + + if actual_resolution in ( + ConflictResolution.SKIP, + ConflictResolution.SKIP_ALL, + ): + continue + elif actual_resolution == ConflictResolution.EXIT: + break + target_path = final_path + + # Handle file replacement on Windows + if target_path and target_path.exists(): + if actual_resolution in ( + ConflictResolution.REPLACE, + ConflictResolution.REPLACE_ALL, + ): + target_path.unlink() # Remove existing file + + if target_path: + _move_file_cross_platform(temp_file, target_path) + finally: + # Clean up temp directory + import shutil + + shutil.rmtree(temp_extract_path, ignore_errors=True)
+ + + +
+[docs] +def list_archive( + archive_path: str | Path, verbose: bool = False, streaming: bool = False +) -> list[dict]: + """ + List contents of a .tzst archive. + + Args: + archive_path: Path to the archive + verbose: Include detailed information + streaming: If True, use streaming mode (memory efficient for large archives) + + Returns: + List of file information dictionaries + + See Also: + :meth:`TzstArchive.list`: Method for listing an open archive + """ + with TzstArchive(archive_path, "r", streaming=streaming) as archive: + return archive.list(verbose=verbose)
+ + + +
+[docs] +def test_archive(archive_path: str | Path, streaming: bool = False) -> bool: + """ + Test the integrity of a .tzst archive. + + Args: + archive_path: Path to the archive + streaming: If True, use streaming mode (memory efficient for large archives) + + Returns: + True if archive is valid, False otherwise + + See Also: + :meth:`TzstArchive.test`: Method for testing an open archive + """ + try: + # Open a fresh archive instance for testing + with TzstArchive(archive_path, "r", streaming=streaming) as archive: + # Try to iterate through all members and read file contents + for member in archive.getmembers(): + if member.isfile(): + # In streaming mode, extractfile may not work properly with r| mode + # So we'll just check that we can iterate through members + if streaming: + # For streaming mode, just verify we can read the member info + # This tests that the archive structure is valid + continue + else: + # Try to extract each file to verify integrity + fileobj = archive.extractfile(member) + if fileobj: + # Read the entire file to verify decompression + while True: + chunk = fileobj.read(8192) + if not chunk: + break + return True + except Exception: + return False
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/tzst/exceptions.html b/_modules/tzst/exceptions.html new file mode 100644 index 0000000..339a18b --- /dev/null +++ b/_modules/tzst/exceptions.html @@ -0,0 +1,276 @@ + + + + + + + + tzst.exceptions — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for tzst.exceptions

+"""Exception classes for tzst."""
+
+
+
+[docs] +class TzstError(Exception): + """Base exception for all tzst operations. + + This is the parent class for all tzst-specific exceptions. + Catch this to handle any tzst-related error. + """ + + pass
+ + + +
+[docs] +class TzstCompressionError(TzstError): + """Exception raised when compression operations fail. + + This can occur when: + - Invalid compression level is specified + - Disk space is insufficient during compression + - Input data cannot be compressed due to corruption + - Zstandard compression encounters an internal error + """ + + pass
+ + + +
+[docs] +class TzstDecompressionError(TzstError): + """Exception raised when decompression operations fail. + + This can occur when: + - Archive file is corrupted or incomplete + - Archive was not created with zstandard compression + - Decompression buffer overflows or underflows + - Archive format is invalid or unsupported + """ + + pass
+ + + +
+[docs] +class TzstArchiveError(TzstError): + """Exception raised when archive operations fail. + + This can occur when: + - Archive file cannot be opened or created + - File permissions prevent archive access + - Archive structure is malformed + - Tar operations fail within the archive + - Atomic file operations fail during creation + """ + + pass
+ + + +
+[docs] +class TzstFileNotFoundError(TzstError, FileNotFoundError): + """Exception raised when a required file is not found. + + This can occur when: + - Archive file does not exist for reading operations + - Input files for archiving do not exist + - Output directory cannot be created for extraction + - Temporary files cannot be created during atomic operations + + Inherits from both TzstError and FileNotFoundError for compatibility + with standard Python exception handling patterns. + """ + + pass
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/_static/_sphinx_javascript_frameworks_compat.js b/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 0000000..8141580 --- /dev/null +++ b/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/_static/base-stemmer.js b/_static/base-stemmer.js new file mode 100644 index 0000000..e6fa0c4 --- /dev/null +++ b/_static/base-stemmer.js @@ -0,0 +1,476 @@ +// @ts-check + +/**@constructor*/ +BaseStemmer = function() { + /** @protected */ + this.current = ''; + this.cursor = 0; + this.limit = 0; + this.limit_backward = 0; + this.bra = 0; + this.ket = 0; + + /** + * @param {string} value + */ + this.setCurrent = function(value) { + this.current = value; + this.cursor = 0; + this.limit = this.current.length; + this.limit_backward = 0; + this.bra = this.cursor; + this.ket = this.limit; + }; + + /** + * @return {string} + */ + this.getCurrent = function() { + return this.current; + }; + + /** + * @param {BaseStemmer} other + */ + this.copy_from = function(other) { + /** @protected */ + this.current = other.current; + this.cursor = other.cursor; + this.limit = other.limit; + this.limit_backward = other.limit_backward; + this.bra = other.bra; + this.ket = other.ket; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.in_grouping = function(s, min, max) { + /** @protected */ + if (this.cursor >= this.limit) return false; + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) return false; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; + this.cursor++; + return true; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_in_grouping = function(s, min, max) { + /** @protected */ + while (this.cursor < this.limit) { + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) + return true; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) + return true; + this.cursor++; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.in_grouping_b = function(s, min, max) { + /** @protected */ + if (this.cursor <= this.limit_backward) return false; + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) return false; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return false; + this.cursor--; + return true; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_in_grouping_b = function(s, min, max) { + /** @protected */ + while (this.cursor > this.limit_backward) { + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) return true; + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) return true; + this.cursor--; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.out_grouping = function(s, min, max) { + /** @protected */ + if (this.cursor >= this.limit) return false; + var ch = this.current.charCodeAt(this.cursor); + if (ch > max || ch < min) { + this.cursor++; + return true; + } + ch -= min; + if ((s[ch >>> 3] & (0X1 << (ch & 0x7))) == 0) { + this.cursor++; + return true; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_out_grouping = function(s, min, max) { + /** @protected */ + while (this.cursor < this.limit) { + var ch = this.current.charCodeAt(this.cursor); + if (ch <= max && ch >= min) { + ch -= min; + if ((s[ch >>> 3] & (0X1 << (ch & 0x7))) != 0) { + return true; + } + } + this.cursor++; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.out_grouping_b = function(s, min, max) { + /** @protected */ + if (this.cursor <= this.limit_backward) return false; + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch > max || ch < min) { + this.cursor--; + return true; + } + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) == 0) { + this.cursor--; + return true; + } + return false; + }; + + /** + * @param {number[]} s + * @param {number} min + * @param {number} max + * @return {boolean} + */ + this.go_out_grouping_b = function(s, min, max) { + /** @protected */ + while (this.cursor > this.limit_backward) { + var ch = this.current.charCodeAt(this.cursor - 1); + if (ch <= max && ch >= min) { + ch -= min; + if ((s[ch >>> 3] & (0x1 << (ch & 0x7))) != 0) { + return true; + } + } + this.cursor--; + } + return false; + }; + + /** + * @param {string} s + * @return {boolean} + */ + this.eq_s = function(s) + { + /** @protected */ + if (this.limit - this.cursor < s.length) return false; + if (this.current.slice(this.cursor, this.cursor + s.length) != s) + { + return false; + } + this.cursor += s.length; + return true; + }; + + /** + * @param {string} s + * @return {boolean} + */ + this.eq_s_b = function(s) + { + /** @protected */ + if (this.cursor - this.limit_backward < s.length) return false; + if (this.current.slice(this.cursor - s.length, this.cursor) != s) + { + return false; + } + this.cursor -= s.length; + return true; + }; + + /** + * @param {Among[]} v + * @return {number} + */ + this.find_among = function(v) + { + /** @protected */ + var i = 0; + var j = v.length; + + var c = this.cursor; + var l = this.limit; + + var common_i = 0; + var common_j = 0; + + var first_key_inspected = false; + + while (true) + { + var k = i + ((j - i) >>> 1); + var diff = 0; + var common = common_i < common_j ? common_i : common_j; // smaller + // w[0]: string, w[1]: substring_i, w[2]: result, w[3]: function (optional) + var w = v[k]; + var i2; + for (i2 = common; i2 < w[0].length; i2++) + { + if (c + common == l) + { + diff = -1; + break; + } + diff = this.current.charCodeAt(c + common) - w[0].charCodeAt(i2); + if (diff != 0) break; + common++; + } + if (diff < 0) + { + j = k; + common_j = common; + } + else + { + i = k; + common_i = common; + } + if (j - i <= 1) + { + if (i > 0) break; // v->s has been inspected + if (j == i) break; // only one item in v + + // - but now we need to go round once more to get + // v->s inspected. This looks messy, but is actually + // the optimal approach. + + if (first_key_inspected) break; + first_key_inspected = true; + } + } + do { + var w = v[i]; + if (common_i >= w[0].length) + { + this.cursor = c + w[0].length; + if (w.length < 4) return w[2]; + var res = w[3](this); + this.cursor = c + w[0].length; + if (res) return w[2]; + } + i = w[1]; + } while (i >= 0); + return 0; + }; + + // find_among_b is for backwards processing. Same comments apply + /** + * @param {Among[]} v + * @return {number} + */ + this.find_among_b = function(v) + { + /** @protected */ + var i = 0; + var j = v.length + + var c = this.cursor; + var lb = this.limit_backward; + + var common_i = 0; + var common_j = 0; + + var first_key_inspected = false; + + while (true) + { + var k = i + ((j - i) >> 1); + var diff = 0; + var common = common_i < common_j ? common_i : common_j; + var w = v[k]; + var i2; + for (i2 = w[0].length - 1 - common; i2 >= 0; i2--) + { + if (c - common == lb) + { + diff = -1; + break; + } + diff = this.current.charCodeAt(c - 1 - common) - w[0].charCodeAt(i2); + if (diff != 0) break; + common++; + } + if (diff < 0) + { + j = k; + common_j = common; + } + else + { + i = k; + common_i = common; + } + if (j - i <= 1) + { + if (i > 0) break; + if (j == i) break; + if (first_key_inspected) break; + first_key_inspected = true; + } + } + do { + var w = v[i]; + if (common_i >= w[0].length) + { + this.cursor = c - w[0].length; + if (w.length < 4) return w[2]; + var res = w[3](this); + this.cursor = c - w[0].length; + if (res) return w[2]; + } + i = w[1]; + } while (i >= 0); + return 0; + }; + + /* to replace chars between c_bra and c_ket in this.current by the + * chars in s. + */ + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + * @return {number} + */ + this.replace_s = function(c_bra, c_ket, s) + { + /** @protected */ + var adjustment = s.length - (c_ket - c_bra); + this.current = this.current.slice(0, c_bra) + s + this.current.slice(c_ket); + this.limit += adjustment; + if (this.cursor >= c_ket) this.cursor += adjustment; + else if (this.cursor > c_bra) this.cursor = c_bra; + return adjustment; + }; + + /** + * @return {boolean} + */ + this.slice_check = function() + { + /** @protected */ + if (this.bra < 0 || + this.bra > this.ket || + this.ket > this.limit || + this.limit > this.current.length) + { + return false; + } + return true; + }; + + /** + * @param {number} c_bra + * @return {boolean} + */ + this.slice_from = function(s) + { + /** @protected */ + var result = false; + if (this.slice_check()) + { + this.replace_s(this.bra, this.ket, s); + result = true; + } + return result; + }; + + /** + * @return {boolean} + */ + this.slice_del = function() + { + /** @protected */ + return this.slice_from(""); + }; + + /** + * @param {number} c_bra + * @param {number} c_ket + * @param {string} s + */ + this.insert = function(c_bra, c_ket, s) + { + /** @protected */ + var adjustment = this.replace_s(c_bra, c_ket, s); + if (c_bra <= this.bra) this.bra += adjustment; + if (c_bra <= this.ket) this.ket += adjustment; + }; + + /** + * @return {string} + */ + this.slice_to = function() + { + /** @protected */ + var result = ''; + if (this.slice_check()) + { + result = this.current.slice(this.bra, this.ket); + } + return result; + }; + + /** + * @return {string} + */ + this.assign_to = function() + { + /** @protected */ + return this.current.slice(0, this.limit); + }; +}; diff --git a/_static/basic.css b/_static/basic.css new file mode 100644 index 0000000..4738b2e --- /dev/null +++ b/_static/basic.css @@ -0,0 +1,906 @@ +/* + * Sphinx stylesheet -- basic theme. + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin-top: 10px; +} + +ul.search li { + padding: 5px 0; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a:visited { + color: #551A8B; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +.sig dd { + margin-top: 0px; + margin-bottom: 0px; +} + +.sig dl { + margin-top: 0px; + margin-bottom: 0px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/_static/css/badge_only.css b/_static/css/badge_only.css new file mode 100644 index 0000000..88ba55b --- /dev/null +++ b/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px} \ No newline at end of file diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff b/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Bold.woff2 b/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff b/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/_static/css/fonts/Roboto-Slab-Regular.woff2 b/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/_static/css/fonts/fontawesome-webfont.eot b/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..e9f60ca Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/_static/css/fonts/fontawesome-webfont.svg b/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 0000000..855c845 --- /dev/null +++ b/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/_static/css/fonts/fontawesome-webfont.ttf b/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 0000000..35acda2 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/_static/css/fonts/fontawesome-webfont.woff b/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..400014a Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/_static/css/fonts/fontawesome-webfont.woff2 b/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..4d13fc6 Binary files /dev/null and b/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/_static/css/fonts/lato-bold-italic.woff b/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff differ diff --git a/_static/css/fonts/lato-bold-italic.woff2 b/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/_static/css/fonts/lato-bold.woff b/_static/css/fonts/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff differ diff --git a/_static/css/fonts/lato-bold.woff2 b/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/css/fonts/lato-bold.woff2 differ diff --git a/_static/css/fonts/lato-normal-italic.woff b/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff differ diff --git a/_static/css/fonts/lato-normal-italic.woff2 b/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/_static/css/fonts/lato-normal.woff b/_static/css/fonts/lato-normal.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/css/fonts/lato-normal.woff differ diff --git a/_static/css/fonts/lato-normal.woff2 b/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/css/fonts/lato-normal.woff2 differ diff --git a/_static/css/theme.css b/_static/css/theme.css new file mode 100644 index 0000000..a88467c --- /dev/null +++ b/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search .wy-dropdown>aactive,.wy-side-nav-search .wy-dropdown>afocus,.wy-side-nav-search>a:hover,.wy-side-nav-search>aactive,.wy-side-nav-search>afocus{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon,.wy-side-nav-search>a.icon{display:block}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.switch-menus{position:relative;display:block;margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-side-nav-search>div.switch-menus>div.language-switch,.wy-side-nav-search>div.switch-menus>div.version-switch{display:inline-block;padding:.2em}.wy-side-nav-search>div.switch-menus>div.language-switch select,.wy-side-nav-search>div.switch-menus>div.version-switch select{display:inline-block;margin-right:-2rem;padding-right:2rem;max-width:240px;text-align-last:center;background:none;border:none;border-radius:0;box-shadow:none;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-size:1em;font-weight:400;color:hsla(0,0%,100%,.3);cursor:pointer;appearance:none;-webkit-appearance:none;-moz-appearance:none}.wy-side-nav-search>div.switch-menus>div.language-switch select:active,.wy-side-nav-search>div.switch-menus>div.language-switch select:focus,.wy-side-nav-search>div.switch-menus>div.language-switch select:hover,.wy-side-nav-search>div.switch-menus>div.version-switch select:active,.wy-side-nav-search>div.switch-menus>div.version-switch select:focus,.wy-side-nav-search>div.switch-menus>div.version-switch select:hover{background:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.wy-side-nav-search>div.switch-menus>div.language-switch select option,.wy-side-nav-search>div.switch-menus>div.version-switch select option{color:#000}.wy-side-nav-search>div.switch-menus>div.language-switch:has(>select):after,.wy-side-nav-search>div.switch-menus>div.version-switch:has(>select):after{display:inline-block;width:1.5em;height:100%;padding:.1em;content:"\f0d7";font-size:1em;line-height:1.2em;font-family:FontAwesome;text-align:center;pointer-events:none;box-sizing:border-box}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions .rst-other-versions .rtd-current-item{font-weight:700}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}#flyout-search-form{padding:6px}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%;float:none;margin-left:0}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js new file mode 100644 index 0000000..807cdb1 --- /dev/null +++ b/_static/doctools.js @@ -0,0 +1,150 @@ +/* + * Base JavaScript utilities for all Sphinx HTML documentation. + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})`, + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)), + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS + && !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) + return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/_static/documentation_options.js b/_static/documentation_options.js new file mode 100644 index 0000000..8545a88 --- /dev/null +++ b/_static/documentation_options.js @@ -0,0 +1,13 @@ +const DOCUMENTATION_OPTIONS = { + VERSION: '1.3.3', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: false, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/_static/english-stemmer.js b/_static/english-stemmer.js new file mode 100644 index 0000000..056760e --- /dev/null +++ b/_static/english-stemmer.js @@ -0,0 +1,1066 @@ +// Generated from english.sbl by Snowball 3.0.1 - https://snowballstem.org/ + +/**@constructor*/ +var EnglishStemmer = function() { + var base = new BaseStemmer(); + + /** @const */ var a_0 = [ + ["arsen", -1, -1], + ["commun", -1, -1], + ["emerg", -1, -1], + ["gener", -1, -1], + ["later", -1, -1], + ["organ", -1, -1], + ["past", -1, -1], + ["univers", -1, -1] + ]; + + /** @const */ var a_1 = [ + ["'", -1, 1], + ["'s'", 0, 1], + ["'s", -1, 1] + ]; + + /** @const */ var a_2 = [ + ["ied", -1, 2], + ["s", -1, 3], + ["ies", 1, 2], + ["sses", 1, 1], + ["ss", 1, -1], + ["us", 1, -1] + ]; + + /** @const */ var a_3 = [ + ["succ", -1, 1], + ["proc", -1, 1], + ["exc", -1, 1] + ]; + + /** @const */ var a_4 = [ + ["even", -1, 2], + ["cann", -1, 2], + ["inn", -1, 2], + ["earr", -1, 2], + ["herr", -1, 2], + ["out", -1, 2], + ["y", -1, 1] + ]; + + /** @const */ var a_5 = [ + ["", -1, -1], + ["ed", 0, 2], + ["eed", 1, 1], + ["ing", 0, 3], + ["edly", 0, 2], + ["eedly", 4, 1], + ["ingly", 0, 2] + ]; + + /** @const */ var a_6 = [ + ["", -1, 3], + ["bb", 0, 2], + ["dd", 0, 2], + ["ff", 0, 2], + ["gg", 0, 2], + ["bl", 0, 1], + ["mm", 0, 2], + ["nn", 0, 2], + ["pp", 0, 2], + ["rr", 0, 2], + ["at", 0, 1], + ["tt", 0, 2], + ["iz", 0, 1] + ]; + + /** @const */ var a_7 = [ + ["anci", -1, 3], + ["enci", -1, 2], + ["ogi", -1, 14], + ["li", -1, 16], + ["bli", 3, 12], + ["abli", 4, 4], + ["alli", 3, 8], + ["fulli", 3, 9], + ["lessli", 3, 15], + ["ousli", 3, 10], + ["entli", 3, 5], + ["aliti", -1, 8], + ["biliti", -1, 12], + ["iviti", -1, 11], + ["tional", -1, 1], + ["ational", 14, 7], + ["alism", -1, 8], + ["ation", -1, 7], + ["ization", 17, 6], + ["izer", -1, 6], + ["ator", -1, 7], + ["iveness", -1, 11], + ["fulness", -1, 9], + ["ousness", -1, 10], + ["ogist", -1, 13] + ]; + + /** @const */ var a_8 = [ + ["icate", -1, 4], + ["ative", -1, 6], + ["alize", -1, 3], + ["iciti", -1, 4], + ["ical", -1, 4], + ["tional", -1, 1], + ["ational", 5, 2], + ["ful", -1, 5], + ["ness", -1, 5] + ]; + + /** @const */ var a_9 = [ + ["ic", -1, 1], + ["ance", -1, 1], + ["ence", -1, 1], + ["able", -1, 1], + ["ible", -1, 1], + ["ate", -1, 1], + ["ive", -1, 1], + ["ize", -1, 1], + ["iti", -1, 1], + ["al", -1, 1], + ["ism", -1, 1], + ["ion", -1, 2], + ["er", -1, 1], + ["ous", -1, 1], + ["ant", -1, 1], + ["ent", -1, 1], + ["ment", 15, 1], + ["ement", 16, 1] + ]; + + /** @const */ var a_10 = [ + ["e", -1, 1], + ["l", -1, 2] + ]; + + /** @const */ var a_11 = [ + ["andes", -1, -1], + ["atlas", -1, -1], + ["bias", -1, -1], + ["cosmos", -1, -1], + ["early", -1, 5], + ["gently", -1, 3], + ["howe", -1, -1], + ["idly", -1, 2], + ["news", -1, -1], + ["only", -1, 6], + ["singly", -1, 7], + ["skies", -1, 1], + ["sky", -1, -1], + ["ugly", -1, 4] + ]; + + /** @const */ var /** Array */ g_aeo = [17, 64]; + + /** @const */ var /** Array */ g_v = [17, 65, 16, 1]; + + /** @const */ var /** Array */ g_v_WXY = [1, 17, 65, 208, 1]; + + /** @const */ var /** Array */ g_valid_LI = [55, 141, 2]; + + var /** boolean */ B_Y_found = false; + var /** number */ I_p2 = 0; + var /** number */ I_p1 = 0; + + + /** @return {boolean} */ + function r_prelude() { + B_Y_found = false; + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + base.bra = base.cursor; + if (!(base.eq_s("'"))) + { + break lab0; + } + base.ket = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + base.cursor = v_1; + /** @const */ var /** number */ v_2 = base.cursor; + lab1: { + base.bra = base.cursor; + if (!(base.eq_s("y"))) + { + break lab1; + } + base.ket = base.cursor; + if (!base.slice_from("Y")) + { + return false; + } + B_Y_found = true; + } + base.cursor = v_2; + /** @const */ var /** number */ v_3 = base.cursor; + lab2: { + while(true) + { + /** @const */ var /** number */ v_4 = base.cursor; + lab3: { + golab4: while(true) + { + /** @const */ var /** number */ v_5 = base.cursor; + lab5: { + if (!(base.in_grouping(g_v, 97, 121))) + { + break lab5; + } + base.bra = base.cursor; + if (!(base.eq_s("y"))) + { + break lab5; + } + base.ket = base.cursor; + base.cursor = v_5; + break golab4; + } + base.cursor = v_5; + if (base.cursor >= base.limit) + { + break lab3; + } + base.cursor++; + } + if (!base.slice_from("Y")) + { + return false; + } + B_Y_found = true; + continue; + } + base.cursor = v_4; + break; + } + } + base.cursor = v_3; + return true; + }; + + /** @return {boolean} */ + function r_mark_regions() { + I_p1 = base.limit; + I_p2 = base.limit; + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + lab1: { + /** @const */ var /** number */ v_2 = base.cursor; + lab2: { + if (base.find_among(a_0) == 0) + { + break lab2; + } + break lab1; + } + base.cursor = v_2; + if (!base.go_out_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + if (!base.go_in_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + } + I_p1 = base.cursor; + if (!base.go_out_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + if (!base.go_in_grouping(g_v, 97, 121)) + { + break lab0; + } + base.cursor++; + I_p2 = base.cursor; + } + base.cursor = v_1; + return true; + }; + + /** @return {boolean} */ + function r_shortv() { + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.out_grouping_b(g_v_WXY, 89, 121))) + { + break lab1; + } + if (!(base.in_grouping_b(g_v, 97, 121))) + { + break lab1; + } + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + lab2: { + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab2; + } + if (!(base.in_grouping_b(g_v, 97, 121))) + { + break lab2; + } + if (base.cursor > base.limit_backward) + { + break lab2; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("past"))) + { + return false; + } + } + return true; + }; + + /** @return {boolean} */ + function r_R1() { + return I_p1 <= base.cursor; + }; + + /** @return {boolean} */ + function r_R2() { + return I_p2 <= base.cursor; + }; + + /** @return {boolean} */ + function r_Step_1a() { + var /** number */ among_var; + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab0: { + base.ket = base.cursor; + if (base.find_among_b(a_1) == 0) + { + base.cursor = base.limit - v_1; + break lab0; + } + base.bra = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + base.ket = base.cursor; + among_var = base.find_among_b(a_2); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + switch (among_var) { + case 1: + if (!base.slice_from("ss")) + { + return false; + } + break; + case 2: + lab1: { + /** @const */ var /** number */ v_2 = base.limit - base.cursor; + lab2: { + { + /** @const */ var /** number */ c1 = base.cursor - 2; + if (c1 < base.limit_backward) + { + break lab2; + } + base.cursor = c1; + } + if (!base.slice_from("i")) + { + return false; + } + break lab1; + } + base.cursor = base.limit - v_2; + if (!base.slice_from("ie")) + { + return false; + } + } + break; + case 3: + if (base.cursor <= base.limit_backward) + { + return false; + } + base.cursor--; + if (!base.go_out_grouping_b(g_v, 97, 121)) + { + return false; + } + base.cursor--; + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_1b() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_5); + base.bra = base.cursor; + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + switch (among_var) { + case 1: + /** @const */ var /** number */ v_2 = base.limit - base.cursor; + lab2: { + lab3: { + /** @const */ var /** number */ v_3 = base.limit - base.cursor; + lab4: { + if (base.find_among_b(a_3) == 0) + { + break lab4; + } + if (base.cursor > base.limit_backward) + { + break lab4; + } + break lab3; + } + base.cursor = base.limit - v_3; + if (!r_R1()) + { + break lab2; + } + if (!base.slice_from("ee")) + { + return false; + } + } + } + base.cursor = base.limit - v_2; + break; + case 2: + break lab1; + case 3: + among_var = base.find_among_b(a_4); + if (among_var == 0) + { + break lab1; + } + switch (among_var) { + case 1: + /** @const */ var /** number */ v_4 = base.limit - base.cursor; + if (!(base.out_grouping_b(g_v, 97, 121))) + { + break lab1; + } + if (base.cursor > base.limit_backward) + { + break lab1; + } + base.cursor = base.limit - v_4; + base.bra = base.cursor; + if (!base.slice_from("ie")) + { + return false; + } + break; + case 2: + if (base.cursor > base.limit_backward) + { + break lab1; + } + break; + } + break; + } + break lab0; + } + base.cursor = base.limit - v_1; + /** @const */ var /** number */ v_5 = base.limit - base.cursor; + if (!base.go_out_grouping_b(g_v, 97, 121)) + { + return false; + } + base.cursor--; + base.cursor = base.limit - v_5; + if (!base.slice_del()) + { + return false; + } + base.ket = base.cursor; + base.bra = base.cursor; + /** @const */ var /** number */ v_6 = base.limit - base.cursor; + among_var = base.find_among_b(a_6); + switch (among_var) { + case 1: + if (!base.slice_from("e")) + { + return false; + } + return false; + case 2: + { + /** @const */ var /** number */ v_7 = base.limit - base.cursor; + lab5: { + if (!(base.in_grouping_b(g_aeo, 97, 111))) + { + break lab5; + } + if (base.cursor > base.limit_backward) + { + break lab5; + } + return false; + } + base.cursor = base.limit - v_7; + } + break; + case 3: + if (base.cursor != I_p1) + { + return false; + } + /** @const */ var /** number */ v_8 = base.limit - base.cursor; + if (!r_shortv()) + { + return false; + } + base.cursor = base.limit - v_8; + if (!base.slice_from("e")) + { + return false; + } + return false; + } + base.cursor = base.limit - v_6; + base.ket = base.cursor; + if (base.cursor <= base.limit_backward) + { + return false; + } + base.cursor--; + base.bra = base.cursor; + if (!base.slice_del()) + { + return false; + } + } + return true; + }; + + /** @return {boolean} */ + function r_Step_1c() { + base.ket = base.cursor; + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.eq_s_b("y"))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("Y"))) + { + return false; + } + } + base.bra = base.cursor; + if (!(base.out_grouping_b(g_v, 97, 121))) + { + return false; + } + lab2: { + if (base.cursor > base.limit_backward) + { + break lab2; + } + return false; + } + if (!base.slice_from("i")) + { + return false; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_2() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_7); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R1()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("tion")) + { + return false; + } + break; + case 2: + if (!base.slice_from("ence")) + { + return false; + } + break; + case 3: + if (!base.slice_from("ance")) + { + return false; + } + break; + case 4: + if (!base.slice_from("able")) + { + return false; + } + break; + case 5: + if (!base.slice_from("ent")) + { + return false; + } + break; + case 6: + if (!base.slice_from("ize")) + { + return false; + } + break; + case 7: + if (!base.slice_from("ate")) + { + return false; + } + break; + case 8: + if (!base.slice_from("al")) + { + return false; + } + break; + case 9: + if (!base.slice_from("ful")) + { + return false; + } + break; + case 10: + if (!base.slice_from("ous")) + { + return false; + } + break; + case 11: + if (!base.slice_from("ive")) + { + return false; + } + break; + case 12: + if (!base.slice_from("ble")) + { + return false; + } + break; + case 13: + if (!base.slice_from("og")) + { + return false; + } + break; + case 14: + if (!(base.eq_s_b("l"))) + { + return false; + } + if (!base.slice_from("og")) + { + return false; + } + break; + case 15: + if (!base.slice_from("less")) + { + return false; + } + break; + case 16: + if (!(base.in_grouping_b(g_valid_LI, 99, 116))) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_3() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_8); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R1()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("tion")) + { + return false; + } + break; + case 2: + if (!base.slice_from("ate")) + { + return false; + } + break; + case 3: + if (!base.slice_from("al")) + { + return false; + } + break; + case 4: + if (!base.slice_from("ic")) + { + return false; + } + break; + case 5: + if (!base.slice_del()) + { + return false; + } + break; + case 6: + if (!r_R2()) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_4() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_9); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + if (!r_R2()) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_del()) + { + return false; + } + break; + case 2: + lab0: { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab1: { + if (!(base.eq_s_b("s"))) + { + break lab1; + } + break lab0; + } + base.cursor = base.limit - v_1; + if (!(base.eq_s_b("t"))) + { + return false; + } + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_Step_5() { + var /** number */ among_var; + base.ket = base.cursor; + among_var = base.find_among_b(a_10); + if (among_var == 0) + { + return false; + } + base.bra = base.cursor; + switch (among_var) { + case 1: + lab0: { + lab1: { + if (!r_R2()) + { + break lab1; + } + break lab0; + } + if (!r_R1()) + { + return false; + } + { + /** @const */ var /** number */ v_1 = base.limit - base.cursor; + lab2: { + if (!r_shortv()) + { + break lab2; + } + return false; + } + base.cursor = base.limit - v_1; + } + } + if (!base.slice_del()) + { + return false; + } + break; + case 2: + if (!r_R2()) + { + return false; + } + if (!(base.eq_s_b("l"))) + { + return false; + } + if (!base.slice_del()) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_exception1() { + var /** number */ among_var; + base.bra = base.cursor; + among_var = base.find_among(a_11); + if (among_var == 0) + { + return false; + } + base.ket = base.cursor; + if (base.cursor < base.limit) + { + return false; + } + switch (among_var) { + case 1: + if (!base.slice_from("sky")) + { + return false; + } + break; + case 2: + if (!base.slice_from("idl")) + { + return false; + } + break; + case 3: + if (!base.slice_from("gentl")) + { + return false; + } + break; + case 4: + if (!base.slice_from("ugli")) + { + return false; + } + break; + case 5: + if (!base.slice_from("earli")) + { + return false; + } + break; + case 6: + if (!base.slice_from("onli")) + { + return false; + } + break; + case 7: + if (!base.slice_from("singl")) + { + return false; + } + break; + } + return true; + }; + + /** @return {boolean} */ + function r_postlude() { + if (!B_Y_found) + { + return false; + } + while(true) + { + /** @const */ var /** number */ v_1 = base.cursor; + lab0: { + golab1: while(true) + { + /** @const */ var /** number */ v_2 = base.cursor; + lab2: { + base.bra = base.cursor; + if (!(base.eq_s("Y"))) + { + break lab2; + } + base.ket = base.cursor; + base.cursor = v_2; + break golab1; + } + base.cursor = v_2; + if (base.cursor >= base.limit) + { + break lab0; + } + base.cursor++; + } + if (!base.slice_from("y")) + { + return false; + } + continue; + } + base.cursor = v_1; + break; + } + return true; + }; + + this.stem = /** @return {boolean} */ function() { + lab0: { + /** @const */ var /** number */ v_1 = base.cursor; + lab1: { + if (!r_exception1()) + { + break lab1; + } + break lab0; + } + base.cursor = v_1; + lab2: { + { + /** @const */ var /** number */ v_2 = base.cursor; + lab3: { + { + /** @const */ var /** number */ c1 = base.cursor + 3; + if (c1 > base.limit) + { + break lab3; + } + base.cursor = c1; + } + break lab2; + } + base.cursor = v_2; + } + break lab0; + } + base.cursor = v_1; + r_prelude(); + r_mark_regions(); + base.limit_backward = base.cursor; base.cursor = base.limit; + /** @const */ var /** number */ v_3 = base.limit - base.cursor; + r_Step_1a(); + base.cursor = base.limit - v_3; + /** @const */ var /** number */ v_4 = base.limit - base.cursor; + r_Step_1b(); + base.cursor = base.limit - v_4; + /** @const */ var /** number */ v_5 = base.limit - base.cursor; + r_Step_1c(); + base.cursor = base.limit - v_5; + /** @const */ var /** number */ v_6 = base.limit - base.cursor; + r_Step_2(); + base.cursor = base.limit - v_6; + /** @const */ var /** number */ v_7 = base.limit - base.cursor; + r_Step_3(); + base.cursor = base.limit - v_7; + /** @const */ var /** number */ v_8 = base.limit - base.cursor; + r_Step_4(); + base.cursor = base.limit - v_8; + /** @const */ var /** number */ v_9 = base.limit - base.cursor; + r_Step_5(); + base.cursor = base.limit - v_9; + base.cursor = base.limit_backward; + /** @const */ var /** number */ v_10 = base.cursor; + r_postlude(); + base.cursor = v_10; + } + return true; + }; + + /**@return{string}*/ + this['stemWord'] = function(/**string*/word) { + base.setCurrent(word); + this.stem(); + return base.getCurrent(); + }; +}; diff --git a/_static/favicon.ico b/_static/favicon.ico new file mode 100644 index 0000000..47db2df Binary files /dev/null and b/_static/favicon.ico differ diff --git a/_static/file.png b/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/_static/file.png differ diff --git a/_static/fonts/Lato/lato-bold.eot b/_static/fonts/Lato/lato-bold.eot new file mode 100644 index 0000000..3361183 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.eot differ diff --git a/_static/fonts/Lato/lato-bold.ttf b/_static/fonts/Lato/lato-bold.ttf new file mode 100644 index 0000000..29f691d Binary files /dev/null and b/_static/fonts/Lato/lato-bold.ttf differ diff --git a/_static/fonts/Lato/lato-bold.woff b/_static/fonts/Lato/lato-bold.woff new file mode 100644 index 0000000..c6dff51 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff differ diff --git a/_static/fonts/Lato/lato-bold.woff2 b/_static/fonts/Lato/lato-bold.woff2 new file mode 100644 index 0000000..bb19504 Binary files /dev/null and b/_static/fonts/Lato/lato-bold.woff2 differ diff --git a/_static/fonts/Lato/lato-bolditalic.eot b/_static/fonts/Lato/lato-bolditalic.eot new file mode 100644 index 0000000..3d41549 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.eot differ diff --git a/_static/fonts/Lato/lato-bolditalic.ttf b/_static/fonts/Lato/lato-bolditalic.ttf new file mode 100644 index 0000000..f402040 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.ttf differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff b/_static/fonts/Lato/lato-bolditalic.woff new file mode 100644 index 0000000..88ad05b Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff differ diff --git a/_static/fonts/Lato/lato-bolditalic.woff2 b/_static/fonts/Lato/lato-bolditalic.woff2 new file mode 100644 index 0000000..c4e3d80 Binary files /dev/null and b/_static/fonts/Lato/lato-bolditalic.woff2 differ diff --git a/_static/fonts/Lato/lato-italic.eot b/_static/fonts/Lato/lato-italic.eot new file mode 100644 index 0000000..3f82642 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.eot differ diff --git a/_static/fonts/Lato/lato-italic.ttf b/_static/fonts/Lato/lato-italic.ttf new file mode 100644 index 0000000..b4bfc9b Binary files /dev/null and b/_static/fonts/Lato/lato-italic.ttf differ diff --git a/_static/fonts/Lato/lato-italic.woff b/_static/fonts/Lato/lato-italic.woff new file mode 100644 index 0000000..76114bc Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff differ diff --git a/_static/fonts/Lato/lato-italic.woff2 b/_static/fonts/Lato/lato-italic.woff2 new file mode 100644 index 0000000..3404f37 Binary files /dev/null and b/_static/fonts/Lato/lato-italic.woff2 differ diff --git a/_static/fonts/Lato/lato-regular.eot b/_static/fonts/Lato/lato-regular.eot new file mode 100644 index 0000000..11e3f2a Binary files /dev/null and b/_static/fonts/Lato/lato-regular.eot differ diff --git a/_static/fonts/Lato/lato-regular.ttf b/_static/fonts/Lato/lato-regular.ttf new file mode 100644 index 0000000..74decd9 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.ttf differ diff --git a/_static/fonts/Lato/lato-regular.woff b/_static/fonts/Lato/lato-regular.woff new file mode 100644 index 0000000..ae1307f Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff differ diff --git a/_static/fonts/Lato/lato-regular.woff2 b/_static/fonts/Lato/lato-regular.woff2 new file mode 100644 index 0000000..3bf9843 Binary files /dev/null and b/_static/fonts/Lato/lato-regular.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot new file mode 100644 index 0000000..79dc8ef Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf new file mode 100644 index 0000000..df5d1df Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff new file mode 100644 index 0000000..6cb6000 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 new file mode 100644 index 0000000..7059e23 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-bold.woff2 differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot new file mode 100644 index 0000000..2f7ca78 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.eot differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf new file mode 100644 index 0000000..eb52a79 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.ttf differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff new file mode 100644 index 0000000..f815f63 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff differ diff --git a/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 new file mode 100644 index 0000000..f2c76e5 Binary files /dev/null and b/_static/fonts/RobotoSlab/roboto-slab-v7-regular.woff2 differ diff --git a/_static/jquery.js b/_static/jquery.js new file mode 100644 index 0000000..c4c6022 --- /dev/null +++ b/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t a.language.name.localeCompare(b.language.name)); + + const languagesHTML = ` +
+
Languages
+ ${languages + .map( + (translation) => ` +
+ ${translation.language.code} +
+ `, + ) + .join("\n")} +
+ `; + return languagesHTML; + } + + function renderVersions(config) { + if (!config.versions.active.length) { + return ""; + } + const versionsHTML = ` +
+
Versions
+ ${config.versions.active + .map( + (version) => ` +
+ ${version.slug} +
+ `, + ) + .join("\n")} +
+ `; + return versionsHTML; + } + + function renderDownloads(config) { + if (!Object.keys(config.versions.current.downloads).length) { + return ""; + } + const downloadsNameDisplay = { + pdf: "PDF", + epub: "Epub", + htmlzip: "HTML", + }; + + const downloadsHTML = ` +
+
Downloads
+ ${Object.entries(config.versions.current.downloads) + .map( + ([name, url]) => ` +
+ ${downloadsNameDisplay[name]} +
+ `, + ) + .join("\n")} +
+ `; + return downloadsHTML; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const flyout = ` +
+ + Read the Docs + v: ${config.versions.current.slug} + + +
+
+ ${renderLanguages(config)} + ${renderVersions(config)} + ${renderDownloads(config)} +
+
On Read the Docs
+
+ Project Home +
+
+ Builds +
+
+ Downloads +
+
+
+
Search
+
+
+ +
+
+
+
+ + Hosted by Read the Docs + +
+
+ `; + + // Inject the generated flyout into the body HTML element. + document.body.insertAdjacentHTML("beforeend", flyout); + + // Trigger the Read the Docs Addons Search modal when clicking on the "Search docs" input from inside the flyout. + document + .querySelector("#flyout-search-form") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); + }) +} + +if (themeLanguageSelector || themeVersionSelector) { + function onSelectorSwitch(event) { + const option = event.target.selectedIndex; + const item = event.target.options[option]; + window.location.href = item.dataset.url; + } + + document.addEventListener("readthedocs-addons-data-ready", function (event) { + const config = event.detail.data(); + + const versionSwitch = document.querySelector( + "div.switch-menus > div.version-switch", + ); + if (themeVersionSelector) { + let versions = config.versions.active; + if (config.versions.current.hidden || config.versions.current.type === "external") { + versions.unshift(config.versions.current); + } + const versionSelect = ` + + `; + + versionSwitch.innerHTML = versionSelect; + versionSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + + const languageSwitch = document.querySelector( + "div.switch-menus > div.language-switch", + ); + + if (themeLanguageSelector) { + if (config.projects.translations.length) { + // Add the current language to the options on the selector + let languages = config.projects.translations.concat( + config.projects.current, + ); + languages = languages.sort((a, b) => + a.language.name.localeCompare(b.language.name), + ); + + const languageSelect = ` + + `; + + languageSwitch.innerHTML = languageSelect; + languageSwitch.firstElementChild.addEventListener("change", onSelectorSwitch); + } + else { + languageSwitch.remove(); + } + } + }); +} + +document.addEventListener("readthedocs-addons-data-ready", function (event) { + // Trigger the Read the Docs Addons Search modal when clicking on "Search docs" input from the topnav. + document + .querySelector("[role='search'] input") + .addEventListener("focusin", () => { + const event = new CustomEvent("readthedocs-search-show"); + document.dispatchEvent(event); + }); +}); \ No newline at end of file diff --git a/_static/language_data.js b/_static/language_data.js new file mode 100644 index 0000000..5776786 --- /dev/null +++ b/_static/language_data.js @@ -0,0 +1,13 @@ +/* + * This script contains the language-specific data used by searchtools.js, + * namely the set of stopwords, stemmer, scorer and splitter. + */ + +const stopwords = new Set(["a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "aren't", "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", "can't", "cannot", "could", "couldn't", "did", "didn't", "do", "does", "doesn't", "doing", "don't", "down", "during", "each", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't", "have", "haven't", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "isn't", "it", "it's", "its", "itself", "let's", "me", "more", "most", "mustn't", "my", "myself", "no", "nor", "not", "of", "off", "on", "once", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", "own", "same", "shan't", "she", "she'd", "she'll", "she's", "should", "shouldn't", "so", "some", "such", "than", "that", "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", "these", "they", "they'd", "they'll", "they're", "they've", "this", "those", "through", "to", "too", "under", "until", "up", "very", "was", "wasn't", "we", "we'd", "we'll", "we're", "we've", "were", "weren't", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", "whom", "why", "why's", "with", "won't", "would", "wouldn't", "you", "you'd", "you'll", "you're", "you've", "your", "yours", "yourself", "yourselves"]); +window.stopwords = stopwords; // Export to global scope + + +/* Non-minified versions are copied as separate JavaScript files, if available */ +BaseStemmer=function(){this.current="",this.cursor=0,this.limit=0,this.limit_backward=0,this.bra=0,this.ket=0,this.setCurrent=function(t){this.current=t,this.cursor=0,this.limit=this.current.length,this.limit_backward=0,this.bra=this.cursor,this.ket=this.limit},this.getCurrent=function(){return this.current},this.copy_from=function(t){this.current=t.current,this.cursor=t.cursor,this.limit=t.limit,this.limit_backward=t.limit_backward,this.bra=t.bra,this.ket=t.ket},this.in_grouping=function(t,r,i){return!(this.cursor>=this.limit||i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i))||(this.cursor++,0))},this.go_in_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.in_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward||i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i))||(this.cursor--,0))},this.go_in_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(i>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.out_grouping=function(t,r,i){return!(this.cursor>=this.limit)&&(i<(i=this.current.charCodeAt(this.cursor))||i>>3]&1<<(7&i)))&&(this.cursor++,!0)},this.go_out_grouping=function(t,r,i){for(;this.cursor>>3]&1<<(7&s)))return!0;this.cursor++}return!1},this.out_grouping_b=function(t,r,i){return!(this.cursor<=this.limit_backward)&&(i<(i=this.current.charCodeAt(this.cursor-1))||i>>3]&1<<(7&i)))&&(this.cursor--,!0)},this.go_out_grouping_b=function(t,r,i){for(;this.cursor>this.limit_backward;){var s=this.current.charCodeAt(this.cursor-1);if(s<=i&&r<=s&&0!=(t[(s-=r)>>>3]&1<<(7&s)))return!0;this.cursor--}return!1},this.eq_s=function(t){return!(this.limit-this.cursor>>1),o=0,a=e=(l=t[r])[0].length){if(this.cursor=s+l[0].length,l.length<4)return l[2];var g=l[3](this);if(this.cursor=s+l[0].length,g)return l[2]}}while(0<=(r=l[1]));return 0},this.find_among_b=function(t){for(var r=0,i=t.length,s=this.cursor,h=this.limit_backward,e=0,n=0,c=!1;;){for(var u,o=r+(i-r>>1),a=0,l=e=(u=t[r])[0].length){if(this.cursor=s-u[0].length,u.length<4)return u[2];var g=u[3](this);if(this.cursor=s-u[0].length,g)return u[2]}}while(0<=(r=u[1]));return 0},this.replace_s=function(t,r,i){var s=i.length-(r-t);return this.current=this.current.slice(0,t)+i+this.current.slice(r),this.limit+=s,this.cursor>=r?this.cursor+=s:this.cursor>t&&(this.cursor=t),s},this.slice_check=function(){return!(this.bra<0||this.bra>this.ket||this.ket>this.limit||this.limit>this.current.length)},this.slice_from=function(t){var r=!1;return this.slice_check()&&(this.replace_s(this.bra,this.ket,t),r=!0),r},this.slice_del=function(){return this.slice_from("")},this.insert=function(t,r,i){r=this.replace_s(t,r,i);t<=this.bra&&(this.bra+=r),t<=this.ket&&(this.ket+=r)},this.slice_to=function(){var t="";return t=this.slice_check()?this.current.slice(this.bra,this.ket):t},this.assign_to=function(){return this.current.slice(0,this.limit)}}; +var EnglishStemmer=function(){var a=new BaseStemmer,c=[["arsen",-1,-1],["commun",-1,-1],["emerg",-1,-1],["gener",-1,-1],["later",-1,-1],["organ",-1,-1],["past",-1,-1],["univers",-1,-1]],o=[["'",-1,1],["'s'",0,1],["'s",-1,1]],u=[["ied",-1,2],["s",-1,3],["ies",1,2],["sses",1,1],["ss",1,-1],["us",1,-1]],t=[["succ",-1,1],["proc",-1,1],["exc",-1,1]],l=[["even",-1,2],["cann",-1,2],["inn",-1,2],["earr",-1,2],["herr",-1,2],["out",-1,2],["y",-1,1]],n=[["",-1,-1],["ed",0,2],["eed",1,1],["ing",0,3],["edly",0,2],["eedly",4,1],["ingly",0,2]],f=[["",-1,3],["bb",0,2],["dd",0,2],["ff",0,2],["gg",0,2],["bl",0,1],["mm",0,2],["nn",0,2],["pp",0,2],["rr",0,2],["at",0,1],["tt",0,2],["iz",0,1]],_=[["anci",-1,3],["enci",-1,2],["ogi",-1,14],["li",-1,16],["bli",3,12],["abli",4,4],["alli",3,8],["fulli",3,9],["lessli",3,15],["ousli",3,10],["entli",3,5],["aliti",-1,8],["biliti",-1,12],["iviti",-1,11],["tional",-1,1],["ational",14,7],["alism",-1,8],["ation",-1,7],["ization",17,6],["izer",-1,6],["ator",-1,7],["iveness",-1,11],["fulness",-1,9],["ousness",-1,10],["ogist",-1,13]],m=[["icate",-1,4],["ative",-1,6],["alize",-1,3],["iciti",-1,4],["ical",-1,4],["tional",-1,1],["ational",5,2],["ful",-1,5],["ness",-1,5]],b=[["ic",-1,1],["ance",-1,1],["ence",-1,1],["able",-1,1],["ible",-1,1],["ate",-1,1],["ive",-1,1],["ize",-1,1],["iti",-1,1],["al",-1,1],["ism",-1,1],["ion",-1,2],["er",-1,1],["ous",-1,1],["ant",-1,1],["ent",-1,1],["ment",15,1],["ement",16,1]],k=[["e",-1,1],["l",-1,2]],g=[["andes",-1,-1],["atlas",-1,-1],["bias",-1,-1],["cosmos",-1,-1],["early",-1,5],["gently",-1,3],["howe",-1,-1],["idly",-1,2],["news",-1,-1],["only",-1,6],["singly",-1,7],["skies",-1,1],["sky",-1,-1],["ugly",-1,4]],d=[17,64],v=[17,65,16,1],i=[1,17,65,208,1],w=[55,141,2],p=!1,y=0,h=0;function q(){var r=a.limit-a.cursor;return!!(a.out_grouping_b(i,89,121)&&a.in_grouping_b(v,97,121)&&a.out_grouping_b(v,97,121)||(a.cursor=a.limit-r,a.out_grouping_b(v,97,121)&&a.in_grouping_b(v,97,121)&&!(a.cursor>a.limit_backward))||(a.cursor=a.limit-r,a.eq_s_b("past")))}function z(){return h<=a.cursor}function Y(){return y<=a.cursor}this.stem=function(){var r=a.cursor;if(!(()=>{var r;if(a.bra=a.cursor,0!=(r=a.find_among(g))&&(a.ket=a.cursor,!(a.cursora.limit)a.cursor=i;else{a.cursor=e,a.cursor=r,(()=>{p=!1;var r=a.cursor;if(a.bra=a.cursor,!a.eq_s("'")||(a.ket=a.cursor,a.slice_del())){a.cursor=r;r=a.cursor;if(a.bra=a.cursor,a.eq_s("y")){if(a.ket=a.cursor,!a.slice_from("Y"))return;p=!0}a.cursor=r;for(r=a.cursor;;){var i=a.cursor;r:{for(;;){var e=a.cursor;if(a.in_grouping(v,97,121)&&(a.bra=a.cursor,a.eq_s("y"))){a.ket=a.cursor,a.cursor=e;break}if(a.cursor=e,a.cursor>=a.limit)break r;a.cursor++}if(!a.slice_from("Y"))return;p=!0;continue}a.cursor=i;break}a.cursor=r}})(),h=a.limit,y=a.limit;i=a.cursor;r:{var s=a.cursor;if(0==a.find_among(c)){if(a.cursor=s,!a.go_out_grouping(v,97,121))break r;if(a.cursor++,!a.go_in_grouping(v,97,121))break r;a.cursor++}h=a.cursor,a.go_out_grouping(v,97,121)&&(a.cursor++,a.go_in_grouping(v,97,121))&&(a.cursor++,y=a.cursor)}a.cursor=i,a.limit_backward=a.cursor,a.cursor=a.limit;var e=a.limit-a.cursor,r=((()=>{var r=a.limit-a.cursor;if(a.ket=a.cursor,0==a.find_among_b(o))a.cursor=a.limit-r;else if(a.bra=a.cursor,!a.slice_del())return;if(a.ket=a.cursor,0!=(r=a.find_among_b(u)))switch(a.bra=a.cursor,r){case 1:if(a.slice_from("ss"))break;return;case 2:r:{var i=a.limit-a.cursor,e=a.cursor-2;if(!(e{a.ket=a.cursor,o=a.find_among_b(n),a.bra=a.cursor;r:{var r=a.limit-a.cursor;i:{switch(o){case 1:var i=a.limit-a.cursor;e:{var e=a.limit-a.cursor;if(0==a.find_among_b(t)||a.cursor>a.limit_backward){if(a.cursor=a.limit-e,!z())break e;if(!a.slice_from("ee"))return}}a.cursor=a.limit-i;break;case 2:break i;case 3:if(0==(o=a.find_among_b(l)))break i;switch(o){case 1:var s=a.limit-a.cursor;if(!a.out_grouping_b(v,97,121))break i;if(a.cursor>a.limit_backward)break i;if(a.cursor=a.limit-s,a.bra=a.cursor,a.slice_from("ie"))break;return;case 2:if(a.cursor>a.limit_backward)break i}}break r}a.cursor=a.limit-r;var c=a.limit-a.cursor;if(!a.go_out_grouping_b(v,97,121))return;if(a.cursor--,a.cursor=a.limit-c,!a.slice_del())return;a.ket=a.cursor,a.bra=a.cursor;var o,c=a.limit-a.cursor;switch(o=a.find_among_b(f)){case 1:return a.slice_from("e");case 2:var u=a.limit-a.cursor;if(a.in_grouping_b(d,97,111)&&!(a.cursor>a.limit_backward))return;a.cursor=a.limit-u;break;case 3:return a.cursor!=h||(u=a.limit-a.cursor,q()&&(a.cursor=a.limit-u,a.slice_from("e")))}if(a.cursor=a.limit-c,a.ket=a.cursor,a.cursor<=a.limit_backward)return;if(a.cursor--,a.bra=a.cursor,!a.slice_del())return}})(),a.cursor=a.limit-r,a.limit-a.cursor),r=(a.ket=a.cursor,e=a.limit-a.cursor,(a.eq_s_b("y")||(a.cursor=a.limit-e,a.eq_s_b("Y")))&&(a.bra=a.cursor,a.out_grouping_b(v,97,121))&&a.cursor>a.limit_backward&&a.slice_from("i"),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(_))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ence"))break;return;case 3:if(a.slice_from("ance"))break;return;case 4:if(a.slice_from("able"))break;return;case 5:if(a.slice_from("ent"))break;return;case 6:if(a.slice_from("ize"))break;return;case 7:if(a.slice_from("ate"))break;return;case 8:if(a.slice_from("al"))break;return;case 9:if(a.slice_from("ful"))break;return;case 10:if(a.slice_from("ous"))break;return;case 11:if(a.slice_from("ive"))break;return;case 12:if(a.slice_from("ble"))break;return;case 13:if(a.slice_from("og"))break;return;case 14:if(!a.eq_s_b("l"))return;if(a.slice_from("og"))break;return;case 15:if(a.slice_from("less"))break;return;case 16:if(!a.in_grouping_b(w,99,116))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.limit-a.cursor),i=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(m))&&(a.bra=a.cursor,z()))switch(r){case 1:if(a.slice_from("tion"))break;return;case 2:if(a.slice_from("ate"))break;return;case 3:if(a.slice_from("al"))break;return;case 4:if(a.slice_from("ic"))break;return;case 5:if(a.slice_del())break;return;case 6:if(!Y())return;if(a.slice_del())break}})(),a.cursor=a.limit-e,a.limit-a.cursor),r=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(b))&&(a.bra=a.cursor,Y()))switch(r){case 1:if(a.slice_del())break;return;case 2:var i=a.limit-a.cursor;if(!a.eq_s_b("s")&&(a.cursor=a.limit-i,!a.eq_s_b("t")))return;if(a.slice_del())break}})(),a.cursor=a.limit-i,a.limit-a.cursor),e=((()=>{var r;if(a.ket=a.cursor,0!=(r=a.find_among_b(k)))switch(a.bra=a.cursor,r){case 1:if(!Y()){if(!z())return;var i=a.limit-a.cursor;if(q())return;a.cursor=a.limit-i}if(a.slice_del())break;return;case 2:if(!Y())return;if(!a.eq_s_b("l"))return;if(a.slice_del())break}})(),a.cursor=a.limit-r,a.cursor=a.limit_backward,a.cursor);(()=>{if(p)for(;;){var r=a.cursor;r:{for(;;){var i=a.cursor;if(a.bra=a.cursor,a.eq_s("Y")){a.ket=a.cursor,a.cursor=i;break}if(a.cursor=i,a.cursor>=a.limit)break r;a.cursor++}if(a.slice_from("y"))continue;return}a.cursor=r;break}})(),a.cursor=e}}return!0},this.stemWord=function(r){return a.setCurrent(r),this.stem(),a.getCurrent()}}; +window.Stemmer = EnglishStemmer; diff --git a/_static/minus.png b/_static/minus.png new file mode 100644 index 0000000..d96755f Binary files /dev/null and b/_static/minus.png differ diff --git a/_static/opensearch.xml b/_static/opensearch.xml new file mode 100644 index 0000000..6b0506f --- /dev/null +++ b/_static/opensearch.xml @@ -0,0 +1,11 @@ + + + tzst + Search tzst 1.3.3 Documentation + utf-8 + + tzst 1.3.3 Documentation + https://tzst.xi-xu.me//_static/favicon.ico + + \ No newline at end of file diff --git a/_static/plus.png b/_static/plus.png new file mode 100644 index 0000000..7107cec Binary files /dev/null and b/_static/plus.png differ diff --git a/_static/pygments.css b/_static/pygments.css new file mode 100644 index 0000000..6f8b210 --- /dev/null +++ b/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #f8f8f8; } +.highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #F00 } /* Error */ +.highlight .k { color: #008000; font-weight: bold } /* Keyword */ +.highlight .o { color: #666 } /* Operator */ +.highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #9C6500 } /* Comment.Preproc */ +.highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #E40000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #008400 } /* Generic.Inserted */ +.highlight .go { color: #717171 } /* Generic.Output */ +.highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #04D } /* Generic.Traceback */ +.highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #008000 } /* Keyword.Pseudo */ +.highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #B00040 } /* Keyword.Type */ +.highlight .m { color: #666 } /* Literal.Number */ +.highlight .s { color: #BA2121 } /* Literal.String */ +.highlight .na { color: #687822 } /* Name.Attribute */ +.highlight .nb { color: #008000 } /* Name.Builtin */ +.highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ +.highlight .no { color: #800 } /* Name.Constant */ +.highlight .nd { color: #A2F } /* Name.Decorator */ +.highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +.highlight .nf { color: #00F } /* Name.Function */ +.highlight .nl { color: #767600 } /* Name.Label */ +.highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #19177C } /* Name.Variable */ +.highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #BBB } /* Text.Whitespace */ +.highlight .mb { color: #666 } /* Literal.Number.Bin */ +.highlight .mf { color: #666 } /* Literal.Number.Float */ +.highlight .mh { color: #666 } /* Literal.Number.Hex */ +.highlight .mi { color: #666 } /* Literal.Number.Integer */ +.highlight .mo { color: #666 } /* Literal.Number.Oct */ +.highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +.highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +.highlight .sc { color: #BA2121 } /* Literal.String.Char */ +.highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +.highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +.highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +.highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +.highlight .sx { color: #008000 } /* Literal.String.Other */ +.highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +.highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +.highlight .ss { color: #19177C } /* Literal.String.Symbol */ +.highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #00F } /* Name.Function.Magic */ +.highlight .vc { color: #19177C } /* Name.Variable.Class */ +.highlight .vg { color: #19177C } /* Name.Variable.Global */ +.highlight .vi { color: #19177C } /* Name.Variable.Instance */ +.highlight .vm { color: #19177C } /* Name.Variable.Magic */ +.highlight .il { color: #666 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/robots.txt b/_static/robots.txt new file mode 100644 index 0000000..9817b58 --- /dev/null +++ b/_static/robots.txt @@ -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 diff --git a/_static/searchtools.js b/_static/searchtools.js new file mode 100644 index 0000000..e29b1c7 --- /dev/null +++ b/_static/searchtools.js @@ -0,0 +1,693 @@ +/* + * Sphinx JavaScript utilities for the full-text search. + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename, kind] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +// Global search result kind enum, used by themes to style search results. +// prettier-ignore +class SearchResultKind { + static get index() { return "index"; } + static get object() { return "object"; } + static get text() { return "text"; } + static get title() { return "title"; } +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _escapeHTML = (text) => { + return text + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +}; + +const _displayItem = (item, searchTerms, highlightTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + const contentRoot = document.documentElement.dataset.content_root; + + const [docName, title, anchor, descr, score, _filename, kind] = item; + + let listItem = document.createElement("li"); + // Add a class representing the item's type: + // can be used by a theme's CSS selector for styling + // See SearchResultKind for the class names. + listItem.classList.add(`kind-${kind}`); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = contentRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = contentRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = _escapeHTML(title); + if (descr) { + listItem.appendChild(document.createElement("span")).innerHTML = + ` (${_escapeHTML(descr)})`; + // highlight search terms in the description + if (SPHINX_HIGHLIGHT_ENABLED) + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + highlightTerms.forEach((term) => + _highlightText(listItem, term, "highlighted"), + ); + } else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms, anchor), + ); + // highlight search terms in the summary + if (SPHINX_HIGHLIGHT_ENABLED) + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + highlightTerms.forEach((term) => + _highlightText(listItem, term, "highlighted"), + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.", + ); + else + Search.status.innerText = Documentation.ngettext( + "Search finished, found one page matching the search query.", + "Search finished, found ${resultCount} pages matching the search query.", + resultCount, + ).replace("${resultCount}", resultCount); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms, + highlightTerms, +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms, highlightTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms, highlightTerms), + 5, + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; +// Helper function used by query() to order search results. +// Each input is an array of [docname, title, anchor, descr, score, filename, kind]. +// Order the results by score (in opposite order of appearance, since the +// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically. +const _orderResultsByScoreThenName = (a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => + query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter((term) => term); // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString, anchor) => { + const htmlElement = new DOMParser().parseFromString( + htmlString, + "text/html", + ); + for (const removalQuery of [".headerlink", "script", "style"]) { + htmlElement.querySelectorAll(removalQuery).forEach((el) => { + el.remove(); + }); + } + if (anchor) { + const anchorContent = htmlElement.querySelector( + `[role="main"] ${anchor}`, + ); + if (anchorContent) return anchorContent.textContent; + + console.warn( + `Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`, + ); + } + + // if anchor not specified or not found, fall back to main content + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent) return docContent.textContent; + + console.warn( + "Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template.", + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.setAttribute("role", "list"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + _parseQuery: (query) => { + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords set is from language_data.js + if (stopwords.has(queryTermLower) || queryTerm.match(/^\d+$/)) return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { + // SPHINX_HIGHLIGHT_ENABLED is set in sphinx_highlight.js + localStorage.setItem( + "sphinx_highlight_terms", + [...highlightTerms].join(" "), + ); + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + return [query, searchTerms, excludedTerms, highlightTerms, objectTerms]; + }, + + /** + * execute search (requires search index to be loaded) + */ + _performSearch: ( + query, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // Collect multiple result groups to be sorted separately and then ordered. + // Each is an array of [docname, title, anchor, descr, score, filename, kind]. + const normalResults = []; + const nonMainIndexResults = []; + + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase().trim(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if ( + title.toLowerCase().trim().includes(queryLower) + && queryLower.length >= title.length / 2 + ) { + for (const [file, id] of foundTitles) { + const score = Math.round( + (Scorer.title * queryLower.length) / title.length, + ); + const boost = titles[file] === title ? 1 : 0; // add a boost for document titles + normalResults.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score + boost, + filenames[file], + SearchResultKind.title, + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && queryLower.length >= entry.length / 2) { + for (const [file, id, isMain] of foundEntries) { + const score = Math.round((100 * queryLower.length) / entry.length); + const result = [ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + SearchResultKind.index, + ]; + if (isMain) { + normalResults.push(result); + } else { + nonMainIndexResults.push(result); + } + } + } + } + + // lookup as object + objectTerms.forEach((term) => + normalResults.push(...Search.performObjectSearch(term, objectTerms)), + ); + + // lookup as search terms in fulltext + normalResults.push( + ...Search.performTermsSearch(searchTerms, excludedTerms), + ); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) { + normalResults.forEach((item) => (item[4] = Scorer.score(item))); + nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item))); + } + + // Sort each group of results by score and then alphabetically by name. + normalResults.sort(_orderResultsByScoreThenName); + nonMainIndexResults.sort(_orderResultsByScoreThenName); + + // Combine the result groups in (reverse) order. + // Non-main index entries are typically arbitrary cross-references, + // so display them after other results. + let results = [...nonMainIndexResults, ...normalResults]; + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result + .slice(0, 4) + .concat([result[5]]) + .map((v) => String(v)) + .join(","); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + return results.reverse(); + }, + + query: (query) => { + const [ + searchQuery, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ] = Search._parseQuery(query); + const results = Search._performSearch( + searchQuery, + searchTerms, + excludedTerms, + highlightTerms, + objectTerms, + ); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms, highlightTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4]; + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + SearchResultKind.object, + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => objectSearchCallback(prefix, array)), + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + // find documents, if any, containing the query word in their text/title term indices + // use Object.hasOwnProperty to avoid mismatching against prototype properties + const arr = [ + { + files: terms.hasOwnProperty(word) ? terms[word] : undefined, + score: Scorer.term, + }, + { + files: titleTerms.hasOwnProperty(word) ? titleTerms[word] : undefined, + score: Scorer.title, + }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + if (!terms.hasOwnProperty(word)) { + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + } + if (!titleTerms.hasOwnProperty(word)) { + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord)) + arr.push({ files: titleTerms[term], score: Scorer.partialTitle }); + }); + } + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, new Map()); + const fileScores = scoreMap.get(file); + fileScores.set(word, record.score); + }); + }); + + // create the mapping + files.forEach((file) => { + if (!fileMap.has(file)) fileMap.set(file, [word]); + else if (fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2, + ).length; + if ( + wordList.length !== searchTerms.size + && wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file + || titleTerms[term] === file + || (terms[term] || []).includes(file) + || (titleTerms[term] || []).includes(file), + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file).get(w))); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + SearchResultKind.text, + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords, anchor) => { + const text = Search.htmlToText(htmlText, anchor); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = + top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/_static/sphinx_highlight.js b/_static/sphinx_highlight.js new file mode 100644 index 0000000..a74e103 --- /dev/null +++ b/_static/sphinx_highlight.js @@ -0,0 +1,159 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true; + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 + && !parent.classList.contains(className) + && !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + const rest = document.createTextNode(val.substr(pos + text.length)); + parent.insertBefore(span, parent.insertBefore(rest, node.nextSibling)); + node.nodeValue = val.substr(0, pos); + /* There may be more occurrences of search term in this node. So call this + * function recursively on the remaining fragment. + */ + _highlight(rest, addItems, text, className); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect", + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target), + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms"); + // Update history only if '?highlight' is present; otherwise it + // clears text fragments (not set in window.location by the browser) + if (url.searchParams.has("highlight")) { + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + } + + // get individual terms from highlight string + const terms = highlight + .toLowerCase() + .split(/\s+/) + .filter((x) => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '", + ), + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms"); + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) + return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) + return; + if ( + DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + && event.key === "Escape" + ) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(() => { + /* Do not call highlightSearchWords() when we are on the search page. + * It will highlight words from the *previous* search query. + */ + if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords(); + SphinxHighlight.initEscapeListener(); +}); diff --git a/_static/tzst-logo.png b/_static/tzst-logo.png new file mode 100644 index 0000000..054563f Binary files /dev/null and b/_static/tzst-logo.png differ diff --git a/_static/tzst-square-logo.png b/_static/tzst-square-logo.png new file mode 100644 index 0000000..9b235f2 Binary files /dev/null and b/_static/tzst-square-logo.png differ diff --git a/api/cli.html b/api/cli.html new file mode 100644 index 0000000..f47f3c2 --- /dev/null +++ b/api/cli.html @@ -0,0 +1,967 @@ + + + + + + + + + + + + + + + + + + + + CLI API — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

CLI API

+

The command-line interface module provides comprehensive functionality for the tzst CLI tool, including argument parsing, command execution, and interactive features.

+

Command-line interface for tzst.

+
+
+tzst.cli.cmd_add(args) → int[source]
+

Command handler for creating/adding to archives.

+

Processes the ‘add’, ‘create’, or ‘a’ CLI commands to create new tzst archives +with the specified files and directories. Uses atomic file operations by +default to ensure data integrity.

+
+
Parameters:
+

args – Parsed command line arguments containing: +- archive (str): Path to the archive file to create +- files (list[str]): List of files/directories to add +- compression_level (int, optional): Compression level 1-22 +- no_atomic (bool, optional): Disable atomic file operations

+
+
Returns:
+

+
Exit code (0 for success, non-zero for failure)
    +
  • 0: Success

  • +
  • 1: File not found, invalid parameters, or archive operation failed

  • +
  • 130: Operation interrupted by user (Ctrl+C)

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

This function uses atomic file operations by default, creating the +archive in a temporary file first, then atomically moving it to the +final location to prevent incomplete archives.

+
+
+

See also

+

tzst.create_archive(): The underlying function for archive creation +TzstArchive.add(): The core method for adding files to archives

+
+
+ +
+
+tzst.cli.cmd_extract_flat(args) → int[source]
+

Command handler for flat extraction without directory structure.

+

Processes the ‘extract-flat’ or ‘e’ CLI commands to extract files from +tzst archives without preserving directory structure (all files extracted +to a single directory).

+
+
Parameters:
+

args – Parsed command line arguments containing: +- archive (str): Path to the archive file to extract +- output (str, optional): Output directory path +- files (list[str], optional): Specific files to extract +- streaming (bool, optional): Use streaming mode for large archives +- filter (str, optional): Security filter (‘data’, ‘tar’, ‘fully_trusted’)

+
+
Returns:
+

+
Exit code (0 for success, non-zero for failure)
    +
  • 0: Success

  • +
  • 1: File not found, decompression failed, or archive operation failed

  • +
  • 130: Operation interrupted by user (Ctrl+C)

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Warning

+

Flat extraction may cause filename conflicts if multiple files have +the same name but are in different directories within the archive.

+
+
+

See also

+

tzst.extract_archive(): The underlying function for extraction +TzstArchive.extract(): The core method for extracting from archives +cmd_extract_full(): For extraction with directory structure

+
+
+ +
+
+tzst.cli.cmd_extract_full(args) → int[source]
+

Command handler for extracting archives with full directory structure.

+

Processes the ‘extract’ or ‘x’ CLI commands to extract files from tzst +archives while preserving the original directory structure.

+
+
Parameters:
+

args – Parsed command line arguments containing: +- archive (str): Path to the archive file to extract +- output (str, optional): Output directory path +- files (list[str], optional): Specific files to extract +- streaming (bool, optional): Use streaming mode for large archives +- filter (str, optional): Security filter (‘data’, ‘tar’, ‘fully_trusted’)

+
+
Returns:
+

+
Exit code (0 for success, non-zero for failure)
    +
  • 0: Success

  • +
  • 1: File not found, decompression failed, or archive operation failed

  • +
  • 130: Operation interrupted by user (Ctrl+C)

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

Uses the ‘data’ security filter by default for safe extraction from +untrusted sources. Streaming mode is recommended for archives > 100MB.

+
+
+

See also

+

tzst.extract_archive(): The underlying function for extraction +TzstArchive.extract(): The core method for extracting from archives +cmd_extract_flat(): For flat extraction without directory structure

+
+
+ +
+
+tzst.cli.cmd_list(args) → int[source]
+

Command handler for listing archive contents.

+

Processes the ‘list’ or ‘l’ CLI commands to display the contents of tzst +archives. Supports both simple and verbose listing modes.

+
+
Parameters:
+

args – Parsed command line arguments containing: +- archive (str): Path to the archive file to list +- verbose (bool, optional): Show detailed file information +- streaming (bool, optional): Use streaming mode for large archives

+
+
Returns:
+

+
Exit code (0 for success, non-zero for failure)
    +
  • 0: Success

  • +
  • 1: File not found, decompression failed, or archive operation failed

  • +
  • 130: Operation interrupted by user (Ctrl+C)

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

Verbose mode displays file permissions, sizes, modification times, +and other metadata. Streaming mode is recommended for archives > 100MB.

+
+
+

See also

+

tzst.list_archive(): The underlying function for listing contents +TzstArchive.list(): The core method for listing archive contents

+
+
+ +
+
+tzst.cli.cmd_test(args) → int[source]
+

Command handler for testing archive integrity.

+

Processes the ‘test’ or ‘t’ CLI commands to verify the integrity of tzst +archives by attempting to read all files and checking for corruption.

+
+
Parameters:
+

args – Parsed command line arguments containing: +- archive (str): Path to the archive file to test +- streaming (bool, optional): Use streaming mode for large archives

+
+
Returns:
+

+
Exit code (0 for success, non-zero for failure)
    +
  • 0: Archive passed integrity test

  • +
  • 1: Archive failed integrity test, file not found, or operation failed

  • +
  • 130: Operation interrupted by user (Ctrl+C)

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

This command verifies that the archive can be read and all files +can be decompressed without errors. Streaming mode is recommended +for archives > 100MB to reduce memory usage.

+
+
+

See also

+

tzst.test_archive(): The underlying function for integrity testing +TzstArchive.test(): The core method for testing archive integrity

+
+
+ +
+
+tzst.cli.cmd_version(args) → int[source]
+

Command handler for version display.

+
+
Returns:
+

Exit code (always 0)

+
+
Return type:
+

int

+
+
+
+ +
+
+tzst.cli.create_parser() → ArgumentParser[source]
+

Create and configure the command-line argument parser.

+

Sets up the argparse ArgumentParser with all subcommands and their +respective arguments for the tzst CLI interface. Includes comprehensive +help text and command reference documentation.

+
+
Returns:
+

Configured parser ready for argument parsing

+
+
Return type:
+

argparse.ArgumentParser

+
+
+
+

Note

+

The parser is configured with RawDescriptionHelpFormatter to preserve +formatting in the epilog help text, and includes detailed command +reference and security notes.

+
+
+
Commands Created:
    +
  • a, add, create: Archive creation with compression levels

  • +
  • x, extract: Full extraction with directory structure

  • +
  • e, extract-flat: Flat extraction without directories

  • +
  • l, list: Archive content listing

  • +
  • t, test: Archive integrity testing

  • +
+
+
+
+

See also

+

main(): The main entry point that uses this parser

+
+
+ +
+
+tzst.cli.format_size(size: int) → str[source]
+

Format file size in human-readable format.

+

Converts byte values to human-readable format using standard units +(B, KB, MB, GB, TB, PB) with appropriate decimal places.

+
+
Parameters:
+

size (int) – Size in bytes to format

+
+
Returns:
+

Formatted size string with units (e.g., “1.5 KB”, “2.3 GB”)

+
+
Return type:
+

str

+
+
+

Examples

+
>>> format_size(1024)
+'  1.0 KB'
+>>> format_size(1536)
+'  1.5 KB'
+>>> format_size(2048576)
+'  2.0 MB'
+
+
+
+ +
+
+tzst.cli.main(argv: list[str] | None = None) → int[source]
+

Main entry point for the tzst command-line interface.

+

Processes command-line arguments and dispatches to appropriate command +handlers. Displays the version banner and provides error handling for +the overall CLI execution.

+
+
Parameters:
+

argv (list[str] | None, optional) – Command line arguments to parse. +If None, uses sys.argv. Defaults to None.

+
+
Returns:
+

+
Exit code for the program
    +
  • 0: Success

  • +
  • 1: Invalid compression level, filter, or command error

  • +
  • 2: Argument parsing error (help, unknown options)

  • +
  • Other codes: Specific to individual command handlers

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

This function serves as the console script entry point defined in +pyproject.toml. It displays the version banner before executing +any commands.

+
+
+

See also

+

create_parser(): Creates the argument parser used by this function

+
+
+ +
+
+tzst.cli.print_banner() → None[source]
+

Print the version and copyright banner.

+

Displays the tzst version number and copyright information to stdout. +Used as a header for CLI operations.

+
+
Returns:
+

None

+
+
+
+ +
+
+tzst.cli.validate_compression_level(value: str) → int[source]
+

Validate and return compression level.

+
+
Parameters:
+

value – String value from command line

+
+
Returns:
+

Valid compression level (1-22)

+
+
Return type:
+

int

+
+
Raises:
+

argparse.ArgumentTypeError – If value is not a valid compression level

+
+
+
+ +
+

Overview

+

The tzst CLI provides a powerful command-line interface for archive operations with intuitive commands and comprehensive options. The interface is designed for both interactive use and scripting, with robust error handling and user-friendly output.

+
+

Core Commands

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Command

Aliases

Description

Streaming Support

a

add, create

Create or add to archive

N/A

x

extract

Extract with full paths

--streaming

e

extract-flat

Extract without directory structure

--streaming

l

list

List archive contents

--streaming

t

test

Test archive integrity

--streaming

+
+
+

Key Features

+
    +
  • Intuitive Commands: Simple, memorable command aliases (a, x, e, l, t)

  • +
  • Streaming Support: Memory-efficient processing for large archives

  • +
  • Interactive Conflict Resolution: User-friendly prompts for handling file conflicts

  • +
  • Comprehensive Options: Fine-grained control over compression, extraction, and security

  • +
  • Cross-Platform: Consistent behavior across Windows, macOS, and Linux

  • +
+
+
+
+

Main Functions

+
+

main

+
+
+tzst.cli.main(argv: list[str] | None = None) → int[source]
+

Main entry point for the tzst command-line interface.

+

Processes command-line arguments and dispatches to appropriate command +handlers. Displays the version banner and provides error handling for +the overall CLI execution.

+
+
Parameters:
+

argv (list[str] | None, optional) – Command line arguments to parse. +If None, uses sys.argv. Defaults to None.

+
+
Returns:
+

+
Exit code for the program
    +
  • 0: Success

  • +
  • 1: Invalid compression level, filter, or command error

  • +
  • 2: Argument parsing error (help, unknown options)

  • +
  • Other codes: Specific to individual command handlers

  • +
+
+
+

+
+
Return type:
+

int

+
+
+
+

Note

+

This function serves as the console script entry point defined in +pyproject.toml. It displays the version banner before executing +any commands.

+
+
+

See also

+

create_parser(): Creates the argument parser used by this function

+
+
+ +

The main entry point for the CLI application. Handles argument parsing, command execution, and comprehensive error reporting.

+

Key Features:

+
    +
  • Robust argument validation and error handling

  • +
  • Support for all archive operations

  • +
  • Consistent exit codes for scripting

  • +
  • User-friendly error messages

  • +
+

Exit Codes:

+
    +
  • 0: Success

  • +
  • 1: General error (file not found, archive corruption, etc.)

  • +
  • 2: Argument parsing error

  • +
  • 130: Interrupted by user (Ctrl+C)

  • +
+
+
+

create_parser

+
+
+tzst.cli.create_parser() → ArgumentParser[source]
+

Create and configure the command-line argument parser.

+

Sets up the argparse ArgumentParser with all subcommands and their +respective arguments for the tzst CLI interface. Includes comprehensive +help text and command reference documentation.

+
+
Returns:
+

Configured parser ready for argument parsing

+
+
Return type:
+

argparse.ArgumentParser

+
+
+
+

Note

+

The parser is configured with RawDescriptionHelpFormatter to preserve +formatting in the epilog help text, and includes detailed command +reference and security notes.

+
+
+
Commands Created:
    +
  • a, add, create: Archive creation with compression levels

  • +
  • x, extract: Full extraction with directory structure

  • +
  • e, extract-flat: Flat extraction without directories

  • +
  • l, list: Archive content listing

  • +
  • t, test: Archive integrity testing

  • +
+
+
+
+

See also

+

main(): The main entry point that uses this parser

+
+
+ +

Creates and configures the comprehensive argument parser for the CLI interface.

+

Supported Arguments:

+
    +
  • Global: --version, --help

  • +
  • Archive Creation: -l/--level, --no-atomic

  • +
  • Extraction: -o/--output, --streaming, --filter, --conflict-resolution

  • +
  • Listing: -v/--verbose, --streaming

  • +
  • Testing: --streaming

  • +
+
+
+
+

Command Handlers

+

The CLI implements dedicated command handlers for each operation, providing specialized functionality and error handling.

+
+

Archive Creation Commands

+
+

cmd_add

+

Creates new archives from files and directories with configurable compression and atomic operations.

+

Features:

+
    +
  • Configurable compression levels (1-22)

  • +
  • Atomic file operations (default) for safe creation

  • +
  • Recursive directory processing

  • +
  • Path validation and normalization

  • +
+

Usage Examples:

+
# Basic archive creation
+tzst a backup.tzst documents/ photos/
+
+# High compression with atomic disabled
+tzst a backup.tzst files/ -l 15 --no-atomic
+
+
+
+
+
+

Extraction Commands

+
+

cmd_extract_full

+

Extracts archives preserving complete directory structure with advanced conflict resolution.

+

Features:

+
    +
  • Preserves full directory paths

  • +
  • Multiple conflict resolution strategies

  • +
  • Security filters for safe extraction

  • +
  • Selective file extraction

  • +
  • Streaming mode for large archives

  • +
+
+
+

cmd_extract_flat

+

Extracts archives flattening all files to a single directory, useful for consolidating files.

+

Features:

+
    +
  • Flattens directory structure

  • +
  • Automatic conflict resolution for filename collisions

  • +
  • Preserves file content while simplifying structure

  • +
  • Same security and streaming features as full extraction

  • +
+
+
+
+

Management Commands

+
+

cmd_list

+

Lists archive contents with optional detailed information and streaming support.

+

Features:

+
    +
  • Simple or verbose listing modes

  • +
  • Human-readable file sizes

  • +
  • Modification timestamps

  • +
  • Streaming mode for memory efficiency

  • +
+
+
+

cmd_test

+

Tests archive integrity and validity with comprehensive error reporting.

+

Features:

+
    +
  • Complete archive validation

  • +
  • Streaming mode support

  • +
  • Detailed error reporting

  • +
  • Exit codes for automated testing

  • +
+
+
+

cmd_version

+

Displays version information and system details.

+
+
+
+
+

Utility Functions

+ +
+

format_size

+
+
+tzst.cli.format_size(size: int) → str[source]
+

Format file size in human-readable format.

+

Converts byte values to human-readable format using standard units +(B, KB, MB, GB, TB, PB) with appropriate decimal places.

+
+
Parameters:
+

size (int) – Size in bytes to format

+
+
Returns:
+

Formatted size string with units (e.g., “1.5 KB”, “2.3 GB”)

+
+
Return type:
+

str

+
+
+

Examples

+
>>> format_size(1024)
+'  1.0 KB'
+>>> format_size(1536)
+'  1.5 KB'
+>>> format_size(2048576)
+'  2.0 MB'
+
+
+
+ +

Formats file sizes in a human-readable format (bytes, KB, MB, GB).

+
+
+

validate_compression_level

+
+
+tzst.cli.validate_compression_level(value: str) → int[source]
+

Validate and return compression level.

+
+
Parameters:
+

value – String value from command line

+
+
Returns:
+

Valid compression level (1-22)

+
+
Return type:
+

int

+
+
Raises:
+

argparse.ArgumentTypeError – If value is not a valid compression level

+
+
+
+ +

Validates compression level arguments and converts them to integers.

+
+
+
+

Interactive Features

+

The CLI includes interactive conflict resolution for file extraction conflicts, allowing users to choose how to handle existing files during extraction operations.

+
+

Conflict Resolution Options

+
    +
  • Replace: Overwrite the existing file

  • +
  • Skip: Keep the existing file, skip extraction

  • +
  • Replace All: Apply replace to all subsequent conflicts

  • +
  • Skip All: Apply skip to all subsequent conflicts

  • +
  • Auto-rename All: Automatically rename conflicting files

  • +
  • Exit: Stop extraction process

  • +
+
+
+

Security Considerations

+

The CLI implements multiple security filters for safe extraction:

+
    +
  • data filter (default): Safest option, blocks potentially dangerous archive members

  • +
  • tar filter: Preserves more tar features while maintaining basic security

  • +
  • fully_trusted filter: No restrictions, use only with completely trusted archives

  • +
+
+
+

Performance Options

+
    +
  • Streaming Mode: Use --streaming for memory-efficient processing of large archives (>100MB)

  • +
  • Compression Levels: Choose from 1 (fastest) to 22 (maximum compression)

  • +
  • Atomic Operations: Default behavior uses temporary files for safe archive creation

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/core.html b/api/core.html new file mode 100644 index 0000000..b9a04c3 --- /dev/null +++ b/api/core.html @@ -0,0 +1,1032 @@ + + + + + + + + + + + + + + + + + + + + Core API — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Core API

+

The core module provides the main functionality for working with tzst archives, including the primary TzstArchive class and high-level convenience functions.

+

Core functionality for tzst archives.

+
+
+class tzst.core.ConflictResolution(*values)[source]
+

Bases: Enum

+

Enum for conflict resolution strategies.

+
+
+ASK = 'ask'
+
+ +
+
+AUTO_RENAME = 'auto_rename'
+
+ +
+
+AUTO_RENAME_ALL = 'auto_rename_all'
+
+ +
+
+EXIT = 'exit'
+
+ +
+
+REPLACE = 'replace'
+
+ +
+
+REPLACE_ALL = 'replace_all'
+
+ +
+
+SKIP = 'skip'
+
+ +
+
+SKIP_ALL = 'skip_all'
+
+ +
+ +
+
+class tzst.core.ConflictResolutionState(initial_resolution: ConflictResolution | None = None)[source]
+

Bases: object

+

State management for conflict resolution during extraction.

+
+
+__init__(initial_resolution: ConflictResolution | None = None)[source]
+
+ +
+
+property apply_to_all: bool
+

Check if the current resolution applies to all future conflicts.

+
+ +
+
+property current_resolution: ConflictResolution | None
+

Get the current resolution state.

+
+ +
+
+should_continue() → bool[source]
+

Check if extraction should continue.

+
+ +
+
+update_resolution(resolution: ConflictResolution) → None[source]
+

Update the global resolution state.

+
+ +
+ +
+
+class tzst.core.TzstArchive(filename: str | Path, mode: str = 'r', compression_level: int = 3, streaming: bool = False)[source]
+

Bases: object

+

A class for handling .tzst/.tar.zst archives.

+
+
+__enter__()[source]
+

Enter context manager.

+
+ +
+
+__exit__(exc_type, exc_val, exc_tb)[source]
+

Exit context manager.

+
+ +
+
+__init__(filename: str | Path, mode: str = 'r', compression_level: int = 3, streaming: bool = False)[source]
+

Initialize a TzstArchive.

+
+
Parameters:
+
    +
  • filename – Path to the archive file

  • +
  • mode – Open mode (‘r’, ‘w’, ‘a’)

  • +
  • compression_level – Zstandard compression level (1-22)

  • +
  • streaming – If True, use streaming mode for reading (reduces memory usage +for very large archives but may limit some tarfile operations +that require seeking. Recommended for archives > 100MB)

  • +
+
+
+
+ +
+
+add(name: str | Path, arcname: str | None = None, recursive: bool = True)[source]
+

Add a file or directory to the archive.

+
+
Parameters:
+
    +
  • name – Path to file or directory to add

  • +
  • arcname – Alternative name for the file in the archive

  • +
  • recursive – If True, add directories recursively

  • +
+
+
+
+

See also

+

create_archive(): Convenience function for creating archives

+
+
+ +
+
+close()[source]
+

Close the archive.

+
+ +
+
+extract(member: str | None = None, path: str | Path = '.', set_attrs: bool = True, numeric_owner: bool = False, filter: str | Callable | None = 'data')[source]
+

Extract files from the archive.

+
+
Parameters:
+
    +
  • member – Specific member to extract (None for all)

  • +
  • path – Destination directory

  • +
  • set_attrs – Whether to set file attributes

  • +
  • numeric_owner – Whether to use numeric owner

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives (default, recommended) +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (use only for trusted archives) +- None: Use default behavior (may show deprecation warning in Python 3.12+) +- callable: Custom filter function

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. +The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+
+
+

Note

+

In streaming mode, extracting specific members is not supported. +Some extraction operations may be limited due to the sequential +nature of streaming mode.

+
+
+

See also

+

extract_archive(): Convenience function for extracting archives

+
+
+ +
+
+extractall(path: str | Path = '.', members: list[TarInfo] | None = None, *, numeric_owner: bool = False, filter: str | Callable | None = 'data')[source]
+

Extract all members from the archive.

+
+
Parameters:
+
    +
  • path – Destination directory (default: current directory)

  • +
  • members – Specific members to extract (None for all)

  • +
  • numeric_owner – Whether to use numeric owner IDs

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (trusted archives only) +- None: Use default behavior (may show deprecation warning) +- callable: Custom filter function

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. +The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+
+
+

Note

+

In streaming mode, extracting specific members is not supported. +Some extraction operations may be limited due to the sequential +nature of streaming mode.

+
+
+

See also

+

extract(): Extract a single member from the archive +extract_archive(): Convenience function for extracting archives

+
+
+ +
+
+extractfile(member: str | TarInfo)[source]
+

Extract a file-like object from the archive.

+
+
Parameters:
+

member – Member name or TarInfo object

+
+
Returns:
+

File-like object or None if member is not a file

+
+
+
+ +
+
+getmembers() → list[TarInfo][source]
+

Get list of all members in the archive.

+
+ +
+
+getnames() → list[str][source]
+

Get list of all member names in the archive.

+
+ +
+
+list(verbose: bool = False) → list[dict][source]
+

List contents of the archive.

+
+
Parameters:
+

verbose – Include detailed information

+
+
Returns:
+

List of file information dictionaries

+
+
+
+

See also

+

getmembers(): Get TarInfo objects for all archive members +getnames(): Get names of all archive members +list_archive(): Convenience function for listing archives

+
+
+ +
+
+open()[source]
+

Open the archive.

+
+

See also

+

close(): Method to close the archive

+
+
+ +
+
+test() → bool[source]
+

Test the integrity of the archive.

+
+
Returns:
+

True if archive is valid, False otherwise

+
+
+
+

See also

+

test_archive(): Convenience function for testing archive integrity

+
+
+ +
+ +
+
+tzst.core.create_archive(archive_path: str | Path, files: Sequence[str | Path], compression_level: int = 3, use_temp_file: bool = True) → None[source]
+

Create a new .tzst archive with atomic file operations.

+
+
Parameters:
+
    +
  • archive_path – Path for the new archive

  • +
  • files – List of files/directories to add

  • +
  • compression_level – Zstandard compression level (1-22)

  • +
  • use_temp_file – If True, create archive in temporary file first, then move +to final location for atomic operation

  • +
+
+
+
+

See also

+

TzstArchive.add(): Method for adding files to an open archive

+
+
+ +
+
+tzst.core.extract_archive(archive_path: str | Path, extract_path: str | Path = '.', members: list[str] | None = None, flatten: bool = False, streaming: bool = False, filter: str | Callable | None = 'data', conflict_resolution: ConflictResolution | str = ConflictResolution.REPLACE, interactive_callback: Callable[[Path], ConflictResolution] | None = None) → None[source]
+

Extract files from a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • extract_path – Destination directory

  • +
  • members – Specific members to extract (None for all)

  • +
  • flatten – If True, extract without directory structure

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives (default) +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (use only for trusted archives) +- None: Use default behavior (may show deprecation warning) +- callable: Custom filter function

  • +
  • conflict_resolution – How to handle file conflicts during extraction

  • +
  • interactive_callback – Function to call for interactive conflict resolution

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+

See Also: +TzstArchive.extract(): Method for extracting from an open archive

+
+
+ +
+
+tzst.core.list_archive(archive_path: str | Path, verbose: bool = False, streaming: bool = False) → list[dict][source]
+

List contents of a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • verbose – Include detailed information

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
+
+
Returns:
+

List of file information dictionaries

+
+
+
+

See also

+

TzstArchive.list(): Method for listing an open archive

+
+
+ +
+
+tzst.core.test_archive(archive_path: str | Path, streaming: bool = False) → bool[source]
+

Test the integrity of a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
+
+
Returns:
+

True if archive is valid, False otherwise

+
+
+
+

See also

+

TzstArchive.test(): Method for testing an open archive

+
+
+ +
+

TzstArchive Class

+

The main class for handling .tzst/.tar.zst archives with comprehensive functionality for creation, extraction, and manipulation.

+
+
+class tzst.TzstArchive(filename: str | Path, mode: str = 'r', compression_level: int = 3, streaming: bool = False)[source]
+

Bases: object

+

A class for handling .tzst/.tar.zst archives.

+
+
+__enter__()[source]
+

Enter context manager.

+
+ +
+
+__exit__(exc_type, exc_val, exc_tb)[source]
+

Exit context manager.

+
+ +
+
+__init__(filename: str | Path, mode: str = 'r', compression_level: int = 3, streaming: bool = False)[source]
+

Initialize a TzstArchive.

+
+
Parameters:
+
    +
  • filename – Path to the archive file

  • +
  • mode – Open mode (‘r’, ‘w’, ‘a’)

  • +
  • compression_level – Zstandard compression level (1-22)

  • +
  • streaming – If True, use streaming mode for reading (reduces memory usage +for very large archives but may limit some tarfile operations +that require seeking. Recommended for archives > 100MB)

  • +
+
+
+
+ +
+
+add(name: str | Path, arcname: str | None = None, recursive: bool = True)[source]
+

Add a file or directory to the archive.

+
+
Parameters:
+
    +
  • name – Path to file or directory to add

  • +
  • arcname – Alternative name for the file in the archive

  • +
  • recursive – If True, add directories recursively

  • +
+
+
+
+

See also

+

create_archive(): Convenience function for creating archives

+
+
+ +
+
+close()[source]
+

Close the archive.

+
+ +
+
+extract(member: str | None = None, path: str | Path = '.', set_attrs: bool = True, numeric_owner: bool = False, filter: str | Callable | None = 'data')[source]
+

Extract files from the archive.

+
+
Parameters:
+
    +
  • member – Specific member to extract (None for all)

  • +
  • path – Destination directory

  • +
  • set_attrs – Whether to set file attributes

  • +
  • numeric_owner – Whether to use numeric owner

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives (default, recommended) +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (use only for trusted archives) +- None: Use default behavior (may show deprecation warning in Python 3.12+) +- callable: Custom filter function

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. +The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+
+
+

Note

+

In streaming mode, extracting specific members is not supported. +Some extraction operations may be limited due to the sequential +nature of streaming mode.

+
+
+

See also

+

extract_archive(): Convenience function for extracting archives

+
+
+ +
+
+extractall(path: str | Path = '.', members: list[TarInfo] | None = None, *, numeric_owner: bool = False, filter: str | Callable | None = 'data')[source]
+

Extract all members from the archive.

+
+
Parameters:
+
    +
  • path – Destination directory (default: current directory)

  • +
  • members – Specific members to extract (None for all)

  • +
  • numeric_owner – Whether to use numeric owner IDs

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (trusted archives only) +- None: Use default behavior (may show deprecation warning) +- callable: Custom filter function

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. +The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+
+
+

Note

+

In streaming mode, extracting specific members is not supported. +Some extraction operations may be limited due to the sequential +nature of streaming mode.

+
+
+

See also

+

extract(): Extract a single member from the archive +extract_archive(): Convenience function for extracting archives

+
+
+ +
+
+extractfile(member: str | TarInfo)[source]
+

Extract a file-like object from the archive.

+
+
Parameters:
+

member – Member name or TarInfo object

+
+
Returns:
+

File-like object or None if member is not a file

+
+
+
+ +
+
+getmembers() → list[TarInfo][source]
+

Get list of all members in the archive.

+
+ +
+
+getnames() → list[str][source]
+

Get list of all member names in the archive.

+
+ +
+
+list(verbose: bool = False) → list[dict][source]
+

List contents of the archive.

+
+
Parameters:
+

verbose – Include detailed information

+
+
Returns:
+

List of file information dictionaries

+
+
+
+

See also

+

getmembers(): Get TarInfo objects for all archive members +getnames(): Get names of all archive members +list_archive(): Convenience function for listing archives

+
+
+ +
+
+open()[source]
+

Open the archive.

+
+

See also

+

close(): Method to close the archive

+
+
+ +
+
+test() → bool[source]
+

Test the integrity of the archive.

+
+
Returns:
+

True if archive is valid, False otherwise

+
+
+
+

See also

+

test_archive(): Convenience function for testing archive integrity

+
+
+ +
+ +
+

Key Features

+
    +
  • Context Manager Support: Use with with statements for automatic resource management

  • +
  • Multiple Access Modes: Read (‘r’), write (‘w’), and append (‘a’) modes

  • +
  • Streaming Support: Memory-efficient processing for large archives

  • +
  • Security Features: Built-in protection against path traversal attacks

  • +
  • Flexible Extraction: Support for selective extraction and conflict resolution

  • +
+
+
+

Usage Examples

+
# Create a new archive
+with TzstArchive("backup.tzst", "w", compression_level=6) as archive:
+    archive.add("important_file.txt")
+    archive.add("documents/", recursive=True)
+
+# Read an existing archive
+with TzstArchive("backup.tzst", "r") as archive:
+    contents = archive.list(verbose=True)
+    is_valid = archive.test()
+    archive.extractall("restore/")
+
+
+
+
+
+

Convenience Functions

+

High-level functions for common archive operations without needing to instantiate the TzstArchive class directly.

+
+

create_archive

+
+
+tzst.create_archive(archive_path: str | Path, files: Sequence[str | Path], compression_level: int = 3, use_temp_file: bool = True) → None[source]
+

Create a new .tzst archive with atomic file operations.

+
+
Parameters:
+
    +
  • archive_path – Path for the new archive

  • +
  • files – List of files/directories to add

  • +
  • compression_level – Zstandard compression level (1-22)

  • +
  • use_temp_file – If True, create archive in temporary file first, then move +to final location for atomic operation

  • +
+
+
+
+

See also

+

TzstArchive.add(): Method for adding files to an open archive

+
+
+ +

Creates a new tzst archive from the specified files and directories.

+

Key Features:

+
    +
  • Configurable compression levels (1-22)

  • +
  • Atomic creation using temporary files

  • +
  • Automatic path validation and normalization

  • +
  • Support for both files and directories

  • +
+
+
+

extract_archive

+
+
+tzst.extract_archive(archive_path: str | Path, extract_path: str | Path = '.', members: list[str] | None = None, flatten: bool = False, streaming: bool = False, filter: str | Callable | None = 'data', conflict_resolution: ConflictResolution | str = ConflictResolution.REPLACE, interactive_callback: Callable[[Path], ConflictResolution] | None = None) → None[source]
+

Extract files from a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • extract_path – Destination directory

  • +
  • members – Specific members to extract (None for all)

  • +
  • flatten – If True, extract without directory structure

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
  • filter – Extraction filter for security. Can be: +- ‘data’: Safe filter for cross-platform data archives (default) +- ‘tar’: Honor most tar features but block dangerous ones +- ‘fully_trusted’: Honor all metadata (use only for trusted archives) +- None: Use default behavior (may show deprecation warning) +- callable: Custom filter function

  • +
  • conflict_resolution – How to handle file conflicts during extraction

  • +
  • interactive_callback – Function to call for interactive conflict resolution

  • +
+
+
+
+

Warning

+

Never extract archives from untrusted sources without proper filtering. The ‘data’ filter is recommended for most use cases as it prevents +dangerous security issues like path traversal attacks.

+

See Also: +TzstArchive.extract(): Method for extracting from an open archive

+
+
+ +

Extracts files from a tzst archive with advanced options for handling conflicts and filtering.

+

Key Features:

+
    +
  • Selective extraction with member filtering

  • +
  • Multiple conflict resolution strategies

  • +
  • Flatten option to extract all files to a single directory

  • +
  • Streaming mode for memory efficiency

  • +
  • Security filters to prevent path traversal attacks

  • +
+
+
+

list_archive

+
+
+tzst.list_archive(archive_path: str | Path, verbose: bool = False, streaming: bool = False) → list[dict][source]
+

List contents of a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • verbose – Include detailed information

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
+
+
Returns:
+

List of file information dictionaries

+
+
+
+

See also

+

TzstArchive.list(): Method for listing an open archive

+
+
+ +

Lists the contents of a tzst archive with optional detailed information.

+

Returns:

+
    +
  • List of dictionaries containing file information

  • +
  • Each entry includes name, size, modification time, and type

  • +
  • Verbose mode provides additional metadata

  • +
+
+
+

test_archive

+
+
+tzst.test_archive(archive_path: str | Path, streaming: bool = False) → bool[source]
+

Test the integrity of a .tzst archive.

+
+
Parameters:
+
    +
  • archive_path – Path to the archive

  • +
  • streaming – If True, use streaming mode (memory efficient for large archives)

  • +
+
+
Returns:
+

True if archive is valid, False otherwise

+
+
+
+

See also

+

TzstArchive.test(): Method for testing an open archive

+
+
+ +

Tests the integrity of a tzst archive to verify it can be successfully decompressed.

+

Returns:

+
    +
  • True if the archive is valid and can be extracted

  • +
  • False if the archive is corrupted or cannot be processed

  • +
+
+
+
+

Enums and Supporting Classes

+
+

ConflictResolution

+

Enumeration for handling file conflicts during extraction:

+
    +
  • REPLACE: Overwrite existing files

  • +
  • SKIP: Skip existing files

  • +
  • REPLACE_ALL: Overwrite all existing files without prompting

  • +
  • SKIP_ALL: Skip all existing files without prompting

  • +
  • AUTO_RENAME: Automatically rename conflicting files

  • +
  • AUTO_RENAME_ALL: Automatically rename all conflicting files

  • +
  • ASK: Prompt user for each conflict (interactive mode)

  • +
  • EXIT: Stop extraction on first conflict

  • +
+
+
+

ConflictResolutionState

+

State management class for tracking conflict resolution decisions during batch operations.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/exceptions.html b/api/exceptions.html new file mode 100644 index 0000000..25a528c --- /dev/null +++ b/api/exceptions.html @@ -0,0 +1,577 @@ + + + + + + + + + + + + + + + + + + + + Exceptions API — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Exceptions API

+

Custom exception classes used by tzst for comprehensive error handling and debugging.

+

Exception classes for tzst.

+
+
+exception tzst.exceptions.TzstArchiveError[source]
+

Bases: TzstError

+

Exception raised when archive operations fail.

+

This can occur when: +- Archive file cannot be opened or created +- File permissions prevent archive access +- Archive structure is malformed +- Tar operations fail within the archive +- Atomic file operations fail during creation

+
+ +
+
+exception tzst.exceptions.TzstCompressionError[source]
+

Bases: TzstError

+

Exception raised when compression operations fail.

+

This can occur when: +- Invalid compression level is specified +- Disk space is insufficient during compression +- Input data cannot be compressed due to corruption +- Zstandard compression encounters an internal error

+
+ +
+
+exception tzst.exceptions.TzstDecompressionError[source]
+

Bases: TzstError

+

Exception raised when decompression operations fail.

+

This can occur when: +- Archive file is corrupted or incomplete +- Archive was not created with zstandard compression +- Decompression buffer overflows or underflows +- Archive format is invalid or unsupported

+
+ +
+
+exception tzst.exceptions.TzstError[source]
+

Bases: Exception

+

Base exception for all tzst operations.

+

This is the parent class for all tzst-specific exceptions. +Catch this to handle any tzst-related error.

+
+ +
+
+exception tzst.exceptions.TzstFileNotFoundError[source]
+

Bases: TzstError, FileNotFoundError

+

Exception raised when a required file is not found.

+

This can occur when: +- Archive file does not exist for reading operations +- Input files for archiving do not exist +- Output directory cannot be created for extraction +- Temporary files cannot be created during atomic operations

+

Inherits from both TzstError and FileNotFoundError for compatibility +with standard Python exception handling patterns.

+
+ +
+

Overview

+

The tzst library provides a comprehensive hierarchy of exceptions to help identify and handle different types of errors that may occur during archive operations. All exceptions inherit from the base TzstError class, making it easy to catch all tzst-related errors with a single exception handler.

+
+

Exception Hierarchy

+
TzstError (base exception)
+├── TzstArchiveError (archive operation failures)
+├── TzstCompressionError (compression failures)
+└── TzstDecompressionError (decompression failures)
+
+
+
+
+
+

Exception Classes

+
+

Base Exception

+
+

TzstError

+
+
+exception tzst.exceptions.TzstError[source]
+

Bases: Exception

+

Base exception for all tzst operations.

+

This is the parent class for all tzst-specific exceptions. +Catch this to handle any tzst-related error.

+
+ +

The base exception class for all tzst operations. Catch this exception to handle any tzst-related error in your application.

+

Usage:

+
from tzst import create_archive, TzstError
+
+try:
+    create_archive("backup.tzst", ["files/"])
+except TzstError as e:
+    print(f"tzst operation failed: {e}")
+
+
+
+
+
+

Archive Operation Exceptions

+
+

TzstArchiveError

+
+
+exception tzst.exceptions.TzstArchiveError[source]
+

Bases: TzstError

+

Exception raised when archive operations fail.

+

This can occur when: +- Archive file cannot be opened or created +- File permissions prevent archive access +- Archive structure is malformed +- Tar operations fail within the archive +- Atomic file operations fail during creation

+
+ +

Raised when archive operations fail, such as:

+
    +
  • Archive file cannot be opened or created

  • +
  • File permissions prevent archive access

  • +
  • Archive structure is malformed

  • +
  • Tar operations fail within the archive

  • +
  • Atomic file operations fail during creation

  • +
+

Common Scenarios:

+
    +
  • Invalid archive file path

  • +
  • Insufficient disk space

  • +
  • File permission errors

  • +
  • Corrupt archive structure

  • +
+
+
+
+

Compression Exceptions

+
+

TzstCompressionError

+
+
+exception tzst.exceptions.TzstCompressionError[source]
+

Bases: TzstError

+

Exception raised when compression operations fail.

+

This can occur when: +- Invalid compression level is specified +- Disk space is insufficient during compression +- Input data cannot be compressed due to corruption +- Zstandard compression encounters an internal error

+
+ +

Raised when compression operations fail, including:

+
    +
  • Invalid compression level is specified

  • +
  • Disk space is insufficient during compression

  • +
  • Input data cannot be compressed due to corruption

  • +
  • Zstandard compression encounters an internal error

  • +
+

Common Scenarios:

+
    +
  • Compression level out of range (1-22)

  • +
  • Insufficient disk space during compression

  • +
  • Source file corruption

  • +
  • Zstandard library errors

  • +
+
+
+
+

Decompression Exceptions

+
+

TzstDecompressionError

+
+
+exception tzst.exceptions.TzstDecompressionError[source]
+

Bases: TzstError

+

Exception raised when decompression operations fail.

+

This can occur when: +- Archive file is corrupted or incomplete +- Archive was not created with zstandard compression +- Decompression buffer overflows or underflows +- Archive format is invalid or unsupported

+
+ +

Raised when decompression operations fail, such as:

+
    +
  • Archive file is corrupted or incomplete

  • +
  • Archive was not created with zstandard compression

  • +
  • Decompression buffer overflows or underflows

  • +
  • Archive format is invalid or unsupported

  • +
+

Common Scenarios:

+
    +
  • Corrupted or truncated archive files

  • +
  • Non-zstandard compressed archives

  • +
  • Invalid tar structure within archive

  • +
  • Archive format version mismatches

  • +
+
+
+
+
+

Error Handling Best Practices

+
+

Basic Error Handling

+
from tzst import create_archive, TzstArchiveError, TzstCompressionError
+
+try:
+    create_archive("backup.tzst", ["documents/"])
+except TzstCompressionError as e:
+    print(f"Compression failed: {e}")
+except TzstArchiveError as e:
+    print(f"Archive operation failed: {e}")
+
+
+
+
+

Comprehensive Error Handling

+
from tzst import extract_archive, TzstError
+
+try:
+    extract_archive("backup.tzst", "restore/")
+except TzstError as e:
+    # Catch any tzst-related error
+    print(f"Operation failed: {e}")
+    # Perform cleanup or fallback operations
+
+
+
+
+

Specific Exception Handling

+
from tzst import TzstArchive, TzstDecompressionError, TzstArchiveError
+
+def safe_extract(archive_path, output_dir):
+    try:
+        with TzstArchive(archive_path, "r") as archive:
+            # Test integrity first
+            if not archive.test():
+                print("Archive integrity check failed")
+                return False
+            
+            # Extract files
+            archive.extractall(output_dir)
+            return True
+            
+    except TzstDecompressionError as e:
+        print(f"Archive is corrupted or invalid: {e}")
+        return False
+    except TzstArchiveError as e:
+        print(f"Archive operation failed: {e}")
+        return False
+    except FileNotFoundError:        print(f"Archive file not found: {archive_path}")
+        return False
+    except PermissionError:
+        print(f"Permission denied accessing: {archive_path}")
+        return False
+
+
+
+
+
+

Logging Integration

+
import logging
+from tzst import test_archive, TzstDecompressionError, TzstError
+
+logger = logging.getLogger(__name__)
+
+def verify_archive(archive_path):
+    """Verify archive integrity with comprehensive logging."""
+    try:
+        if test_archive(archive_path):
+            logger.info(f"Archive {archive_path} is valid")
+            return True
+    except TzstDecompressionError as e:
+        logger.error(f"Archive {archive_path} is corrupted: {e}")
+    except TzstError as e:
+        logger.error(f"tzst error for {archive_path}: {e}")
+    except Exception as e:
+        logger.error(f"Unexpected error testing {archive_path}: {e}")
+    
+    return False
+
+
+
+
+

Error Recovery Patterns

+
from tzst import create_archive, extract_archive, TzstError
+from pathlib import Path
+import tempfile
+import shutil
+
+def robust_backup_and_restore(source_dir, backup_path, restore_dir):
+    """Robust backup with error recovery and validation."""
+    temp_backup = None
+    
+    try:
+        # Create backup with temporary file for atomicity
+        with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as temp_file:
+            temp_backup = Path(temp_file.name)
+        
+        # Create archive
+        create_archive(temp_backup, [source_dir], compression_level=6)
+        
+        # Verify archive before moving to final location
+        if not test_archive(temp_backup):
+            raise TzstArchiveError("Created archive failed integrity check")
+        
+        # Move to final location atomically
+        shutil.move(temp_backup, backup_path)
+        temp_backup = None  # Successfully moved
+        
+        # Test restoration
+        extract_archive(backup_path, restore_dir)
+        
+        print(f"Backup and restore completed successfully")
+        return True
+        
+    except TzstError as e:
+        print(f"tzst operation failed: {e}")
+        # Cleanup and recovery logic
+        if restore_dir.exists():
+            shutil.rmtree(restore_dir)
+        return False
+          except Exception as e:
+        print(f"Unexpected error: {e}")
+        return False
+        
+    finally:
+        # Cleanup temporary files
+        if temp_backup and temp_backup.exists():
+            temp_backup.unlink()
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/api/index.html b/api/index.html new file mode 100644 index 0000000..7549a91 --- /dev/null +++ b/api/index.html @@ -0,0 +1,397 @@ + + + + + + + + + + + + + + + + + + + + API Reference — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

API Reference

+

This section contains the complete API documentation for tzst, providing detailed information about classes, functions, and exceptions.

+ +
+

Overview

+

The tzst library provides both high-level convenience functions and a comprehensive class-based API for working with .tzst/.tar.zst archives. The library is designed with security, performance, and ease of use in mind.

+
+

Main Components

+
    +
  • Core API: Core functionality including TzstArchive class and convenience functions for archive operations

  • +
  • CLI API: Command-line interface functions and utilities for batch operations

  • +
  • Exceptions API: Custom exception classes for comprehensive error handling and debugging

  • +
+
+
+

Architecture Overview

+

The tzst library follows a layered architecture:

+
    +
  1. High-Level API: Convenience functions for common operations

  2. +
  3. Class-Based API: TzstArchive class for advanced control

  4. +
  5. CLI Interface: Command-line tools for interactive and scripted use

  6. +
  7. Exception System: Comprehensive error handling for robust applications

  8. +
+
+
+

Quick Reference

+
+

Core Classes

+ + + + + + +

TzstArchive

A class for handling .tzst/.tar.zst archives.

+

The main class for archive manipulation with context manager support and comprehensive functionality.

+
+
+

Convenience Functions

+ + + + + + + + + + + + + + + +

create_archive

Create a new .tzst archive with atomic file operations.

extract_archive

Extract files from a .tzst archive.

list_archive

List contents of a .tzst archive.

test_archive

Test the integrity of a .tzst archive.

+

High-level functions that provide simple interfaces for common archive operations.

+
+
+

CLI Functions

+ + + + + + + + + + + + + + + + + + +

main

Main entry point for the tzst command-line interface.

create_parser

Create and configure the command-line argument parser.

print_banner

Print the version and copyright banner.

format_size

Format file size in human-readable format.

validate_compression_level

Validate and return compression level.

+

Command-line interface utilities for interactive and batch operations.

+
+
+

Exception Classes

+ + + + + + + + + + + + + + + +

TzstError

Base exception for all tzst operations.

TzstArchiveError

Exception raised when archive operations fail.

TzstCompressionError

Exception raised when compression operations fail.

TzstDecompressionError

Exception raised when decompression operations fail.

+

Exception hierarchy for comprehensive error handling and debugging support.

+
+
+
+
+

Key Features

+
+

Security First

+
    +
  • Built-in path traversal protection

  • +
  • Multiple security filter options

  • +
  • Safe extraction by default

  • +
+
+
+

High Performance

+
    +
  • Zstandard compression with configurable levels

  • +
  • Streaming support for large archives

  • +
  • Memory-efficient operations

  • +
+
+
+

Developer Friendly

+
    +
  • Clean, Pythonic API

  • +
  • Comprehensive error handling

  • +
  • Context manager support

  • +
  • Extensive documentation and examples

  • +
+
+
+

Cross-Platform

+
    +
  • Works on Windows, macOS, and Linux

  • +
  • Consistent behavior across platforms

  • +
  • Native performance optimizations

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/development.html b/development.html new file mode 100644 index 0000000..2efbe2a --- /dev/null +++ b/development.html @@ -0,0 +1,659 @@ + + + + + + + + + + + + + + + + + + + + Development Guide — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Development Guide

+

This guide provides comprehensive information for developers contributing to or working with the tzst library.

+
+

Setting up Development Environment

+

This project uses modern Python packaging standards:

+
git clone https://github.com/xixu-me/tzst.git
+cd tzst
+pip install -e .[dev]
+
+
+

The development installation includes all necessary tools:

+
    +
  • pytest - Testing framework

  • +
  • ruff - Linting and formatting

  • +
  • coverage - Code coverage analysis

  • +
  • sphinx - Documentation generation

  • +
+
+
+

Running Tests

+
+

Basic Test Commands

+
# Run all tests
+python -m pytest
+
+# Run tests with coverage
+pytest --cov=tzst --cov-report=html
+
+# Or use the simpler command (coverage settings are in pyproject.toml)
+pytest
+
+# Run with verbose output
+python -m pytest -v
+
+# Run specific test file
+python -m pytest tests/test_core.py
+
+# Run integration tests only
+python -m pytest -m integration
+
+
+
+
+

Test Structure

+
    +
  • Unit tests: Test individual functions and methods

  • +
  • Integration tests: Test component interactions

  • +
  • CLI tests: Test command-line interface

  • +
  • Platform-specific tests: Test OS-specific functionality

  • +
+
+
+

Writing Tests

+
    +
  1. Use descriptive test names:

    +
    def test_create_archive_with_compression_level_9():
    +
    +
    +
  2. +
  3. Use fixtures for common test data:

    +
    def test_extract_archive(sample_archive_path, temp_dir):
    +
    +
    +
  4. +
  5. Test edge cases:

    +
      +
    • Empty files

    • +
    • Large files

    • +
    • Invalid inputs

    • +
    • Corrupted archives

    • +
    +
  6. +
  7. Add markers for test categorization:

    +
    @pytest.mark.integration
    +def test_full_archive_workflow():
    +
    +
    +
  8. +
+
+
+
+

Code Quality

+
+

Running Code Style Tools

+
# Check code quality
+ruff check src tests
+
+# Fix auto-fixable issues
+ruff check --fix src tests
+
+# Format code
+ruff format src tests
+
+# Check formatting without making changes
+ruff format --check src tests
+
+
+
+
+

Configuration

+

Settings are defined in pyproject.toml:

+
    +
  • Line length: 88 characters

  • +
  • Target Python version: 3.12+ (tested on 3.12-3.14)

  • +
  • Import sorting with isort

  • +
  • Quote style: double quotes

  • +
+
+
+

Code Style Guidelines

+
    +
  1. Follow PEP 8 with project-specific modifications

  2. +
  3. Use type hints for all public APIs

  4. +
  5. Write docstrings for classes and public methods

  6. +
  7. Keep functions focused and reasonably sized

  8. +
  9. Use meaningful variable names

  10. +
  11. Add comments for complex logic

  12. +
+
+
+
+

Documentation

+
+

Building Documentation

+
# Navigate to docs directory
+cd docs
+
+# Install documentation dependencies
+pip install -r requirements.txt
+
+# Build HTML documentation
+make html
+
+# On Windows, use:
+make.bat html
+
+# View built documentation
+# Open docs/_build/html/index.html in your browser
+
+
+
+
+

Documentation Structure

+
docs/
+├── index.md           # Main documentation landing page
+├── quickstart.md      # Getting started guide
+├── performance.md     # Performance guide and comparisons
+├── examples.md        # Usage examples
+├── development.md     # This development guide
+├── api/               # API reference documentation
+│   ├── index.md
+│   ├── core.md
+│   ├── cli.md
+│   └── exceptions.md
+├── conf.py           # Sphinx configuration
+└── requirements.txt  # Documentation dependencies
+
+
+
+
+

Writing Documentation

+
    +
  • Use MyST Markdown format

  • +
  • Include code examples for new features

  • +
  • Add cross-references using proper syntax

  • +
  • Test all code snippets to ensure they work

  • +
+
+
+
+

Project Structure

+
tzst/
+├── src/tzst/           # Main package source code
+│   ├── __init__.py     # Package initialization and exports
+│   ├── __main__.py     # CLI entry point
+│   ├── cli.py          # Command-line interface
+│   ├── core.py         # Core archive functionality
+│   └── exceptions.py   # Custom exceptions
+├── tests/              # Test suite
+│   ├── conftest.py     # Pytest configuration and fixtures
+│   ├── test_core.py    # Core functionality tests
+│   ├── test_cli.py     # CLI tests
+│   └── test_*.py       # Additional test modules
+├── docs/               # Documentation source
+├── .github/            # GitHub workflows and templates
+├── pyproject.toml      # Project configuration
+├── README.md           # Project Readme
+├── LICENSE             # BSD 3-Clause License
+└── CONTRIBUTING.md     # Contribution guidelines
+
+
+
+
+

Contributing Workflow

+
+

1. Making Changes

+
+

Types of Contributions

+
    +
  • Bug fixes: Fix issues in existing functionality

  • +
  • Features: Add new capabilities to the library

  • +
  • Documentation: Improve or add documentation

  • +
  • Tests: Add or improve test coverage

  • +
  • Performance: Optimize existing code

  • +
  • Security: Address security vulnerabilities

  • +
+
+
+

Branch Naming

+

Use descriptive branch names:

+
    +
  • feature/add-streaming-mode

  • +
  • fix/handle-corrupted-archives

  • +
  • docs/improve-api-documentation

  • +
  • test/add-compression-tests

  • +
+
+
+
+

2. Commit Messages

+

Follow conventional commit format:

+
type(scope): description
+
+[optional body]
+
+[optional footer]
+
+
+

Types:

+
    +
  • feat: New feature

  • +
  • fix: Bug fix

  • +
  • docs: Documentation changes

  • +
  • test: Adding or modifying tests

  • +
  • refactor: Code refactoring

  • +
  • perf: Performance improvements

  • +
  • chore: Build process or auxiliary tool changes

  • +
+

Examples:

+
feat(core): add streaming compression support
+
+fix(cli): handle invalid archive paths gracefully
+
+docs(readme): update installation instructions
+
+
+
+
+

3. Pull Request Process

+
    +
  1. Create a feature branch:

    +
    git checkout -b feature/your-feature-name
    +
    +
    +
  2. +
  3. Make your changes following the guidelines above

  4. +
  5. Add tests for new functionality

  6. +
  7. Update documentation if needed

  8. +
  9. Run the test suite:

    +
    python -m pytest
    +ruff check .
    +ruff format --check .
    +
    +
    +
  10. +
  11. Commit your changes:

    +
    git add .
    +git commit -m "feat: add your feature description"
    +
    +
    +
  12. +
  13. Push to your fork:

    +
    git push origin feature/your-feature-name
    +
    +
    +
  14. +
  15. Create a pull request using the provided template

  16. +
+
+
+

4. Pull Request Guidelines

+
    +
  • Fill out the PR template completely

  • +
  • Link related issues using keywords (fixes #123)

  • +
  • Keep PRs focused - one feature/fix per PR

  • +
  • Ensure all CI checks pass

  • +
  • Respond to review feedback promptly

  • +
+
+
+
+

Development Tips

+
+

Performance Considerations

+
    +
  • Use streaming for large files

  • +
  • Consider memory usage patterns

  • +
  • Profile code for bottlenecks

  • +
  • Test with various file sizes

  • +
+
+
+

Security Considerations

+
    +
  • Validate all user inputs

  • +
  • Use secure defaults (e.g., ‘data’ filter)

  • +
  • Handle malicious archives safely

  • +
  • Be cautious with file paths

  • +
+
+
+

Compatibility

+
    +
  • Support Python 3.12+ with CI coverage for 3.12-3.14

  • +
  • Test on multiple platforms (Windows, macOS, Linux)

  • +
  • Consider different filesystem behaviors

  • +
  • Maintain backwards compatibility when possible

  • +
+
+
+
+

Release Process

+

Releases are handled by maintainers:

+
    +
  1. Update version in src/tzst/__init__.py

  2. +
  3. Create a release tag

  4. +
  5. Automated CI/CD publishes to PyPI

  6. +
+
+
+

Getting Help

+
+

Resources

+
    +
  • Issues: GitHub Issues

  • +
  • Discussions: Use GitHub Discussions for questions

  • +
  • Documentation: Check the README and code comments

  • +
+
+
+

Reporting Issues

+

When reporting bugs:

+
    +
  1. Use the bug report template

  2. +
  3. Provide a minimal reproduction case

  4. +
  5. Include system information (OS, Python version)

  6. +
  7. Attach relevant files if possible (archives, logs)

  8. +
+
+
+

Suggesting Features

+

When suggesting features:

+
    +
  1. Use the feature request template

  2. +
  3. Explain the use case and motivation

  4. +
  5. Consider backwards compatibility

  6. +
  7. Provide implementation ideas if you have them

  8. +
+
+
+
+

Code of Conduct

+

This project follows the principles of respectful collaboration. Please be kind, constructive, and professional in all interactions.

+
+
+

Recognition

+

Contributors are recognized in several ways:

+
    +
  • Listed in release notes for significant contributions

  • +
  • Mentioned in README acknowledgments

  • +
  • GitHub contributor statistics

  • +
+

Thank you for contributing to tzst! Your efforts help make this library better for everyone.

+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/examples.html b/examples.html new file mode 100644 index 0000000..59a2f64 --- /dev/null +++ b/examples.html @@ -0,0 +1,1473 @@ + + + + + + + + + + + + + + + + + + + + Examples — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Examples

+

This section provides comprehensive examples of using tzst for various scenarios and use cases.

+
+

Table of Contents

+ +
+
+

Basic Operations

+
+

Creating Your First Archive

+
from tzst import create_archive
+
+# Simple archive creation
+files_to_archive = ["document.pdf", "photos/", "config.json"]
+create_archive("my-archive.tzst", files_to_archive)
+
+# With custom compression level
+create_archive("high-compression.tzst", files_to_archive, compression_level=9)
+
+
+
+
+

Command Line Equivalent

+
# Create archive
+tzst a my-archive.tzst document.pdf photos/ config.json
+
+# With high compression
+tzst a high-compression.tzst document.pdf photos/ config.json -l 9
+
+
+
+
+

Basic Extraction

+
from tzst import extract_archive
+
+# Extract to current directory
+extract_archive("my-archive.tzst")
+
+# Extract to specific directory
+extract_archive("my-archive.tzst", "extracted/")
+
+# Extract specific files only
+extract_archive("my-archive.tzst", "output/", members=["document.pdf", "config.json"])
+
+# Extract with conflict resolution
+extract_archive("my-archive.tzst", "output/", conflict_resolution="skip")
+
+
+
+
+

Listing Archive Contents

+
from tzst import list_archive
+
+# Simple listing
+contents = list_archive("my-archive.tzst")
+for item in contents:
+    print(f"{item['name']} ({item['size']} bytes)")
+
+# Detailed listing with timestamps and permissions
+contents = list_archive("my-archive.tzst", verbose=True)
+for item in contents:
+    print(f"{item['name']:30} {item['size']:>10} bytes  {item['mtime']}")
+
+
+
+
+
+

Command Line Usage

+
+

Note: Download the standalone binary for the best performance and no Python dependency. Alternatively, use uvx tzst for running without installation. See uv documentation for details.

+
+
+

Archive Creation Commands

+
# Basic archive creation
+tzst a backup.tzst documents/ photos/
+# Or with uvx (no installation needed)
+uvx tzst a backup.tzst documents/ photos/
+
+# Create with specific compression level
+tzst a backup.tzst documents/ photos/ -l 6
+uvx tzst a backup.tzst documents/ photos/ -l 6
+
+# Create from multiple sources
+tzst a complete-backup.tzst /home/user/documents /home/user/photos /etc/config
+
+# Create with verbose output
+tzst a backup.tzst documents/ photos/ -v
+
+
+
+
+

Extraction Commands

+
# Extract to current directory
+tzst x backup.tzst
+# Or with uvx
+uvx tzst x backup.tzst
+
+# Extract to specific directory
+tzst x backup.tzst --output /restore/
+
+# Extract specific files only
+tzst x backup.tzst documents/report.pdf photos/vacation.jpg
+
+# Extract with conflict resolution
+tzst x backup.tzst --conflict-resolution skip
+
+# Extract flattening directory structure
+tzst e backup.tzst --output flat-restore/
+
+
+
+
+

Archive Inspection Commands

+
# List archive contents
+tzst l backup.tzst
+# Or with uvx
+uvx tzst l backup.tzst
+
+# List with detailed information
+tzst l backup.tzst --verbose
+
+# Test archive integrity
+tzst t backup.tzst
+
+# Stream large archives efficiently
+tzst l huge-archive.tzst --streaming
+
+
+
+
+
+

Advanced Archive Creation

+
+

Working with the TzstArchive Class

+
from tzst import TzstArchive
+from pathlib import Path
+
+# Create archive with fine-grained control
+with TzstArchive("project-backup.tzst", "w", compression_level=6) as archive:
+    # Add individual files
+    archive.add("README.md")
+    archive.add("LICENSE")
+    
+    # Add directories recursively
+    archive.add("src/", recursive=True)
+    archive.add("tests/", recursive=True)
+    
+    # Add with custom archive names
+    archive.add("config/production.yaml", arcname="config.yaml")
+    archive.add("/tmp/build-info.json", arcname="build-info.json")
+
+
+
+
+

Conditional File Addition

+
import os
+from tzst import TzstArchive
+from pathlib import Path
+
+def backup_project(project_path, output_archive):
+    """Create a project backup excluding certain files."""
+    project_path = Path(project_path)
+    
+    # Define exclusion patterns
+    exclude_patterns = {
+        "*.pyc", "*.pyo", "__pycache__", 
+        ".git", ".svn", "node_modules",
+        "*.tmp", "*.log", ".DS_Store"
+    }
+    
+    with TzstArchive(output_archive, "w", compression_level=5) as archive:
+        for item in project_path.rglob("*"):
+            # Skip excluded patterns
+            if any(item.match(pattern) for pattern in exclude_patterns):
+                continue
+                
+            # Skip if it's a directory (will be created automatically)
+            if item.is_dir():
+                continue
+                
+            # Add file with relative path
+            rel_path = item.relative_to(project_path)
+            archive.add(str(item), arcname=str(rel_path))
+            print(f"Added: {rel_path}")
+
+# Usage
+backup_project("/home/user/myproject", "project-clean.tzst")
+
+
+
+
+

Atomic Archive Creation

+
from tzst import create_archive
+
+# Safe atomic creation (default behavior)
+# Creates in temporary file first, then moves to final location
+create_archive("important-data.tzst", ["critical/"], use_temp_file=True)
+
+# Direct creation (faster but not atomic)
+create_archive("temp-data.tzst", ["temp/"], use_temp_file=False)
+
+
+
+
+
+

Flexible Extraction

+
+

Extracting with Different Structures

+
from tzst import extract_archive, TzstArchive
+
+# Standard extraction (preserves directory structure)
+extract_archive("archive.tzst", "output/")
+
+# Flatten all files to single directory
+extract_archive("archive.tzst", "flat-output/", flatten=True)
+
+# Extract with streaming for large archives
+extract_archive("huge-archive.tzst", "output/", streaming=True)
+
+
+
+
+

Selective Extraction

+
from tzst import TzstArchive
+
+def extract_by_extension(archive_path, output_dir, extensions):
+    """Extract only files with specific extensions."""
+    with TzstArchive(archive_path, "r") as archive:
+        members = archive.getmembers()
+        
+        # Filter members by extension
+        filtered_members = [
+            member.name for member in members 
+            if any(member.name.endswith(ext) for ext in extensions)
+        ]
+        
+        if filtered_members:
+            archive.extractall(output_dir, members=filtered_members)
+            print(f"Extracted {len(filtered_members)} files")
+        else:
+            print("No matching files found")
+
+# Extract only images
+extract_by_extension("photos.tzst", "images/", [".jpg", ".png", ".gif"])
+
+# Extract only documents
+extract_by_extension("backup.tzst", "docs/", [".pdf", ".docx", ".txt"])
+
+
+
+
+

Custom Extraction Logic

+
from tzst import TzstArchive
+import os
+
+def extract_large_files_only(archive_path, output_dir, min_size_mb=10):
+    """Extract only files larger than specified size."""
+    min_size_bytes = min_size_mb * 1024 * 1024
+    
+    with TzstArchive(archive_path, "r") as archive:
+        large_files = []
+        
+        for member in archive.getmembers():
+            if member.isfile() and member.size > min_size_bytes:
+                large_files.append(member.name)
+                size_mb = member.size / (1024 * 1024)
+                print(f"Will extract: {member.name} ({size_mb:.1f} MB)")
+        
+        if large_files:
+            os.makedirs(output_dir, exist_ok=True)
+            for filename in large_files:
+                archive.extract(filename, output_dir)
+            print(f"Extracted {len(large_files)} large files")
+
+extract_large_files_only("mixed-content.tzst", "large-files/", min_size_mb=5)
+
+
+
+
+
+

Security and Filtering

+
+

Safe Extraction Practices

+
from tzst import extract_archive
+
+# Always use secure filters (default behavior)
+extract_archive("untrusted.tzst", "safe-output/", filter="data")
+
+# For trusted archives with special tar features
+extract_archive("trusted.tzst", "output/", filter="tar")
+
+# Only for completely trusted archives
+extract_archive("internal.tzst", "output/", filter="fully_trusted")
+
+
+
+
+

Custom Security Filter

+
import tarfile
+from tzst import TzstArchive
+
+def secure_data_filter(member, path):
+    """Custom filter that only allows regular files and directories."""
+    # Only allow regular files and directories
+    if not (member.isfile() or member.isdir()):
+        return None
+    
+    # Prevent path traversal
+    if os.path.isabs(member.name) or ".." in member.name:
+        return None
+    
+    # Limit file size (100MB max)
+    if member.isfile() and member.size > 100 * 1024 * 1024:
+        return None
+    
+    return member
+
+# Use custom filter
+with TzstArchive("archive.tzst", "r") as archive:
+    archive.extractall("secure-output/", filter=secure_data_filter)
+
+
+
+
+

Handling File Conflicts

+
from tzst import extract_archive, ConflictResolution
+
+# Skip existing files
+extract_archive("archive.tzst", "output/", 
+                conflict_resolution=ConflictResolution.SKIP_ALL)
+
+# Replace all existing files
+extract_archive("archive.tzst", "output/", 
+                conflict_resolution=ConflictResolution.REPLACE_ALL)
+
+# Auto-rename conflicting files (adds suffix like "_1", "_2", etc.)
+extract_archive("archive.tzst", "output/", 
+                conflict_resolution=ConflictResolution.AUTO_RENAME_ALL)
+
+# Interactive resolution (command line only)
+extract_archive("archive.tzst", "output/", 
+                conflict_resolution=ConflictResolution.ASK)
+
+
+
+
+

Custom Conflict Resolution

+
from tzst import extract_archive, ConflictResolution
+from pathlib import Path
+
+def custom_conflict_handler(target_path: Path) -> ConflictResolution:
+    """Custom logic for handling file conflicts."""
+    # Check file age
+    if target_path.exists():
+        file_age_days = (time.time() - target_path.stat().st_mtime) / (24 * 3600)
+        
+        if file_age_days > 30:
+            print(f"Replacing old file: {target_path}")
+            return ConflictResolution.REPLACE
+        else:
+            print(f"Keeping newer file: {target_path}")
+            return ConflictResolution.SKIP
+    
+    return ConflictResolution.REPLACE
+
+# Use custom callback
+extract_archive("archive.tzst", "output/", 
+                conflict_resolution=ConflictResolution.ASK,
+                interactive_callback=custom_conflict_handler)
+
+
+
+
+
+

Performance Optimization

+
+

Streaming for Large Archives

+
from tzst import TzstArchive, list_archive, test_archive
+
+# Memory-efficient operations for large archives
+large_archive = "backup-500gb.tzst"
+
+# Test integrity with streaming
+is_valid = test_archive(large_archive, streaming=True)
+
+# List contents with streaming
+contents = list_archive(large_archive, streaming=True, verbose=True)
+
+# Extract with streaming
+with TzstArchive(large_archive, "r", streaming=True) as archive:
+    archive.extractall("restore/")
+
+
+
+
+

Compression Level Optimization

+
import time
+from tzst import create_archive
+
+def benchmark_compression_levels(files, output_prefix="test"):
+    """Compare different compression levels."""
+    levels_to_test = [1, 3, 6, 9, 15, 22]
+    
+    results = []
+    for level in levels_to_test:
+        output_file = f"{output_prefix}_level_{level}.tzst"
+        
+        # Measure compression time
+        start_time = time.time()
+        create_archive(output_file, files, compression_level=level)
+        compress_time = time.time() - start_time
+        
+        # Get file size
+        file_size = Path(output_file).stat().st_size
+        
+        results.append({
+            'level': level,
+            'time': compress_time,
+            'size': file_size,
+            'size_mb': file_size / (1024 * 1024)
+        })
+        
+        print(f"Level {level}: {compress_time:.2f}s, {file_size/1024/1024:.1f} MB")
+    
+    return results
+
+# Test different compression levels
+results = benchmark_compression_levels(["large-directory/"])
+
+
+
+
+

Parallel Processing

+
import concurrent.futures
+from tzst import create_archive
+from pathlib import Path
+
+def create_archive_batch(file_groups, output_dir="archives/", compression_level=6):
+    """Create multiple archives in parallel."""
+    Path(output_dir).mkdir(exist_ok=True)
+    
+    def create_single_archive(args):
+        group_name, files = args
+        output_path = Path(output_dir) / f"{group_name}.tzst"
+        create_archive(output_path, files, compression_level=compression_level)
+        return f"Created {output_path}"
+    
+    # Use ThreadPoolExecutor for I/O-bound operations
+    with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
+        future_to_group = {
+            executor.submit(create_single_archive, item): item[0] 
+            for item in file_groups.items()
+        }
+        
+        for future in concurrent.futures.as_completed(future_to_group):
+            group_name = future_to_group[future]
+            try:
+                result = future.result()
+                print(result)
+            except Exception as e:
+                print(f"Archive {group_name} failed: {e}")
+
+# Example usage
+file_groups = {
+    "documents": ["docs/", "papers/"],
+    "projects": ["src/", "tests/"],
+    "media": ["photos/", "videos/"]
+}
+
+create_archive_batch(file_groups)
+
+
+
+
+
+

Error Handling

+
+

Comprehensive Error Handling

+
from tzst import TzstArchive, TzstArchiveError, TzstDecompressionError
+import logging
+
+def safe_archive_operation(operation, *args, **kwargs):
+    """Wrapper for safe archive operations with logging."""
+    try:
+        return operation(*args, **kwargs)
+    except TzstDecompressionError as e:
+        logging.error(f"Decompression error: {e}")
+        print("The archive appears to be corrupted or not a valid tzst file.")
+        return None
+    except TzstArchiveError as e:
+        logging.error(f"Archive error: {e}")
+        print(f"Archive operation failed: {e}")
+        return None
+    except PermissionError as e:
+        logging.error(f"Permission error: {e}")
+        print("Permission denied. Check file/directory permissions.")
+        return None
+    except FileNotFoundError as e:
+        logging.error(f"File not found: {e}")
+        print(f"File or directory not found: {e}")
+        return None
+    except Exception as e:
+        logging.error(f"Unexpected error: {e}")
+        print(f"An unexpected error occurred: {e}")
+        return None
+
+# Example usage
+def create_backup_safely(files, output_archive):
+    def create_operation():
+        from tzst import create_archive
+        return create_archive(output_archive, files)
+    
+    result = safe_archive_operation(create_operation)
+    if result is not None:
+        print(f"Backup created successfully: {output_archive}")
+    else:
+        print("Backup creation failed!")
+
+
+
+
+

Validation and Recovery

+
from tzst import test_archive, list_archive, TzstArchive
+from pathlib import Path
+
+def validate_and_repair_archive(archive_path):
+    """Validate archive and attempt basic recovery."""
+    archive_path = Path(archive_path)
+    
+    print(f"Validating {archive_path}...")
+    
+    # Test basic integrity
+    try:
+        if test_archive(archive_path):
+            print("Archive integrity test passed")
+            return True
+    except Exception as e:
+        print(f"Integrity test failed: {e}")
+    
+    # Try to list contents
+    try:
+        contents = list_archive(archive_path)
+        print(f"Archive contains {len(contents)} items")
+        
+        # Try streaming mode if regular mode fails
+        contents_streaming = list_archive(archive_path, streaming=True)
+        if len(contents_streaming) != len(contents):
+            print("Different results between modes - possible corruption")
+        
+    except Exception as e:
+        print(f"Cannot list contents: {e}")
+        return False
+    
+    # Try partial extraction
+    try:
+        backup_dir = archive_path.parent / f"{archive_path.stem}_recovery"
+        backup_dir.mkdir(exist_ok=True)
+        
+        with TzstArchive(archive_path, "r") as archive:
+            extracted_count = 0
+            for member in archive.getmembers():
+                try:
+                    if member.isfile():
+                        archive.extract(member.name, backup_dir)
+                        extracted_count += 1
+                except Exception as e:
+                    print(f"Failed to extract {member.name}: {e}")
+            
+        print(f"Recovered {extracted_count} files to {backup_dir}")
+        return True
+        
+    except Exception as e:
+        print(f"Recovery failed: {e}")
+        return False
+
+# Example usage
+validate_and_repair_archive("potentially-corrupted.tzst")
+
+
+
+
+
+

Real-World Scenarios

+
+

Automated Backup System

+
#!/usr/bin/env python3
+"""
+Daily backup script with rotation and validation.
+"""
+
+import os
+import sys
+from datetime import datetime, timedelta
+from pathlib import Path
+from tzst import create_archive, test_archive
+
+class BackupManager:
+    def __init__(self, source_dirs, backup_dir, retention_days=30):
+        self.source_dirs = [Path(d) for d in source_dirs]
+        self.backup_dir = Path(backup_dir)
+        self.retention_days = retention_days
+        self.backup_dir.mkdir(parents=True, exist_ok=True)
+    
+    def create_backup(self):
+        """Create a new backup with timestamp."""
+        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+        backup_name = f"backup_{timestamp}.tzst"
+        backup_path = self.backup_dir / backup_name
+        
+        print(f"Creating backup: {backup_name}")
+        
+        # Collect all existing files
+        files_to_backup = []
+        for source_dir in self.source_dirs:
+            if source_dir.exists():
+                files_to_backup.append(str(source_dir))
+            else:
+                print(f"Warning: Source directory not found: {source_dir}")
+        
+        if not files_to_backup:
+            print("No files to backup!")
+            return None
+        
+        try:
+            # Create backup with high compression for storage efficiency
+            create_archive(backup_path, files_to_backup, compression_level=9)
+            
+            # Validate the backup
+            if test_archive(backup_path):
+                file_size = backup_path.stat().st_size / (1024 * 1024)
+                print(f"Backup created and validated: {file_size:.1f} MB")
+                return backup_path
+            else:
+                print("Backup validation failed!")
+                backup_path.unlink()  # Remove invalid backup
+                return None
+                
+        except Exception as e:
+            print(f"Backup failed: {e}")
+            return None
+    
+    def cleanup_old_backups(self):
+        """Remove backups older than retention period."""
+        cutoff_date = datetime.now() - timedelta(days=self.retention_days)
+        
+        removed_count = 0
+        for backup_file in self.backup_dir.glob("backup_*.tzst"):
+            # Extract timestamp from filename
+            try:
+                timestamp_str = backup_file.stem.split("_", 1)[1]
+                file_date = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
+                
+                if file_date < cutoff_date:
+                    backup_file.unlink()
+                    removed_count += 1
+                    print(f"Removed old backup: {backup_file.name}")
+                    
+            except (ValueError, IndexError):
+                print(f"Warning: Could not parse backup date: {backup_file.name}")
+        
+        print(f"Cleaned up {removed_count} old backups")
+    
+    def run_backup(self):
+        """Run complete backup process."""
+        print("Starting backup process...")
+        
+        backup_path = self.create_backup()
+        if backup_path:
+            self.cleanup_old_backups()
+            print("Backup process completed successfully!")
+            return True
+        else:
+            print("Backup process failed!")
+            return False
+
+# Configuration
+if __name__ == "__main__":
+    # Customize these paths for your setup
+    BACKUP_SOURCES = [
+        "~/Documents",
+        "~/Projects", 
+        "~/Pictures",
+        "/etc",  # System configs (Linux/macOS)
+    ]
+    
+    BACKUP_DESTINATION = "~/Backups"
+    RETENTION_DAYS = 30
+    
+    # Expand user paths
+    sources = [os.path.expanduser(path) for path in BACKUP_SOURCES]
+    destination = os.path.expanduser(BACKUP_DESTINATION)
+    
+    # Run backup
+    backup_manager = BackupManager(sources, destination, RETENTION_DAYS)
+    success = backup_manager.run_backup()
+    
+    sys.exit(0 if success else 1)
+
+
+
+
+

Log File Archiver

+
#!/usr/bin/env python3
+"""
+Archive and compress log files by date.
+"""
+
+import re
+from datetime import datetime, timedelta
+from pathlib import Path
+from tzst import create_archive
+
+def archive_logs_by_date(log_dir, archive_dir, days_old=7):
+    """Archive log files older than specified days."""
+    log_dir = Path(log_dir)
+    archive_dir = Path(archive_dir)
+    archive_dir.mkdir(parents=True, exist_ok=True)
+    
+    cutoff_date = datetime.now() - timedelta(days=days_old)
+    
+    # Group log files by date
+    log_groups = {}
+    log_pattern = re.compile(r"(\d{4}-\d{2}-\d{2})")
+    
+    for log_file in log_dir.glob("*.log"):
+        # Try to extract date from filename or modification time
+        date_match = log_pattern.search(log_file.name)
+        if date_match:
+            file_date_str = date_match.group(1)
+            try:
+                file_date = datetime.strptime(file_date_str, "%Y-%m-%d")
+            except ValueError:
+                # Fall back to modification time
+                file_date = datetime.fromtimestamp(log_file.stat().st_mtime)
+        else:
+            # Use modification time
+            file_date = datetime.fromtimestamp(log_file.stat().st_mtime)
+        
+        # Skip recent files
+        if file_date >= cutoff_date:
+            continue
+        
+        # Group by date
+        date_key = file_date.strftime("%Y-%m-%d")
+        if date_key not in log_groups:
+            log_groups[date_key] = []
+        log_groups[date_key].append(log_file)
+    
+    # Create archives for each date group
+    archived_files = []
+    for date_key, files in log_groups.items():
+        archive_name = f"logs_{date_key}.tzst"
+        archive_path = archive_dir / archive_name
+        
+        # Skip if archive already exists
+        if archive_path.exists():
+            print(f"Archive already exists: {archive_name}")
+            continue
+        
+        print(f"Archiving {len(files)} log files for {date_key}")
+        
+        try:
+            # Create archive with maximum compression (logs compress well)
+            create_archive(archive_path, [str(f) for f in files], compression_level=22)
+            
+            # Verify archive
+            from tzst import test_archive
+            if test_archive(archive_path):
+                # Remove original files after successful archiving
+                for log_file in files:
+                    log_file.unlink()
+                    archived_files.append(log_file)
+                
+                file_size = archive_path.stat().st_size / 1024
+                print(f"Created {archive_name} ({file_size:.1f} KB)")
+            else:
+                print(f"Archive validation failed for {archive_name}")
+                archive_path.unlink()
+        
+        except Exception as e:
+            print(f"Failed to archive logs for {date_key}: {e}")
+    
+    print(f"Archived {len(archived_files)} log files")
+
+# Usage
+if __name__ == "__main__":
+    archive_logs_by_date("/var/log", "/var/archives", days_old=7)
+
+
+
+
+

Data Migration Tool

+
#!/usr/bin/env python3
+"""
+Migrate data between systems using tzst archives.
+"""
+
+import hashlib
+from pathlib import Path
+from tzst import create_archive, extract_archive, test_archive
+
+class DataMigrator:
+    def __init__(self, source_dir, staging_dir):
+        self.source_dir = Path(source_dir)
+        self.staging_dir = Path(staging_dir)
+        self.staging_dir.mkdir(parents=True, exist_ok=True)
+    
+    def calculate_checksum(self, file_path):
+        """Calculate SHA256 checksum of a file."""
+        sha256_hash = hashlib.sha256()
+        with open(file_path, "rb") as f:
+            for chunk in iter(lambda: f.read(4096), b""):
+                sha256_hash.update(chunk)
+        return sha256_hash.hexdigest()
+    
+    def create_migration_package(self, package_name):
+        """Create a migration package with checksums."""
+        package_path = self.staging_dir / f"{package_name}.tzst"
+        checksum_file = self.staging_dir / f"{package_name}.sha256"
+        
+        print(f"Creating migration package: {package_name}")
+        
+        # Create the archive
+        create_archive(
+            package_path, 
+            [str(self.source_dir)], 
+            compression_level=6  # Balanced for network transfer
+        )
+        
+        # Verify archive
+        if not test_archive(package_path):
+            raise RuntimeError("Archive validation failed")
+        
+        # Calculate and save checksum
+        checksum = self.calculate_checksum(package_path)
+        with open(checksum_file, "w") as f:
+            f.write(f"{checksum}  {package_path.name}\n")
+        
+        package_size = package_path.stat().st_size / (1024 * 1024)        print(f"Package created: {package_size:.1f} MB")
+        print(f"Checksum: {checksum}")
+        
+        return package_path, checksum_file
+    
+    def verify_and_extract_package(self, package_path, checksum_path, destination):
+        """Verify package integrity and extract."""
+        package_path = Path(package_path)
+        checksum_path = Path(checksum_path)
+        destination = Path(destination)
+        
+        print(f"Verifying package: {package_path.name}")
+        
+        # Verify checksum
+        expected_checksum = checksum_path.read_text().strip().split()[0]
+        actual_checksum = self.calculate_checksum(package_path)
+        
+        if expected_checksum != actual_checksum:
+            raise RuntimeError(f"Checksum mismatch! Expected: {expected_checksum}, Got: {actual_checksum}")
+        
+        print("Checksum verification passed")
+        
+        # Test archive integrity
+        if not test_archive(package_path):
+            raise RuntimeError("Archive integrity check failed")
+        
+        print("Archive integrity verified")
+        
+        # Extract with conflict resolution
+        destination.mkdir(parents=True, exist_ok=True)
+        extract_archive(
+            package_path, 
+            destination,
+            conflict_resolution="replace_all"  # Overwrite for migration
+        )
+        
+        print(f"Package extracted to: {destination}")
+
+# Example usage
+if __name__ == "__main__":
+    # Create migration package
+    migrator = DataMigrator("/home/user/important-data", "/tmp/migration")
+    package_path, checksum_path = migrator.create_migration_package("data-migration-v1")
+    
+    # Simulate transfer and extraction on target system
+    migrator.verify_and_extract_package(
+        package_path, 
+        checksum_path, 
+        "/home/user/restored-data"
+    )
+
+
+
+
+
+

Integration Examples

+
+

Django Management Command

+
# management/commands/backup_media.py
+from django.core.management.base import BaseCommand
+from django.conf import settings
+from tzst import create_archive
+from datetime import datetime
+import os
+
+class Command(BaseCommand):
+    help = 'Create a backup of media files'
+    
+    def add_arguments(self, parser):
+        parser.add_argument(
+            '--output-dir',
+            default='/backups',
+            help='Output directory for backup files'
+        )
+        parser.add_argument(
+            '--compression-level',
+            type=int,
+            default=6,
+            help='Compression level (1-22)'
+        )
+    
+    def handle(self, *args, **options):
+        media_root = settings.MEDIA_ROOT
+        output_dir = options['output_dir']
+        compression_level = options['compression_level']
+        
+        if not os.path.exists(media_root):
+            self.stdout.write(
+                self.style.ERROR(f'Media directory not found: {media_root}')
+            )
+            return
+        
+        # Create backup filename with timestamp
+        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
+        backup_filename = f'media_backup_{timestamp}.tzst'
+        backup_path = os.path.join(output_dir, backup_filename)
+        
+        # Ensure output directory exists
+        os.makedirs(output_dir, exist_ok=True)
+        
+        try:
+            self.stdout.write(f'Creating media backup: {backup_filename}')
+            create_archive(backup_path, [media_root], compression_level=compression_level)
+            
+            # Verify backup
+            from tzst import test_archive
+            if test_archive(backup_path):
+                file_size = os.path.getsize(backup_path) / (1024 * 1024)
+                self.stdout.write(
+                    self.style.SUCCESS(
+                        f'Backup created successfully: {backup_filename} ({file_size:.1f} MB)'
+                    )
+                )
+            else:
+                self.stdout.write(
+                    self.style.ERROR('Backup validation failed!')
+                )
+                
+        except Exception as e:
+            self.stdout.write(
+                self.style.ERROR(f'Backup failed: {e}')
+            )
+
+
+
+
+

Flask Application Integration

+
from flask import Flask, request, send_file, jsonify
+from tzst import create_archive, extract_archive
+import tempfile
+import os
+from pathlib import Path
+
+app = Flask(__name__)
+
+@app.route('/api/backup', methods=['POST'])
+def create_backup():
+    """API endpoint to create backups."""
+    try:
+        data = request.get_json()
+        paths = data.get('paths', [])
+        compression_level = data.get('compression_level', 6)
+        
+        if not paths:
+            return jsonify({'error': 'No paths specified'}), 400
+        
+        # Create temporary archive
+        with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as tmp:
+            temp_path = tmp.name
+        
+        create_archive(temp_path, paths, compression_level=compression_level)
+        
+        # Return archive file
+        return send_file(
+            temp_path,
+            as_attachment=True,
+            download_name='backup.tzst',
+            mimetype='application/octet-stream'
+        )
+        
+    except Exception as e:
+        return jsonify({'error': str(e)}), 500
+    finally:
+        # Clean up temporary file
+        if 'temp_path' in locals() and os.path.exists(temp_path):
+            os.unlink(temp_path)
+
+@app.route('/api/extract', methods=['POST'])
+def extract_files():
+    """API endpoint to extract archives."""
+    try:
+        if 'file' not in request.files:
+            return jsonify({'error': 'No file uploaded'}), 400
+        
+        file = request.files['file']
+        if file.filename == '':
+            return jsonify({'error': 'No file selected'}), 400
+        
+        # Save uploaded file temporarily
+        with tempfile.NamedTemporaryFile(suffix='.tzst', delete=False) as tmp:
+            temp_archive = tmp.name
+            file.save(temp_archive)
+        
+        # Create extraction directory
+        extract_dir = tempfile.mkdtemp()
+        
+        # Extract archive
+        extract_archive(temp_archive, extract_dir)
+        
+        # List extracted files
+        extracted_files = []
+        for root, dirs, files in os.walk(extract_dir):
+            for file in files:
+                rel_path = os.path.relpath(os.path.join(root, file), extract_dir)
+                extracted_files.append(rel_path)
+        
+        return jsonify({
+            'success': True,
+            'extracted_files': extracted_files,
+            'extract_path': extract_dir
+        })
+        
+    except Exception as e:
+        return jsonify({'error': str(e)}), 500
+    finally:
+        # Clean up temporary archive
+        if 'temp_archive' in locals() and os.path.exists(temp_archive):
+            os.unlink(temp_archive)
+
+if __name__ == '__main__':
+    app.run(debug=True)
+
+
+
+
+

Jupyter Notebook Integration

+
# Cell 1: Setup
+import pandas as pd
+from tzst import create_archive, extract_archive, list_archive
+from pathlib import Path
+import matplotlib.pyplot as plt
+
+# Cell 2: Create dataset archive
+def archive_datasets(data_dir="./data", archive_name="datasets.tzst"):
+    """Archive all dataset files for sharing."""
+    data_path = Path(data_dir)
+    
+    if not data_path.exists():
+        print(f"Creating sample data directory: {data_dir}")
+        data_path.mkdir(exist_ok=True)
+        
+        # Create sample datasets
+        sample_data = pd.DataFrame({
+            'A': range(100),
+            'B': range(100, 200),
+            'C': range(200, 300)
+        })
+        
+        sample_data.to_csv(data_path / "sample.csv", index=False)
+        sample_data.to_parquet(data_path / "sample.parquet")
+    
+    # Create archive
+    create_archive(archive_name, [str(data_path)], compression_level=9)
+    
+    # Show archive contents
+    contents = list_archive(archive_name, verbose=True)
+    df = pd.DataFrame(contents)
+    
+    print(f"Created archive: {archive_name}")
+    return df
+
+# Execute
+archive_contents = archive_datasets()
+display(archive_contents)
+
+# Cell 3: Analyze archive
+def analyze_archive(archive_path="datasets.tzst"):
+    """Analyze archive contents and compression."""
+    contents = list_archive(archive_path, verbose=True)
+    df = pd.DataFrame(contents)
+    
+    # File type analysis
+    df['extension'] = df['name'].str.split('.').str[-1]
+    file_types = df.groupby('extension')['size'].agg(['count', 'sum']).reset_index()
+    
+    # Visualization
+    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
+    
+    # File count by type
+    ax1.bar(file_types['extension'], file_types['count'])
+    ax1.set_title('File Count by Type')
+    ax1.set_xlabel('File Extension')
+    ax1.set_ylabel('Count')
+    
+    # Size by type
+    ax2.bar(file_types['extension'], file_types['sum'] / 1024)  # KB
+    ax2.set_title('Total Size by Type (KB)')
+    ax2.set_xlabel('File Extension')
+    ax2.set_ylabel('Size (KB)')
+    
+    plt.tight_layout()
+    plt.show()
+    
+    return df, file_types
+
+# Execute
+contents_df, file_summary = analyze_archive()
+print("File Summary:")
+display(file_summary)
+
+
+

These examples demonstrate the flexibility and power of tzst for various real-world scenarios. The library’s clean API and robust error handling make it suitable for everything from simple backup scripts to complex enterprise applications.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 0000000..8d5129b --- /dev/null +++ b/genindex.html @@ -0,0 +1,349 @@ + + + + + + + + Index — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..849e893 --- /dev/null +++ b/index.html @@ -0,0 +1,421 @@ + + + + + + + + + + + + + + + + + + + + tzst Documentation — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

tzst Documentation

+

codecov +CodeQL +CI/CD +PyPI - Version +PyPI - Downloads +GitHub License +Sponsor

+

Welcome to tzst, the next-generation Python library engineered for modern archive management, leveraging cutting-edge Zstandard compression to deliver superior performance, security, and reliability.

+ +
+
+
+

What is tzst?

+

tzst is a modern Python library built exclusively for Python 3.12+ that provides comprehensive support for creating, extracting, and managing .tzst and .tar.zst archives. It combines the proven reliability of the tar format with the superior compression efficiency of Zstandard (zstd) to deliver:

+
    +
  • Superior Performance: Fast compression and decompression with excellent compression ratios

  • +
  • Enterprise-Grade Security: Safe extraction with built-in protections against path traversal attacks

  • +
  • Memory Efficiency: Streaming mode for handling large archives with minimal memory usage

  • +
  • Cross-Platform Compatibility: Works seamlessly on Windows, macOS, and Linux

  • +
  • Developer-Friendly: Clean, Pythonic API with comprehensive error handling

  • +
+
+
+

Key Features

+
+

Advanced Compression

+
    +
  • Zstandard Compression: Best-in-class compression algorithm with configurable levels (1-22)

  • +
  • Multiple Extensions: Support for both .tzst and .tar.zst file extensions

  • +
  • Streaming Support: Memory-efficient processing for large archives

  • +
+
+
+

Security First

+
    +
  • Safe by Default: Uses ‘data’ filter for secure extraction without dangerous path traversal

  • +
  • Multiple Filter Options: Choose from ‘data’, ‘tar’, or ‘fully_trusted’ filters based on your security needs

  • +
  • Atomic Operations: All file operations use temporary files with atomic moves to prevent corruption

  • +
+
+
+

Dual Interfaces

+
    +
  • Command Line: Intuitive CLI with comprehensive options for batch operations

  • +
  • Python API: Clean, object-oriented interface for programmatic use

  • +
  • Convenience Functions: High-level functions for common operations

  • +
+
+
+

High Performance

+
    +
  • Optimized I/O: Efficient buffering and streaming for large files

  • +
  • Conflict Resolution: Intelligent handling of file conflicts during extraction

  • +
  • Cross-Platform: Native performance on all major operating systems

  • +
+
+
+
+

Quick Example

+
from tzst import TzstArchive, create_archive, extract_archive
+
+# Create an archive
+create_archive("backup.tzst", ["documents/", "photos/"], compression_level=5)
+
+# Extract an archive
+extract_archive("backup.tzst", "restore/")
+
+# Work with archives programmatically
+with TzstArchive("data.tzst", "r") as archive:
+    contents = archive.list(verbose=True)
+    archive.extract("important.txt", "output/")
+    is_valid = archive.test()
+
+
+
+
+

Installation

+

For detailed installation instructions, including standalone binaries and source installation, please refer to the Quick Start Guide guide.

+
# Install from PyPI
+pip install tzst
+
+# Or using uv (recommended)
+uv tool install tzst
+
+
+
+
+

Getting Started

+

For a quick introduction, see the Quick Start Guide guide. For comprehensive usage examples, explore the Examples section.

+
+

API Documentation

+

Complete API documentation is available in the API Reference section, covering:

+ +
+
+
+

Development

+

For comprehensive development information, see the Development Guide guide, which covers:

+
    +
  • Setting up development environment

  • +
  • Running tests and code quality checks

  • +
  • Documentation building

  • +
  • Contributing workflow and guidelines

  • +
  • Project structure and best practices

  • +
+
+

Quick Start

+
git clone https://github.com/xixu-me/tzst.git
+cd tzst
+pip install -e .[dev]
+pytest
+
+
+
+
+
+

Contributing

+

We welcome contributions! Please read our Contributing Guide for:

+
    +
  • Development setup and project structure

  • +
  • Code style guidelines and best practices

  • +
  • Testing requirements and writing tests

  • +
  • Pull request process and review workflow

  • +
+
+

Types of Contributions Welcome

+
    +
  • Bug fixes - Fix issues in existing functionality

  • +
  • Features - Add new capabilities to the library

  • +
  • Documentation - Improve or add documentation

  • +
  • Tests - Add or improve test coverage

  • +
  • Performance - Optimize existing code

  • +
  • Security - Address security vulnerabilities

  • +
+
+
+
+

Acknowledgments

+ +
+
+

License

+

Copyright © Xi Xu. All rights reserved.

+

Licensed under the BSD 3-Clause license.

+
+
+

Documentation Guide

+
    +
  1. Quick Start Guide - Get up and running quickly with basic examples

  2. +
  3. Performance Guide - Performance optimization guide and comparisons

  4. +
  5. Examples - Comprehensive usage examples and patterns

  6. +
  7. API Reference - Complete API reference documentation

  8. +
  9. Development Guide - Development and contribution guidelines

  10. +
  11. Index - Index of all documented items

  12. +
+
+
+

Requirements

+
    +
  • Python 3.12 or higher (tested on 3.12-3.14)

  • +
  • zstandard >= 0.19.0

  • +
+
+
+ + +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 0000000..66e7625 Binary files /dev/null and b/objects.inv differ diff --git a/performance.html b/performance.html new file mode 100644 index 0000000..c2521e8 --- /dev/null +++ b/performance.html @@ -0,0 +1,556 @@ + + + + + + + + + + + + + + + + + + + + Performance Guide — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Performance Guide

+

This guide covers performance optimization techniques and provides detailed comparisons with other archive tools.

+
+

Performance Tips

+
+

1. Compression Levels

+

Choose the right compression level for your use case:

+
    +
  • Level 1-3: Fast compression, larger files (good for temporary archives or real-time processing)

  • +
  • Level 3 (default): Optimal balance for most use cases

  • +
  • Level 6-9: Higher compression, moderate speed (good for regular backups)

  • +
  • Level 15-22: Maximum compression, slower (for long-term storage or bandwidth-limited scenarios)

  • +
+
from tzst import create_archive
+
+# For temporary files or frequent operations
+create_archive("temp.tzst", files, compression_level=1)
+
+# Balanced default (recommended)
+create_archive("backup.tzst", files, compression_level=3)
+
+# Long-term storage
+create_archive("archive.tzst", files, compression_level=9)
+
+# Maximum compression for critical space savings
+create_archive("minimal.tzst", files, compression_level=22)
+
+
+
+
+

2. Streaming

+

Use streaming mode for archives larger than 100MB:

+
from tzst import extract_archive, list_archive, test_archive
+
+# Memory-efficient operations for large archives
+extract_archive("large-backup.tzst", "restore/", streaming=True)
+contents = list_archive("large-backup.tzst", streaming=True)
+is_valid = test_archive("large-backup.tzst", streaming=True)
+
+
+

Streaming Benefits:

+
    +
  • Significantly reduced memory usage

  • +
  • Better performance for large archives

  • +
  • Handles archives that don’t fit in memory

  • +
+
+
+

3. Batch Operations

+

Add multiple files in a single session when possible:

+
from tzst import TzstArchive
+
+# Efficient: Single archive session
+with TzstArchive("backup.tzst", "w") as archive:
+    archive.add("file1.txt")
+    archive.add("file2.txt")
+    archive.add("directory/", recursive=True)
+
+# Less efficient: Multiple separate operations
+create_archive("backup1.tzst", ["file1.txt"])
+create_archive("backup2.tzst", ["file2.txt"])
+
+
+
+
+

4. File Type Considerations

+
    +
  • Already compressed files (.jpg, .png, .mp4, .pdf) won’t compress much further

  • +
  • Text files, source code, and logs compress very well

  • +
  • Consider compression level based on your data types

  • +
+
+
+
+

Comparison with Other Tools

+
+

vs tar + gzip

+

tzst Advantages:

+
    +
  • Better compression ratios: 10-40% smaller archives

  • +
  • Faster decompression: 2-3x faster extraction

  • +
  • Modern algorithm: Better handling of various file types

  • +
  • Streaming support: Better memory efficiency

  • +
+

When to use tar + gzip:

+
    +
  • Legacy system compatibility requirements

  • +
  • Very old systems without zstd support

  • +
+
+
+

vs tar + xz

+

tzst Advantages:

+
    +
  • Significantly faster compression: 3-10x faster creation

  • +
  • Faster decompression: 2-4x faster extraction

  • +
  • Better speed/compression trade-off: Similar compression with much better speed

  • +
  • More compression levels: Fine-grained control (22 levels vs 9)

  • +
+

When to use tar + xz:

+
    +
  • Maximum compression is critical and time is not a factor

  • +
  • Systems that don’t support zstd

  • +
+
+
+

vs zip

+

tzst Advantages:

+
    +
  • Better compression: 15-30% smaller archives

  • +
  • Preserves Unix permissions and metadata: Full POSIX compatibility

  • +
  • Better streaming support: Memory-efficient for large archives

  • +
  • Better directory handling: Preserves directory structure and timestamps

  • +
+

When to use zip:

+
    +
  • Cross-platform compatibility with very old systems

  • +
  • Individual file access without full extraction is required

  • +
  • Windows-centric environments with no command-line tools

  • +
+
+
+
+

Benchmarking Examples

+
+

Compression Level Benchmark

+
import time
+from pathlib import Path
+from tzst import create_archive
+
+def benchmark_compression_levels(files, output_prefix="benchmark"):
+    """Compare different compression levels."""
+    levels_to_test = [1, 3, 6, 9, 15, 22]
+    
+    results = []
+    for level in levels_to_test:
+        output_file = f"{output_prefix}_level_{level}.tzst"
+        
+        # Measure compression time
+        start_time = time.time()
+        create_archive(output_file, files, compression_level=level)
+        compress_time = time.time() - start_time
+        
+        # Get file size
+        file_size = Path(output_file).stat().st_size
+        
+        results.append({
+            'level': level,
+            'time': compress_time,
+            'size': file_size,
+            'size_mb': file_size / (1024 * 1024)
+        })
+        
+        print(f"Level {level}: {compress_time:.2f}s, {file_size/1024/1024:.1f} MB")
+    
+    return results
+
+# Example usage
+files = ["documents/", "projects/"]
+results = benchmark_compression_levels(files)
+
+
+
+
+

Memory Usage Comparison

+
import psutil
+import os
+from tzst import extract_archive
+
+def monitor_memory_usage(func, *args, **kwargs):
+    """Monitor memory usage during function execution."""
+    process = psutil.Process(os.getpid())
+    initial_memory = process.memory_info().rss / 1024 / 1024  # MB
+    
+    func(*args, **kwargs)
+    
+    peak_memory = process.memory_info().rss / 1024 / 1024  # MB
+    return peak_memory - initial_memory
+
+# Compare streaming vs non-streaming extraction
+large_archive = "large-dataset.tzst"
+
+memory_normal = monitor_memory_usage(extract_archive, large_archive, "output1/")
+memory_streaming = monitor_memory_usage(extract_archive, large_archive, "output2/", streaming=True)
+
+print(f"Normal extraction: {memory_normal:.1f} MB")
+print(f"Streaming extraction: {memory_streaming:.1f} MB")
+print(f"Memory savings: {memory_normal - memory_streaming:.1f} MB")
+
+
+
+
+
+

Best Practices

+
+

For Development

+
# Fast compression for frequent builds
+create_archive("build-artifacts.tzst", ["build/"], compression_level=1)
+
+
+
+
+

For Backups

+
# Balanced compression for regular backups
+create_archive("daily-backup.tzst", ["data/"], compression_level=6)
+
+
+
+
+

For Distribution

+
# Higher compression for software distribution
+create_archive("software-package.tzst", ["app/"], compression_level=9)
+
+
+
+
+

For Archival Storage

+
# Maximum compression for long-term storage
+create_archive("archive-2024.tzst", ["historical-data/"], compression_level=22)
+
+
+
+
+
+

Hardware Considerations

+
+

CPU Usage

+
    +
  • Higher compression levels use more CPU but for shorter time periods

  • +
  • Modern multi-core systems handle zstd compression very efficiently

  • +
  • Consider system load when choosing compression levels

  • +
+
+
+

Memory Usage

+
    +
  • Streaming mode: ~16-32 MB memory usage regardless of archive size

  • +
  • Normal mode: Memory usage proportional to archive size

  • +
  • Use streaming for archives >100 MB or on memory-constrained systems

  • +
+
+
+

Storage

+
    +
  • SSDs benefit from higher compression (less I/O)

  • +
  • HDDs may prefer lower compression levels (CPU vs I/O trade-off)

  • +
  • Network storage benefits from higher compression (bandwidth savings)

  • +
+
+
+
+

Integration with Build Systems

+
+

Makefile Example

+
# Fast compression for development
+build-dev: 
+ tzst a build-dev.tzst build/ -l 1
+
+# Production compression
+build-prod:
+ tzst a build-prod.tzst build/ -l 9
+
+# CI/CD artifacts
+artifacts:
+ tzst a artifacts.tzst dist/ logs/ -l 6
+
+
+
+
+

GitHub Actions Example

+
- name: Create release archive
+  run: |
+    tzst a release-${{ github.ref_name }}.tzst \
+      build/ docs/ \
+      --compression-level 9
+
+
+

This performance guide helps you choose the right settings for your specific use case and understand how tzst compares to alternative archive tools.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/quickstart.html b/quickstart.html new file mode 100644 index 0000000..2cac3c0 --- /dev/null +++ b/quickstart.html @@ -0,0 +1,819 @@ + + + + + + + + + + + + + + + + + + + + Quick Start Guide — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Quick Start Guide

+

This guide will get you up and running with tzst in just a few minutes.

+
+

Installation

+

Choose your preferred installation method:

+
+

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

  2. +
  3. 📦 Extract the archive to get the tzst executable (or tzst.exe on Windows)

  4. +
  5. 📂 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

    • +
    +
  6. +
  7. ✅ Verify installation: tzst --help

  8. +
+
+
+

🎯 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:

+
pip install tzst
+
+
+

Or using uv (recommended):

+
uv tool install tzst
+
+
+
+
+

From Source

+
git clone https://github.com/xixu-me/tzst.git
+cd tzst
+pip install .
+
+
+
+
+

Development Installation

+

This project uses modern Python packaging standards:

+
git clone https://github.com/xixu-me/tzst.git
+cd tzst
+pip install -e .[dev]
+
+
+
+
+
+

Basic Usage

+
+

Command Line Interface

+
+

Note: Download the standalone binary for the best performance and no Python dependency. Alternatively, use uvx tzst for running without installation. See uv documentation for details.

+
+

The CLI provides four main operations:

+
# Create an archive
+tzst a archive.tzst file1.txt file2.txt directory/
+
+# Extract an archive  
+tzst x archive.tzst
+
+# List archive contents
+tzst l archive.tzst
+
+# Test archive integrity
+tzst t archive.tzst
+
+
+
+
+

Command Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Command

Aliases

Description

Streaming Support

a

add, create

Create or add to archive

N/A

x

extract

Extract with full paths

--streaming

e

extract-flat

Extract without directory structure

--streaming

l

list

List archive contents

--streaming

t

test

Test archive integrity

--streaming

+
+
+

CLI Options

+
    +
  • -v, --verbose: Enable verbose output

  • +
  • -o, --output DIR: Specify output directory (extract commands)

  • +
  • -l, --level LEVEL: Set compression level 1-22 (create command)

  • +
  • --streaming: Enable streaming mode for memory-efficient processing

  • +
  • --filter FILTER: Security filter for extraction (data/tar/fully_trusted)

  • +
  • --no-atomic: Disable atomic file operations (not recommended)

  • +
+
+

Create Archives

+
# Create archive with default compression (level 3)
+tzst a backup.tzst documents/ photos/
+
+# Create with high compression
+tzst a backup.tzst documents/ photos/ --compression-level 9
+
+# Create from current directory
+tzst a project.tzst .
+
+# Specify different output location
+tzst a /backups/data.tzst /home/user/important/
+
+
+
+
+

Extract Archives

+
# Extract to current directory
+tzst x backup.tzst
+
+# Extract to specific directory
+tzst x backup.tzst --output /restore/
+
+# Extract specific files only
+tzst x backup.tzst documents/report.pdf photos/vacation.jpg
+
+# Extract with conflict resolution
+tzst x backup.tzst --conflict-resolution skip
+
+
+
+
+

List Contents

+
# Simple listing
+tzst l backup.tzst
+
+# Detailed listing with file info
+tzst l backup.tzst --verbose
+
+# Stream large archives efficiently
+tzst l huge-archive.tzst --streaming
+
+
+
+
+
+

Python API

+
+

Quick Start

+
from tzst import create_archive, extract_archive, list_archive, test_archive
+
+# Create an archive
+create_archive("backup.tzst", ["documents/", "photos/"], compression_level=5)
+
+# Extract an archive
+extract_archive("backup.tzst", "restore/")
+
+# List contents
+contents = list_archive("backup.tzst", verbose=True)
+for item in contents:
+    print(f"{item['name']} - {item['size']} bytes")
+
+# Test integrity
+is_valid = test_archive("backup.tzst")
+print(f"Archive is {'valid' if is_valid else 'corrupted'}")
+
+
+
+
+

Using the TzstArchive Class

+
from tzst import TzstArchive
+
+# Create a new archive
+with TzstArchive("data.tzst", "w", compression_level=6) as archive:
+    archive.add("file.txt")
+    archive.add("directory/", recursive=True)
+    
+    # Add with custom archive name
+    archive.add("config/prod.yaml", arcname="config.yaml")
+
+# Read an existing archive
+with TzstArchive("data.tzst", "r") as archive:
+    # List contents
+    contents = archive.list(verbose=True)
+    for item in contents:
+        print(f"{item['name']} - {item['size']} bytes")
+    
+    # Test integrity
+    is_valid = archive.test()
+    print(f"Archive is {'valid' if is_valid else 'corrupted'}")
+    
+    # Extract specific files
+    archive.extract("file.txt", "output/")
+    
+    # Extract all files
+    archive.extractall("restore/")
+
+
+
+
+
+
+

Advanced Features

+
+

Security and Filtering

+
from tzst import extract_archive
+
+# Safe extraction with built-in security (default)
+extract_archive("untrusted.tzst", "safe-output/", filter="data")
+
+# For trusted archives with special features
+extract_archive("trusted.tzst", "output/", filter="tar")
+
+
+
+
+

Security Filters

+

tzst provides three security filter options for extraction:

+
from tzst import extract_archive
+
+# Extract with maximum security (default)
+extract_archive("archive.tzst", "output/", filter="data")
+
+# Extract with standard tar compatibility
+extract_archive("archive.tzst", "output/", filter="tar")
+
+# Extract with full trust (dangerous - only for trusted archives)
+extract_archive("archive.tzst", "output/", filter="fully_trusted")
+
+
+

Security Filter Options:

+
    +
  • data (default): Most secure. Blocks dangerous files, absolute paths, and paths outside extraction directory

  • +
  • tar: Standard tar compatibility. Blocks absolute paths and directory traversal

  • +
  • fully_trusted: No security restrictions. Only use with completely trusted archives

  • +
+
+
+

Conflict Resolution

+
from tzst import extract_archive, ConflictResolution
+
+# Skip existing files
+extract_archive("archive.tzst", "output/",
+                conflict_resolution=ConflictResolution.SKIP_ALL)
+
+# Auto-rename conflicting files
+extract_archive("archive.tzst", "output/",
+                conflict_resolution=ConflictResolution.AUTO_RENAME_ALL)
+
+
+
+
+

Performance Optimization

+
from tzst import create_archive, extract_archive
+
+# Create with different compression levels
+create_archive("fast.tzst", files, compression_level=1)    # Fastest
+create_archive("balanced.tzst", files, compression_level=6) # Balanced
+create_archive("best.tzst", files, compression_level=22)   # Best compression
+
+# Memory-efficient operations for large archives
+extract_archive("huge-archive.tzst", "output/", streaming=True)
+
+
+
+
+

Streaming Mode

+

For large archives (>100MB), use streaming mode to reduce memory usage:

+
# Memory-efficient operations
+with TzstArchive("large-archive.tzst", "r", streaming=True) as archive:
+    contents = archive.list()
+    archive.extractall("output/")
+    is_valid = archive.test()
+
+
+

Note: Streaming mode has limitations - you cannot extract specific files or use random access operations.

+
+
+

File Extensions

+

The library automatically handles file extensions with intelligent normalization:

+
    +
  • .tzst - Primary extension for tar+zstandard archives

  • +
  • .tar.zst - Alternative standard extension

  • +
  • Auto-detection when opening existing archives

  • +
  • Automatic extension addition when creating archives

  • +
+
from tzst import create_archive
+
+# These all create valid archives
+create_archive("backup.tzst", files)      # Creates backup.tzst
+create_archive("backup.tar.zst", files)  # Creates backup.tar.zst  
+create_archive("backup", files)          # Creates backup.tzst
+create_archive("backup.txt", files)      # Creates backup.tzst (normalized)
+
+
+
+
+

Atomic Operations

+

All file creation operations use atomic file operations by default:

+
    +
  • Archives created in temporary files first, then atomically moved

  • +
  • Automatic cleanup if process is interrupted

  • +
  • No risk of corrupted or incomplete archives

  • +
  • Cross-platform compatibility

  • +
+
# Atomic operations enabled by default
+create_archive("important.tzst", files)  # Safe from interruption
+
+# Can be disabled if needed (not recommended)
+create_archive("test.tzst", files, use_temp_file=False)
+
+
+
+
+
+

Error Handling

+
from tzst import create_archive, TzstArchiveError, TzstCompressionError
+
+try:
+    create_archive("backup.tzst", ["documents/"])
+except TzstCompressionError as e:
+    print(f"Compression failed: {e}")
+except TzstArchiveError as e:
+    print(f"Archive operation failed: {e}")
+except Exception as e:
+    print(f"Unexpected error: {e}")
+
+
+
+
+

Next Steps

+
    +
  • Explore comprehensive Examples for real-world scenarios

  • +
  • Check the API Reference for detailed API documentation

  • +
  • See advanced features like atomic operations and custom filters

  • +
  • Learn about integration with web frameworks and automation tools

  • +
+
+
+

Read an Existing Archive

+
with TzstArchive("data.tzst", "r") as archive:
+    # List contents
+    contents = archive.list(verbose=True)
+    
+    # Extract specific file
+    archive.extract("file.txt", "output/")
+    
+    # Test integrity
+    is_valid = archive.test()
+    
+    # Get raw member information
+    members = archive.getmembers()
+
+
+
+
+

Common Patterns

+
+

Backup Script

+
#!/usr/bin/env python3
+from pathlib import Path
+from datetime import datetime
+from tzst import create_archive
+
+def create_backup():
+    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+    backup_name = f"backup_{timestamp}.tzst"
+    
+    # Backup important directories
+    directories = ["documents/", "projects/", "config/"]
+    
+    print(f"Creating backup: {backup_name}")
+    create_archive(backup_name, directories, compression_level=6)
+    print(f"Backup created: {Path(backup_name).stat().st_size / 1024 / 1024:.1f} MB")
+
+if __name__ == "__main__":
+    create_backup()
+
+
+
+
+

Archive Verification

+
from tzst import test_archive, list_archive
+
+def verify_archive(archive_path):
+    print(f"Verifying {archive_path}...")
+    
+    # Test integrity
+    if not test_archive(archive_path):
+        print("Archive is corrupted!")
+        return False
+    
+    # List contents
+    contents = list_archive(archive_path, verbose=True)
+    total_size = sum(item['size'] for item in contents if item['is_file'])
+    file_count = sum(1 for item in contents if item['is_file'])
+    print(f"Archive is valid")
+    print(f"Files: {file_count}")
+    print(f"Total size: {total_size / 1024 / 1024:.1f} MB")
+    
+    return True
+
+
+
+
+
+

Further Learning

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 0000000..cbffe5b --- /dev/null +++ b/search.html @@ -0,0 +1,213 @@ + + + + + + + + Search — tzst 1.3.3 Documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2026, Xi Xu.

+
+ + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 0000000..cc2da51 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles":{"1. Compression Levels":[[9,"compression-levels"]],"1. Making Changes":[[6,"making-changes"]],"2. Commit Messages":[[6,"commit-messages"]],"2. Streaming":[[9,"streaming"]],"3. Batch Operations":[[9,"batch-operations"]],"3. Pull Request Process":[[6,"pull-request-process"]],"4. File Type Considerations":[[9,"file-type-considerations"]],"4. Pull Request Guidelines":[[6,"pull-request-guidelines"]],"404 - Page Not Found":[[0,null]],"API Documentation":[[1,"api-documentation"],[8,"api-documentation"]],"API Reference":[[5,null]],"Acknowledgments":[[8,"acknowledgments"]],"Adding New Pages":[[1,"adding-new-pages"]],"Additional Resources":[[0,"additional-resources"]],"Advanced Archive Creation":[[7,"advanced-archive-creation"]],"Advanced Compression":[[8,"advanced-compression"]],"Advanced Features":[[10,"advanced-features"]],"Architecture Overview":[[5,"architecture-overview"]],"Archive Creation Commands":[[2,"archive-creation-commands"],[7,"archive-creation-commands"]],"Archive Inspection Commands":[[7,"archive-inspection-commands"]],"Archive Operation Exceptions":[[4,"archive-operation-exceptions"]],"Archive Verification":[[10,"archive-verification"]],"Atomic Archive Creation":[[7,"atomic-archive-creation"]],"Atomic Operations":[[10,"atomic-operations"]],"Automated Backup System":[[7,"automated-backup-system"]],"Automated Deployment":[[1,"automated-deployment"]],"Backup Script":[[10,"backup-script"]],"Base Exception":[[4,"base-exception"]],"Basic Error Handling":[[4,"basic-error-handling"]],"Basic Extraction":[[7,"basic-extraction"]],"Basic Operations":[[7,"basic-operations"]],"Basic Test Commands":[[6,"basic-test-commands"]],"Basic Usage":[[10,"basic-usage"]],"Benchmarking Examples":[[9,"benchmarking-examples"]],"Best Practices":[[9,"best-practices"]],"Branch Naming":[[6,"branch-naming"]],"Build Warnings":[[1,"build-warnings"]],"Building Documentation":[[1,"building-documentation"],[6,"building-documentation"]],"CLI API":[[2,null]],"CLI Functions":[[5,"cli-functions"]],"CLI Options":[[10,"cli-options"]],"CPU Usage":[[9,"cpu-usage"]],"Code Examples":[[1,"code-examples"]],"Code Quality":[[6,"code-quality"]],"Code Style Guidelines":[[6,"code-style-guidelines"]],"Code of Conduct":[[6,"code-of-conduct"]],"Command Handlers":[[2,"command-handlers"]],"Command Line Equivalent":[[7,"command-line-equivalent"]],"Command Line Interface":[[10,"command-line-interface"]],"Command Line Usage":[[7,"command-line-usage"]],"Command Reference":[[10,"command-reference"]],"Common Patterns":[[10,"common-patterns"]],"Comparison with Other Tools":[[9,"comparison-with-other-tools"]],"Compatibility":[[6,"compatibility"]],"Comprehensive Error Handling":[[4,"comprehensive-error-handling"],[7,"comprehensive-error-handling"]],"Compression Exceptions":[[4,"compression-exceptions"]],"Compression Level Benchmark":[[9,"compression-level-benchmark"]],"Compression Level Optimization":[[7,"compression-level-optimization"]],"Conditional File Addition":[[7,"conditional-file-addition"]],"Configuration":[[6,"configuration"]],"Conflict Resolution":[[10,"conflict-resolution"]],"Conflict Resolution Options":[[2,"conflict-resolution-options"]],"ConflictResolution":[[3,"conflictresolution"]],"ConflictResolutionState":[[3,"conflictresolutionstate"]],"Contents:":[[8,null]],"Contributing":[[1,"contributing"],[8,"contributing"]],"Contributing Workflow":[[6,"contributing-workflow"]],"Convenience Functions":[[3,"convenience-functions"],[5,"convenience-functions"]],"Core API":[[3,null]],"Core Classes":[[5,"core-classes"]],"Core Commands":[[2,"core-commands"]],"Core Features":[[0,"core-features"]],"Create Archives":[[10,"create-archives"]],"Creating Your First Archive":[[7,"creating-your-first-archive"]],"Cross-Platform":[[5,"cross-platform"]],"Cross-References":[[1,"cross-references"]],"Custom Conflict Resolution":[[7,"custom-conflict-resolution"]],"Custom Extraction Logic":[[7,"custom-extraction-logic"]],"Custom Security Filter":[[7,"custom-security-filter"]],"Data Migration Tool":[[7,"data-migration-tool"]],"Decompression Exceptions":[[4,"decompression-exceptions"]],"Developer Friendly":[[5,"developer-friendly"]],"Development":[[8,"development"]],"Development Guide":[[6,null]],"Development Installation":[[10,"development-installation"]],"Development Tips":[[6,"development-tips"]],"Django Management Command":[[7,"django-management-command"]],"Documentation":[[1,null],[6,"documentation"]],"Documentation Guide":[[8,"documentation-guide"]],"Documentation Structure":[[1,"documentation-structure"],[6,"documentation-structure"]],"Dual Interfaces":[[8,"dual-interfaces"]],"Enums and Supporting Classes":[[3,"enums-and-supporting-classes"]],"Error Handling":[[7,"error-handling"],[10,"error-handling"]],"Error Handling Best Practices":[[4,"error-handling-best-practices"]],"Error Recovery Patterns":[[4,"error-recovery-patterns"]],"Examples":[[7,null]],"Examples & Tutorials":[[0,"examples-tutorials"]],"Exception Classes":[[4,"exception-classes"],[5,"exception-classes"]],"Exception Hierarchy":[[4,"exception-hierarchy"]],"Exceptions API":[[4,null]],"Extract Archives":[[10,"extract-archives"]],"Extracting with Different Structures":[[7,"extracting-with-different-structures"]],"Extraction Commands":[[2,"extraction-commands"],[7,"extraction-commands"]],"File Extensions":[[10,"file-extensions"]],"Flask Application Integration":[[7,"flask-application-integration"]],"Flexible Extraction":[[7,"flexible-extraction"]],"For Archival Storage":[[9,"for-archival-storage"]],"For Backups":[[9,"for-backups"]],"For Development":[[9,"for-development"]],"For Distribution":[[9,"for-distribution"]],"From GitHub Releases":[[10,"from-github-releases"]],"From PyPI":[[10,"from-pypi"]],"From Source":[[10,"from-source"]],"Further Learning":[[10,"further-learning"]],"Getting Help":[[6,"getting-help"]],"Getting Started":[[8,"getting-started"]],"GitHub Actions Example":[[9,"github-actions-example"]],"Handling File Conflicts":[[7,"handling-file-conflicts"]],"Hardware Considerations":[[9,"hardware-considerations"]],"High Performance":[[5,"high-performance"],[8,"high-performance"]],"Import Errors":[[1,"import-errors"]],"Installation":[[8,"installation"],[10,"installation"]],"Integration Examples":[[7,"integration-examples"]],"Integration with Build Systems":[[9,"integration-with-build-systems"]],"Interactive Features":[[2,"interactive-features"]],"Jupyter Notebook Integration":[[7,"jupyter-notebook-integration"]],"Key Features":[[2,"key-features"],[3,"key-features"],[5,"key-features"],[8,"key-features"]],"License":[[8,"license"]],"List Contents":[[10,"list-contents"]],"Listing Archive Contents":[[7,"listing-archive-contents"]],"Live Reload (Recommended for Development)":[[1,"live-reload-recommended-for-development"]],"Local Development":[[1,"local-development"]],"Local Testing of Deployment":[[1,"local-testing-of-deployment"]],"Log File Archiver":[[7,"log-file-archiver"]],"Logging Integration":[[4,"logging-integration"]],"Main Components":[[5,"main-components"]],"Main Functions":[[2,"main-functions"]],"Makefile Example":[[9,"makefile-example"]],"Management Commands":[[2,"management-commands"]],"Markdown vs reStructuredText":[[1,"markdown-vs-restructuredtext"]],"Memory Usage":[[9,"memory-usage"]],"Memory Usage Comparison":[[9,"memory-usage-comparison"]],"Next Steps":[[10,"next-steps"]],"Oops! The page you\u2019re looking for doesn\u2019t exist":[[0,"oops-the-page-youre-looking-for-doesnt-exist"]],"Other Build Targets":[[1,"other-build-targets"]],"Overview":[[2,"overview"],[4,"overview"],[5,"overview"]],"Parallel Processing":[[7,"parallel-processing"]],"Performance Considerations":[[6,"performance-considerations"]],"Performance Guide":[[9,null]],"Performance Optimization":[[7,"performance-optimization"],[10,"performance-optimization"]],"Performance Options":[[2,"performance-options"]],"Performance Tips":[[9,"performance-tips"]],"Popular Pages":[[0,"popular-pages"]],"Project Structure":[[6,"project-structure"]],"Python API":[[10,"python-api"]],"Quick Example":[[8,"quick-example"]],"Quick Navigation":[[0,"quick-navigation"]],"Quick Reference":[[5,"quick-reference"]],"Quick Start":[[8,"quick-start"],[10,"quick-start"]],"Quick Start Guide":[[10,null]],"Read an Existing Archive":[[10,"read-an-existing-archive"]],"Real-World Scenarios":[[7,"real-world-scenarios"]],"Recognition":[[6,"recognition"]],"Release Process":[[6,"release-process"]],"Report an Issue":[[0,"report-an-issue"]],"Reporting Issues":[[6,"reporting-issues"]],"Requirements":[[8,"requirements"]],"Resources":[[6,"resources"]],"Running Code Style Tools":[[6,"running-code-style-tools"]],"Running Tests":[[6,"running-tests"]],"Safe Extraction Practices":[[7,"safe-extraction-practices"]],"Search Documentation":[[0,"search-documentation"]],"Security Considerations":[[2,"security-considerations"],[6,"security-considerations"]],"Security Filters":[[10,"security-filters"]],"Security First":[[5,"security-first"],[8,"security-first"]],"Security and Filtering":[[7,"security-and-filtering"],[10,"security-and-filtering"]],"Selective Extraction":[[7,"selective-extraction"]],"Setting up Development Environment":[[6,"setting-up-development-environment"]],"Setup":[[1,"setup"]],"Specific Exception Handling":[[4,"specific-exception-handling"]],"Storage":[[9,"storage"]],"Streaming Mode":[[10,"streaming-mode"]],"Streaming for Large Archives":[[7,"streaming-for-large-archives"]],"Suggesting Features":[[6,"suggesting-features"]],"Supported Platforms":[[10,"supported-platforms"]],"Table of Contents":[[7,"table-of-contents"]],"Test Structure":[[6,"test-structure"]],"Theme Issues":[[1,"theme-issues"]],"Troubleshooting":[[1,"troubleshooting"]],"Types of Contributions":[[6,"types-of-contributions"]],"Types of Contributions Welcome":[[8,"types-of-contributions-welcome"]],"TzstArchive Class":[[3,"tzstarchive-class"]],"TzstArchiveError":[[4,"tzstarchiveerror"]],"TzstCompressionError":[[4,"tzstcompressionerror"]],"TzstDecompressionError":[[4,"tzstdecompressionerror"]],"TzstError":[[4,"tzsterror"]],"Usage Examples":[[3,"usage-examples"]],"Using the TzstArchive Class":[[10,"using-the-tzstarchive-class"]],"Utility Functions":[[2,"utility-functions"]],"Validation and Recovery":[[7,"validation-and-recovery"]],"What is tzst?":[[8,"what-is-tzst"]],"Where would you like to go?":[[0,"where-would-you-like-to-go"]],"Working with the TzstArchive Class":[[7,"working-with-the-tzstarchive-class"]],"Writing Documentation":[[1,"writing-documentation"],[6,"writing-documentation"]],"Writing Tests":[[6,"writing-tests"]],"cmd_add":[[2,"cmd-add"]],"cmd_extract_flat":[[2,"cmd-extract-flat"]],"cmd_extract_full":[[2,"cmd-extract-full"]],"cmd_list":[[2,"cmd-list"]],"cmd_test":[[2,"cmd-test"]],"cmd_version":[[2,"cmd-version"]],"create_archive":[[3,"create-archive"]],"create_parser":[[2,"create-parser"]],"extract_archive":[[3,"extract-archive"]],"format_size":[[2,"format-size"]],"list_archive":[[3,"list-archive"]],"main":[[2,"main"]],"print_banner":[[2,"print-banner"]],"test_archive":[[3,"test-archive"]],"tzst Documentation":[[8,null]],"validate_compression_level":[[2,"validate-compression-level"]],"vs tar + gzip":[[9,"vs-tar-gzip"]],"vs tar + xz":[[9,"vs-tar-xz"]],"vs zip":[[9,"vs-zip"]],"\ud83c\udfaf Benefits of Binary Installation":[[10,"benefits-of-binary-installation"]],"\ud83d\udee0\ufe0f Installation Steps":[[10,"installation-steps"]]},"docnames":["404","README","api/cli","api/core","api/exceptions","api/index","development","examples","index","performance","quickstart"],"envversion":{"sphinx":66,"sphinx.domains.c":3,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":9,"sphinx.domains.index":1,"sphinx.domains.javascript":3,"sphinx.domains.math":2,"sphinx.domains.python":4,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1},"filenames":["404.md","README.md","api/cli.md","api/core.md","api/exceptions.md","api/index.md","development.md","examples.md","index.md","performance.md","quickstart.md"],"indexentries":{"__enter__() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.__enter__",false]],"__exit__() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.__exit__",false]],"__init__() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.__init__",false]],"add() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.add",false]],"close() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.close",false]],"create_archive() (in module tzst)":[[3,"tzst.create_archive",false]],"create_parser() (in module tzst.cli)":[[2,"tzst.cli.create_parser",false]],"extract() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.extract",false]],"extract_archive() (in module tzst)":[[3,"tzst.extract_archive",false]],"extractall() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.extractall",false]],"extractfile() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.extractfile",false]],"format_size() (in module tzst.cli)":[[2,"tzst.cli.format_size",false]],"getmembers() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.getmembers",false]],"getnames() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.getnames",false]],"list() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.list",false]],"list_archive() (in module tzst)":[[3,"tzst.list_archive",false]],"main() (in module tzst.cli)":[[2,"tzst.cli.main",false]],"open() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.open",false]],"print_banner() (in module tzst.cli)":[[2,"tzst.cli.print_banner",false]],"test() (tzst.tzstarchive method)":[[3,"tzst.TzstArchive.test",false]],"test_archive() (in module tzst)":[[3,"tzst.test_archive",false]],"tzstarchive (class in tzst)":[[3,"tzst.TzstArchive",false]],"tzstarchiveerror":[[4,"tzst.exceptions.TzstArchiveError",false]],"tzstcompressionerror":[[4,"tzst.exceptions.TzstCompressionError",false]],"tzstdecompressionerror":[[4,"tzst.exceptions.TzstDecompressionError",false]],"tzsterror":[[4,"tzst.exceptions.TzstError",false]],"validate_compression_level() (in module tzst.cli)":[[2,"tzst.cli.validate_compression_level",false]]},"objects":{"tzst":[[3,0,1,"","TzstArchive"],[3,2,1,"","create_archive"],[3,2,1,"","extract_archive"],[3,2,1,"","list_archive"],[3,2,1,"","test_archive"]],"tzst.TzstArchive":[[3,1,1,"","__enter__"],[3,1,1,"","__exit__"],[3,1,1,"","__init__"],[3,1,1,"","add"],[3,1,1,"","close"],[3,1,1,"","extract"],[3,1,1,"","extractall"],[3,1,1,"","extractfile"],[3,1,1,"","getmembers"],[3,1,1,"","getnames"],[3,1,1,"","list"],[3,1,1,"","open"],[3,1,1,"","test"]],"tzst.cli":[[2,2,1,"","create_parser"],[2,2,1,"","format_size"],[2,2,1,"","main"],[2,2,1,"","print_banner"],[2,2,1,"","validate_compression_level"]],"tzst.exceptions":[[4,3,1,"","TzstArchiveError"],[4,3,1,"","TzstCompressionError"],[4,3,1,"","TzstDecompressionError"],[4,3,1,"","TzstError"]]},"objnames":{"0":["py","class","Python class"],"1":["py","method","Python method"],"2":["py","function","Python function"],"3":["py","exception","Python exception"]},"objtypes":{"0":"py:class","1":"py:method","2":"py:function","3":"py:exception"},"terms":{"100mb":[2,3,7,9,10],"10x":9,"1f":[7,9,10],"2f":[7,9],"3x":9,"4x":9,"500gb":7,"A":[2,3,7,10],"All":[2,4,8,10],"An":7,"Be":6,"Cannot":7,"Could":7,"Each":3,"For":[1,2,7,8,10],"Further":8,"How":3,"I":[7,8,9],"If":[0,1,2,3],"In":3,"It":[2,8],"More":9,"Most":10,"No":[2,7,10],"On":[1,6],"Or":[6,7,8,10],"Other":[2,8],"Same":2,"Some":3,"The":[1,2,3,4,5,6,7,8,10],"There":0,"These":[7,10],"This":[0,1,2,4,5,6,7,9,10],"To":1,"We":8,"When":[1,6,9],"With":7,"You":[0,1],"Your":[0,6],"_":7,"_1":7,"_2":7,"__enter__":3,"__exit__":3,"__init__":[3,6,7],"__main__":[6,7,10],"__name__":[4,7,10],"__pycache__":7,"_build":[1,6],"_level_":[7,9],"_recoveri":7,"abov":6,"absolut":10,"access":[3,4,9,10],"acknowledg":6,"across":[2,5],"actual_checksum":7,"add":[2,3,6,7,8,9,10],"add_argu":7,"addit":[3,6,10],"address":[1,6,8],"advanc":[2,3,5],"advantag":9,"age":7,"agg":7,"algorithm":[8,9],"alias":[2,10],"allow":[1,2,7],"alreadi":[1,7,9],"also":3,"altern":[3,7,9,10],"alway":[2,7],"amd64":10,"analysi":[6,7],"analyz":7,"analyze_arch":7,"ani":[2,4,7],"api":[0,6,7],"app":[7,9],"appear":7,"append":[3,7,9],"appl":10,"appli":[2,3],"applic":[2,4,5],"apply_to_al":3,"appropri":[1,2,10],"architectur":10,"archiv":[0,1,3,5,6,8],"archive api":5,"archive error":4,"archive performance optim":9,"archive_cont":7,"archive_dataset":7,"archive_dir":7,"archive_logs_by_d":7,"archive_nam":7,"archive_path":[3,4,7,10],"archived_fil":7,"arcnam":[3,7,10],"arg":[2,7,9],"argpars":2,"argument":2,"argumentpars":2,"argumenttypeerror":2,"argv":2,"arm64":10,"artifact":9,"as_attach":7,"as_complet":7,"ask":[3,7],"atom":[2,3,4,8],"attach":6,"attack":[3,8],"attempt":[2,7],"attribut":3,"auto":[2,6,7,10],"auto_renam":3,"auto_rename_al":[3,7,10],"autobuild":1,"autoclass":1,"autodoc":1,"autofunct":1,"autom":[2,6,10],"automat":[1,2,3,7,10],"automodul":1,"auxiliari":6,"avail":8,"ax1":7,"ax2":7,"b":[2,6,7],"back":7,"backup":[2,3,4,8],"backup1":9,"backup2":9,"backup_":[7,10],"backup_destin":7,"backup_dir":7,"backup_fil":7,"backup_filenam":7,"backup_manag":7,"backup_media":7,"backup_nam":[7,10],"backup_path":[4,7],"backup_project":7,"backup_sourc":7,"backupmanag":7,"backward":6,"balanc":[7,9,10],"bandwidth":9,"banner":2,"bar":7,"base":[3,5,7,8,9],"basecommand":7,"basic":[0,2,8],"bat":[1,6],"batch":[3,5,8],"befor":[1,2,4],"behavior":[2,3,5,6,7,10],"believ":0,"benchmark":8,"benchmark_compression_level":[7,9],"benefit":9,"best":[0,5,7,8,10],"better":[6,9],"bin":[7,10],"binari":[7,8],"bind":8,"block":[1,2,3,10],"bodi":6,"bool":[2,3],"bottleneck":6,"bound":7,"box":0,"branch":1,"broken":[0,1],"brows":0,"browser":[1,6],"bsd":[6,8],"buffer":[4,8],"bug":[6,8],"build":[7,8],"built":[1,3,5,6,8,10],"bundl":10,"byte":[2,7,10],"c":[2,7],"calcul":7,"calculate_checksum":7,"call":3,"callabl":3,"callback":7,"can":[1,2,3,4,10],"capabl":[6,8],"case":[0,1,3,6,7,9],"catch":4,"categor":6,"caus":2,"cautious":6,"cd":[6,8,9,10],"cell":7,"centric":9,"certain":7,"chang":[0,1],"changelog":1,"charact":6,"check":[0,1,2,3,4,6,7,8,10],"checkout":6,"checksum":7,"checksum_fil":7,"checksum_path":7,"choos":[2,8,9,10],"chore":6,"chunk":7,"ci":[6,9],"class":[0,1,6,8],"claus":[6,8],"clean":[1,5,7,8],"cleanup":[4,10],"cleanup_old_backup":7,"cli":[0,1,6,8],"clone":[6,8,10],"close":3,"code":[0,2,8,9],"collabor":6,"collect":7,"collis":2,"com":[6,8,10],"combin":8,"command":[0,5,8,9],"command line interfac":2,"comment":6,"common":[0,3,4,5,6,8],"communiti":8,"compar":[7,9],"comparison":[6,8],"compat":[4,8,9,10],"compil":7,"complet":[0,2,4,5,6,7,8,10],"complex":[6,7],"compon":6,"comprehens":[2,3,5,6,8,10],"compress":[2,3,5,6,10],"compress_tim":[7,9],"compression benchmark":9,"compression error":4,"compression_level":[2,3,4,7,8,9,10],"concurr":7,"conduct":8,"conf":[1,6,7],"config":[7,10],"configur":[1,2,3,5,7,8],"conflict":[3,8],"conflict_resolut":[3,7,10],"conflictresolut":[7,10],"conftest":6,"consid":[6,9],"consider":8,"consist":[2,5,10],"consol":2,"consolid":2,"constrain":9,"construct":6,"contain":[1,2,3,5,7,10],"content":[2,3,9],"contents_df":7,"contents_stream":7,"context":[3,5],"continu":[3,7],"contribut":0,"contributing to tzst":6,"contributor":6,"control":[2,5,7,9],"conveni":8,"convent":6,"convert":2,"copyright":[2,8],"core":[1,6,7,8,9],"correct":1,"corrupt":[2,3,4,6,7,8,10],"count":7,"cov":6,"cover":[8,9],"coverag":[1,6,8],"creat":[0,1,2,3,4,6,8,9],"create_arch":[2,4,7,8,9,10],"create_archive_batch":7,"create_backup":[7,10],"create_backup_saf":7,"create_migration_packag":7,"create_oper":7,"create_single_arch":7,"creation":[0,3,4,8,9,10],"critic":[7,9],"cross":[2,3,6,8,9,10],"csv":7,"ctrl":2,"current":[3,7,10],"current_resolut":3,"custom":[3,4,5,6,10],"custom_conflict_handl":7,"cut":8,"cutoff_d":7,"d":[1,7],"d_":[7,10],"daili":[7,9],"danger":[2,3,8,10],"darwin":10,"data":[2,3,4,6,8,9,10],"data_dir":7,"data_path":7,"datafram":7,"datamigr":7,"dataset":[7,9],"date":7,"date_key":7,"date_match":7,"datetim":[7,10],"day":7,"days_old":7,"debug":[4,5,7],"decim":2,"decis":3,"decompress":[2,3,7,8,9],"dedic":2,"def":[4,6,7,9,10],"default":[2,3,5,6,7,8,9,10],"defin":[1,2,6,7],"delet":[4,7],"deliv":8,"demonstr":7,"deni":[4,7],"depend":[1,6,7,10],"deploy":10,"deprec":3,"descript":[2,6,10],"design":[2,5],"destin":[3,7],"detail":[2,3,5,7,8,9,10],"detect":10,"dev":[6,8,9,10],"develop":0,"df":7,"dict":3,"dictionari":3,"differ":[2,4,6,9,10],"dir":[7,10],"direct":[1,3,7],"directori":[1,2,3,4,6,7,9,10],"disabl":[2,10],"discuss":6,"disk":4,"dispatch":2,"display":[2,7],"dist":9,"distribut":10,"doc":[1,6,7,9],"docstr":[1,6],"document":[2,3,4,5,7,9,10],"docx":7,"doe":4,"don":[9,10],"doubl":6,"download":[0,7,10],"download_nam":7,"ds_store":7,"due":[3,4],"dure":[2,3,4,8,9],"e":[1,2,4,6,7,8,10],"eas":5,"easi":[4,10],"edg":[6,8],"effici":[2,3,5,7,8,9,10],"effort":6,"either":1,"els":[7,10],"empti":6,"enabl":10,"encount":4,"endpoint":7,"endswith":7,"engin":8,"ensur":[1,2,6,7],"enter":3,"enterpris":[7,8],"entri":[2,3,6],"enum":5,"enumer":3,"env":[7,10],"environ":[8,9,10],"epilog":2,"error":[0,2,5,8],"error handl":4,"etc":[2,7],"everyon":6,"everyth":7,"exampl":[2,5,6,10],"exc_tb":3,"exc_typ":3,"exc_val":3,"excel":8,"except":[0,1,6,7,8,10],"exclud":7,"exclude_pattern":7,"exclus":[7,8],"exe":10,"execut":[2,7,9,10],"executor":7,"exist":[1,2,3,4,6,7,8],"exist_ok":7,"exit":[2,3,7],"expand":7,"expandus":7,"expect":7,"expected_checksum":7,"explain":6,"explor":[8,10],"export":6,"ext":7,"extens":[5,7,8],"extract":[3,4,5,8,9],"extract_arch":[2,4,7,8,9,10],"extract_by_extens":7,"extract_dir":7,"extract_fil":7,"extract_large_files_on":7,"extract_path":[3,7],"extractal":[3,4,7,10],"extracted_count":7,"extracted_fil":7,"extractfil":3,"f":[4,7,9,10],"factor":9,"fail":[2,4,7,10],"failur":[2,4],"fall":7,"fallback":4,"fals":[3,4,7,10],"fast":[8,9,10],"faster":[7,9,10],"fastest":[2,10],"feat":6,"featur":[1,7],"feedback":[6,8],"fenc":1,"fig":7,"figsiz":7,"file":[1,2,3,4,6,8],"file manag":8,"file1":[9,10],"file2":[9,10],"file_age_day":7,"file_count":10,"file_d":7,"file_date_str":7,"file_group":7,"file_path":7,"file_s":[7,9],"file_summari":7,"file_typ":7,"filenam":[2,3,7],"filenotfounderror":[4,7],"files_to_arch":7,"files_to_backup":7,"filesystem":6,"fill":6,"filter":[0,2,3,5,6,8],"filtered_memb":7,"final":[2,3,4,7],"find":0,"fine":[2,7,9],"first":[0,2,3,4,10],"fit":9,"fix":[1,6,8],"fixabl":6,"fixtur":6,"flat":[2,7,10],"flatten":[2,3,7],"flexibl":[3,8],"focus":6,"follow":[0,1,5,6],"footer":6,"fork":6,"format":[2,4,6,8],"found":[2,4,7],"four":10,"framework":[6,10],"frequent":9,"friend":[2,8],"fromtimestamp":7,"full":[2,9,10],"fully_trust":[2,3,7,8,10],"func":9,"function":[0,1,6,8,9],"futur":[3,7],"future_to_group":7,"g":[1,2,6],"gb":2,"general":2,"generat":[1,6,8],"get":[0,1,3,7,9,10],"get_json":7,"getlogg":4,"getmemb":[3,7,10],"getnam":3,"getpid":9,"getsiz":7,"gif":7,"git":[6,7,8,10],"github":[0,1,6,8],"glob":7,"global":[2,3],"good":9,"got":7,"grace":6,"grade":8,"grain":[2,7,9],"group":7,"group_nam":7,"groupbi":7,"guid":[0,1],"guidanc":10,"guidelin":8,"h":[7,10],"handl":[0,2,3,5,6,8,9],"handler":[4,5],"happen":0,"hardwar":8,"hashlib":7,"hdds":9,"header":[1,2],"help":[0,2,4,7,8,9,10],"hexdigest":7,"hierarchi":5,"high":[1,2,3,7,10],"higher":[8,9],"hint":6,"histor":9,"home":[7,10],"homepag":[0,1],"honor":3,"html":[1,6],"http":1,"https":[6,8,10],"huge":[7,10],"human":2,"id":3,"idea":6,"identifi":4,"imag":7,"implement":[2,6],"import":[4,6,7,8,9,10],"important_fil":3,"improv":[6,8],"includ":[1,2,3,4,5,6,8],"incomplet":[2,4,10],"index":[0,1,6,7,8],"indexerror":7,"individu":[2,6,7,9],"info":[4,7,10],"inform":[1,2,3,5,6,7,8,10],"inherit":4,"initi":[3,6],"initial_memori":9,"initial_resolut":3,"input":[4,6],"inspir":8,"instal":[0,1,6,7],"instanti":3,"instruct":[6,8],"insuffici":4,"int":[2,3,7],"integ":2,"integr":[0,2,3,6,8,10],"intel":10,"intellig":[8,10],"interact":[3,5,6,7],"interactive_callback":[3,7],"interfac":[0,2,5,6],"internal":[4,7],"interpret":10,"interrupt":[2,10],"introduct":8,"intuit":[2,8],"invalid":[2,4,6,7],"is_dir":7,"is_fil":10,"is_valid":[3,7,8,9,10],"isab":7,"isdir":7,"isfil":7,"isn":1,"isort":6,"issu":[3,8],"item":[0,7,8,10],"iter":7,"join":7,"jpg":[7,9,10],"json":7,"jsonifi":7,"just":10,"kb":[2,7],"keep":[2,6,7],"keyword":6,"kind":6,"kwarg":[7,9],"l":[2,7,9,10],"lambda":7,"land":6,"languag":1,"larg":[2,3,5,6,8,9,10],"large_arch":[7,9],"large_fil":7,"larger":[7,9],"latest":[0,10],"latex":1,"latexpdf":1,"layer":5,"learn":[0,8],"legaci":9,"len":7,"length":6,"less":9,"level":[2,3,4,5,8,10],"levels_to_test":[7,9],"leverag":8,"librari":[1,4,5,6,7,8,10],"licens":[6,7],"like":[1,3,7,10],"limit":[3,7,9,10],"line":[0,2,5,6,8,9],"link":[0,1,6],"linkcheck":1,"lint":6,"linux":[2,5,6,7,8,10],"list":[1,2,3,6,8],"list_arch":[2,7,9,10],"livehtml":1,"load":9,"local":[7,10],"localhost":1,"locat":[2,3,4,7,10],"log":[6,9],"log_dir":7,"log_fil":7,"log_group":7,"log_pattern":7,"logger":4,"logic":[4,6],"logs_":7,"long":9,"lower":9,"m":[1,6,7,10],"maco":[1,2,5,6,7,8,10],"main":[1,3,6,8,10],"maintain":[2,6],"major":8,"make":[1,4,7],"makedir":7,"malform":[1,4],"malici":6,"manag":[3,5,8],"manipul":[3,5],"mark":6,"markdown":6,"marker":6,"markup":1,"match":7,"matplotlib":7,"max":7,"max_work":7,"maximum":[2,7,9,10],"may":[2,3,4,9],"mb":[2,7,9,10],"md":[1,6,7],"meaning":6,"measur":[7,9],"media":7,"media_backup_":7,"media_root":7,"member":[2,3,7,10],"memor":2,"memori":[2,3,5,6,7,8,10],"memory_info":9,"memory_norm":9,"memory_stream":9,"mention":6,"messag":2,"meta":8,"metadata":[2,3,9],"method":[2,3,6,7,10],"might":0,"mimetyp":7,"min_size_byt":7,"min_size_mb":7,"mind":5,"minim":[6,8,9],"minut":10,"mismatch":[4,7],"miss":1,"mix":7,"mkdir":7,"mkdtemp":7,"mode":[1,2,3,6,7,8,9],"moder":9,"modern":[6,8,9,10],"modif":[2,3,6,7],"modifi":6,"modul":[1,2,3,6],"monitor":9,"monitor_memory_usag":9,"motiv":6,"move":[0,2,3,4,7,8,10],"mp4":9,"mtime":7,"much":9,"multi":9,"multipl":[2,3,5,6,7,8,9],"mv":10,"myproject":7,"myst":[1,6],"n":[2,7,10],"name":[2,3,4,7,9,10],"namedtemporaryfil":[4,7],"nativ":[5,8],"natur":3,"navig":6,"necessari":6,"need":[0,3,6,7,8,10],"network":[7,9],"never":3,"new":[2,3,6,7,8,10],"newer":7,"next":8,"no_atom":2,"node_modul":7,"non":[2,4,9],"none":[2,3,4,7],"normal":[2,3,9,10],"note":[0,2,6,7,10],"now":[7,10],"number":2,"numer":3,"numeric_own":3,"o":[2,7,8,9,10],"object":[3,8],"occur":[4,7],"octet":7,"old":[7,9],"older":7,"one":[3,6],"onli":[2,3,6,7,10],"open":[1,3,4,6,7,10],"oper":[2,3,5,8],"optim":[0,5,6,8,9],"option":[3,5,6,7,8],"orient":8,"origin":[2,6,7],"os":[6,7,9],"otherwis":3,"output":[2,4,6,7,8,10],"output1":9,"output2":9,"output_arch":7,"output_dir":[4,7],"output_fil":[7,9],"output_path":7,"output_prefix":[7,9],"outsid":10,"overal":2,"overflow":4,"overhead":10,"overview":[1,8],"overwrit":[2,3,7],"owner":3,"packag":[0,1,6,7,9,10],"package_nam":7,"package_path":7,"package_s":7,"page":[6,10],"page not found":0,"panda":7,"paper":7,"paramet":[2,3],"parent":[1,4,7],"parquet":7,"pars":[2,7],"parser":[1,2,7],"partial":7,"pass":[2,6,7],"path":[1,2,3,4,5,6,7,8,9,10],"pathlib":[4,7,9,10],"pattern":[6,7,8],"pb":2,"pd":7,"pdf":[1,7,9,10],"peak_memori":9,"pep":6,"per":6,"perf":6,"perform":[0,4],"period":[7,9],"permiss":[2,4,7,9],"permissionerror":[4,7],"photo":[2,7,8,10],"pictur":7,"pip":[1,6,8,10],"place":2,"platform":[2,3,6,8,9],"pleas":[0,6,8],"plt":7,"png":[7,9],"point":[2,6],"posix":9,"possibl":[6,7,9],"post":7,"potenti":[2,7],"power":[2,7],"pr":6,"practic":[0,1,5,8],"prefer":[9,10],"preserv":[2,7,9],"prevent":[2,3,4,7,8],"primari":[3,10],"principl":6,"print":[2,4,7,9,10],"process":[1,2,3,8,9,10],"prod":[9,10],"product":[7,9],"profession":6,"profil":6,"program":2,"programmat":8,"project":[7,8,9,10],"project_path":7,"prompt":[2,3,6],"proper":[1,3,6],"properti":3,"proport":9,"protect":[3,5,8],"proven":8,"provid":[2,3,4,5,6,7,8,9,10],"prs":6,"psutil":9,"public":6,"publish":6,"publish_doc":1,"pull":8,"push":[1,6],"py":[1,6,7],"pyc":7,"pyo":7,"pypi":[0,6,8],"pyplot":7,"pyproject":[2,6],"pytest":[6,8],"python":[1,3,4,5,6,7,8],"python api document":5,"python archive class":3,"python archive exampl":7,"python archive tutori":10,"python c":2,"python develop":6,"python except":4,"python3":[7,10],"qualiti":[1,8],"question":6,"quick":1,"quickstart":[1,6],"quot":6,"r":[1,3,4,6,7,8,10],"rais":[2,4,7],"random":10,"rang":[4,7],"ratio":[8,9],"raw":10,"rawdescriptionhelpformatt":2,"rb":7,"re":7,"read":[2,3,4,7,8],"read_text":7,"readabl":2,"readi":2,"readm":[6,7],"real":[8,9,10],"reason":6,"rebuild":1,"recent":7,"recogn":6,"recognit":8,"recommend":[2,3,8,9,10],"recov":7,"recurs":[2,3,7,9,10],"reduc":[2,3,9,10],"ref_nam":9,"refactor":6,"refer":[0,2,6,8],"refresh":1,"regardless":9,"regular":[7,9],"rel_path":7,"relat":[4,6,7],"relative_to":7,"releas":[0,8,9],"relev":[1,6],"reliabl":8,"relpath":7,"remov":[0,7],"removed_count":7,"renam":[0,2,3,7,10],"replac":[2,3,7],"replace_al":[3,7],"report":[2,7,10],"repositori":0,"reproduct":6,"request":[0,7,8],"requir":[1,3,4,6,9,10],"reserv":8,"reset_index":7,"resolut":[3,8],"resolv":1,"resourc":3,"respect":[2,6],"respond":6,"restor":[3,4,7,8,9,10],"restore_dir":4,"restrict":[2,10],"result":[7,9],"retent":7,"retention_day":7,"return":[2,3,4,7,9,10],"review":[6,8],"rglob":7,"right":[8,9],"risk":10,"rmtree":4,"robust":[2,4,5,7],"robust_backup_and_restor":4,"root":7,"rotat":7,"rout":7,"rss":9,"rst":1,"rtd":1,"ruff":6,"run":[7,8,9,10],"run_backup":7,"runtimeerror":7,"s":[0,1,7,9,10],"safe":[2,3,5,6,8,10],"safe_archive_oper":7,"safe_extract":4,"safest":2,"sampl":7,"sample_archive_path":6,"sample_data":7,"save":[1,7,9],"scenario":[4,8,9,10],"scope":6,"script":[2,5,7],"seamless":8,"search":7,"section":[0,5,7,8],"secur":[0,3],"secure_data_filt":7,"see":[1,3,7,8,10],"seek":3,"select":[2,3],"self":7,"send_fil":7,"separ":9,"sequenc":3,"sequenti":3,"serv":[1,2],"server":1,"session":9,"set":[2,3,7,8,9,10],"set_attr":3,"set_titl":7,"set_xlabel":7,"set_ylabel":7,"setup":[7,8],"sever":6,"sha256":7,"sha256_hash":7,"share":7,"shorter":9,"should_continu":3,"show":[2,3,7],"shutil":4,"signific":[6,9],"silicon":10,"similar":9,"simpl":[2,5,7,10],"simpler":6,"simplifi":2,"simul":7,"singl":[2,3,4,7,9,10],"size":[2,3,6,7,9,10],"size_mb":[7,9],"skip":[2,3,7,10],"skip_al":[3,7,10],"slower":9,"smaller":9,"snippet":6,"softwar":9,"sort":6,"sourc":[0,2,3,4,6,7,8,9],"source_dir":[4,7],"space":[4,9],"special":[2,7,10],"specif":[1,2,3,6,7,9,10],"specifi":[2,3,4,7,10],"speed":9,"sphinx":[1,6],"split":7,"src":[1,6,7],"ssds":9,"st_mtime":7,"st_size":[7,9,10],"staging_dir":7,"standalon":[7,8,10],"standard":[2,4,6,7,10],"start":[0,1,6,7],"start_tim":[7,9],"startup":10,"stat":[7,9,10],"state":3,"statement":3,"statist":6,"stdout":[2,7],"stem":7,"step":8,"stop":[2,3],"storag":7,"str":[2,3,7],"strategi":[2,3],"stream":[2,3,5,6,8],"strftime":[7,10],"string":2,"strip":7,"strptime":7,"structur":[2,3,4,8,9,10],"style":[1,7,8],"subcommand":2,"submit":[1,7],"subplot":7,"subsequ":2,"success":[2,3,4,7],"sudo":10,"suffix":[4,7],"suit":6,"suitabl":7,"sum":[7,10],"summari":7,"superior":8,"support":[2,5,6,8,9],"sure":1,"svn":7,"syntax":6,"sys":[2,7],"system":[2,5,6,8],"t":[1,2,7,9,10],"tabl":8,"tag":6,"tar":[2,3,4,5,7,8,10],"tar gzip comparison":9,"tar.zst":8,"tar.zst api refer":5,"tar.zst command":2,"tar.zst funct":3,"tar.zst guid":10,"tar.zst tutori":7,"tarfil":[3,7],"target":[6,7],"target_path":7,"tarinfo":3,"tb":2,"techniqu":9,"temp":[7,9],"temp_arch":7,"temp_backup":4,"temp_dir":6,"temp_fil":4,"temp_path":7,"tempfil":[4,7],"templat":6,"temporari":[2,3,4,7,8,9,10],"temporarili":7,"term":9,"test":[2,3,4,7,8,10],"test_":6,"test_arch":[2,4,7,9,10],"test_c":6,"test_cor":6,"test_create_archive_with_compression_level_9":6,"test_extract_arch":6,"test_full_archive_workflow":6,"testing guid":6,"text":[2,9],"thank":6,"threadpoolexecutor":7,"three":10,"tight_layout":7,"time":[2,3,7,9],"timedelta":7,"timestamp":[2,7,9,10],"timestamp_str":7,"tip":8,"tmp":7,"to_csv":7,"to_parquet":7,"toctre":1,"toml":[2,6],"tool":[2,5,8,10],"top":0,"total":[7,10],"total_s":10,"track":3,"tracker":0,"trade":9,"transfer":7,"travers":[3,5,7,8,10],"tri":[4,7,10],"true":[3,4,7,8,9,10],"truncat":4,"trust":[2,3,7,10],"txt":[1,3,6,7,8,9,10],"type":[2,3,4,7],"typo":0,"tzst":[0,1,2,3,4,5,6,7,9,10],"tzst api":5,"tzst cli api":2,"tzst core api":3,"tzst develop":6,"tzst document":0,"tzst exampl":7,"tzst except":4,"tzst perform":9,"tzst tutori":10,"tzstarchiv":[0,1,2,4,5,8,9],"tzstarchiveerror":[7,10],"tzstcompressionerror":10,"tzstdecompressionerror":7,"tzstfilenotfounderror":4,"underflow":4,"underlying":2,"understand":9,"unexpect":[4,7,10],"unit":[2,6],"unix":[1,9],"unknown":2,"unlink":[4,7],"unsupport":4,"untrust":[2,3,7,10],"updat":[0,1,3,6,7],"update_resolut":3,"upload":7,"url":0,"usag":[0,2,4,6,8],"use":[0,1,2,3,4,5,6,7,8,9],"use_temp_fil":[3,7,10],"user":[1,2,3,6,7,10],"usr":[7,10],"usual":1,"util":5,"uv":[7,8,10],"uvx":[7,10],"v":[2,6,7,10],"v1":7,"vacat":[7,10],"valid":[2,3,4,6,10],"validate_and_repair_arch":7,"valu":[2,3],"valueerror":7,"var":7,"variabl":[6,10],"various":[6,7,9],"verbos":[2,3,6,7,8,10],"veri":[3,9],"verif":7,"verifi":[1,2,3,4,7,10],"verify_and_extract_packag":7,"verify_arch":[4,10],"version":[2,4,6,10],"video":7,"view":6,"visit":1,"visual":7,"vulner":[6,8],"w":[1,3,7,9,10],"walk":7,"warn":[3,7],"way":6,"web":10,"well":[7,9],"whether":3,"will":[1,7,10],"window":[1,2,5,6,8,9,10],"within":[0,2,4],"without":[2,3,6,7,8,9,10],"won":9,"work":[1,3,5,6,8],"workflow":[1,8],"world":[8,10],"wrapper":7,"write":[3,7,8],"x":[2,7,10],"x64":10,"x86_64":10,"xi":8,"xixu":[6,8,10],"xu":8,"y":[7,10],"yaml":[7,10],"yml":1,"zero":2,"zip":10,"zst":[3,5,8,10],"zstandard":[3,4,5,8,10],"zstandard compression exampl":7,"zstandard compression guid":10,"zstd":[8,9]},"titles":["404 - Page Not Found","Documentation","CLI API","Core API","Exceptions API","API Reference","Development Guide","Examples","tzst Documentation","Performance Guide","Quick Start Guide"],"titleterms":{"For":9,"From":10,"Further":10,"Not":0,"Other":[1,9],"The":0,"What":8,"Where":0,"Your":7,"acknowledg":8,"action":9,"add":1,"addit":[0,7],"advanc":[7,8,10],"api":[1,2,3,4,5,8,10],"applic":7,"architectur":5,"archiv":[2,4,7,9,10],"atom":[7,10],"autom":[1,7],"backup":[7,9,10],"base":4,"basic":[4,6,7,10],"batch":9,"benchmark":9,"benefit":10,"best":[4,9],"binari":10,"branch":6,"build":[1,6,9],"chang":6,"class":[3,4,5,7,10],"cli":[2,5,10],"cmd_add":2,"cmd_extract_flat":2,"cmd_extract_ful":2,"cmd_list":2,"cmd_test":2,"cmd_version":2,"code":[1,6],"command":[2,6,7,10],"commit":6,"common":10,"comparison":9,"compat":6,"compon":5,"comprehens":[4,7],"compress":[4,7,8,9],"condit":7,"conduct":6,"configur":6,"conflict":[2,7,10],"conflictresolut":3,"conflictresolutionst":3,"consider":[2,6,9],"content":[7,8,10],"contribut":[1,6,8],"conveni":[3,5],"core":[0,2,3,5],"cpu":9,"creat":[7,10],"create_arch":3,"create_pars":2,"creation":[2,7],"cross":[1,5],"custom":7,"data":7,"decompress":4,"deploy":1,"develop":[1,5,6,8,9,10],"differ":7,"distribut":9,"django":7,"document":[0,1,6,8],"doesn":0,"dual":8,"enum":3,"environ":6,"equival":7,"error":[1,4,7,10],"exampl":[0,1,3,7,8,9],"except":[4,5],"exist":[0,10],"extens":10,"extract":[2,7,10],"extract_arch":3,"featur":[0,2,3,5,6,8,10],"file":[7,9,10],"filter":[7,10],"first":[5,7,8],"flask":7,"flexibl":7,"format_s":2,"found":0,"friend":5,"function":[2,3,5],"get":[6,8],"github":[9,10],"go":0,"guid":[6,8,9,10],"guidelin":6,"gzip":9,"handl":[4,7,10],"handler":2,"hardwar":9,"help":6,"hierarchi":4,"high":[5,8],"import":1,"inspect":7,"instal":[8,10],"integr":[4,7,9],"interact":2,"interfac":[8,10],"issu":[0,1,6],"jupyt":7,"key":[2,3,5,8],"larg":7,"learn":10,"level":[7,9],"licens":8,"like":0,"line":[7,10],"list":[7,10],"list_arch":3,"live":1,"local":1,"log":[4,7],"logic":7,"look":0,"main":[2,5],"make":6,"makefil":9,"manag":[2,7],"markdown":1,"memori":9,"messag":6,"migrat":7,"mode":10,"name":6,"navig":0,"new":1,"next":10,"notebook":7,"oop":0,"oper":[4,7,9,10],"optim":[7,10],"option":[2,10],"overview":[2,4,5],"page":[0,1],"parallel":7,"pattern":[4,10],"perform":[2,5,6,7,8,9,10],"platform":[5,10],"popular":0,"practic":[4,7,9],"print_bann":2,"process":[6,7],"project":6,"pull":6,"pypi":10,"python":10,"qualiti":6,"quick":[0,5,8,10],"re":0,"read":10,"real":7,"recognit":6,"recommend":1,"recoveri":[4,7],"refer":[1,5,10],"releas":[6,10],"reload":1,"report":[0,6],"request":6,"requir":8,"resolut":[2,7,10],"resourc":[0,6],"restructuredtext":1,"run":6,"safe":7,"scenario":7,"script":10,"search":0,"secur":[2,5,6,7,8,10],"select":7,"set":6,"setup":1,"sourc":10,"specif":4,"start":[8,10],"step":10,"storag":9,"stream":[7,9,10],"structur":[1,6,7],"style":6,"suggest":6,"support":[3,10],"system":[7,9],"t":0,"tabl":7,"tar":9,"target":1,"test":[1,6],"test_arch":3,"theme":1,"tip":[6,9],"tool":[6,7,9],"troubleshoot":1,"tutori":0,"type":[6,8,9],"tzst":8,"tzstarchiv":[3,7,10],"tzstarchiveerror":4,"tzstcompressionerror":4,"tzstdecompressionerror":4,"tzsterror":4,"usag":[3,7,9,10],"use":10,"util":2,"valid":7,"validate_compression_level":2,"verif":10,"vs":[1,9],"warn":1,"welcom":8,"work":7,"workflow":6,"world":7,"write":[1,6],"xz":9,"zip":9}}) \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..5db0154 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,2 @@ + +https://tzst.xi-xu.me/404.htmlhttps://tzst.xi-xu.me/README.htmlhttps://tzst.xi-xu.me/api/cli.htmlhttps://tzst.xi-xu.me/api/core.htmlhttps://tzst.xi-xu.me/api/exceptions.htmlhttps://tzst.xi-xu.me/api/index.htmlhttps://tzst.xi-xu.me/development.htmlhttps://tzst.xi-xu.me/examples.htmlhttps://tzst.xi-xu.me/index.htmlhttps://tzst.xi-xu.me/performance.htmlhttps://tzst.xi-xu.me/quickstart.htmlhttps://tzst.xi-xu.me/genindex.htmlhttps://tzst.xi-xu.me/_modules/tzst/cli.htmlhttps://tzst.xi-xu.me/_modules/tzst/core.htmlhttps://tzst.xi-xu.me/_modules/tzst/exceptions.htmlhttps://tzst.xi-xu.me/_modules/index.htmlhttps://tzst.xi-xu.me/search.htmlhttps://tzst.xi-xu.me/opensearch.html \ No newline at end of file