Skip to main content

Pormat

A CLI tool to format and convert data (JSON/YAML/TOML/Python) in the terminal.

Features

  • Auto-detection: Automatically detects input format (JSON, YAML, TOML, or Python literals)
  • Format conversion: Convert between JSON, YAML, TOML, and Python formats
  • Compact mode: Output compact single-line format with -C flag
  • Configurable: Support for configuration files and CLI options
  • Pipe-friendly: Works seamlessly with stdin/stdout for Unix pipeline integration

Installation

pip install pormat

Or using uv:

uv pip install pormat

Usage

Basic Usage

Format input from stdin:

echo '{"name": "John", "age": 30}' | pormat

Format input as argument:

pormat '{"name": "John", "age": 30}'

Convert to different format:

echo '{"name": "John", "age": 30}' | pormat -f yaml

Compact Mode

Output compact single-line format:

echo '{"name": "John", "age": 30}' | pormat --compact
# Output: {"name":"John","age":30}

echo '{"name": "John", "age": 30}' | pormat -C -f yaml
# Output: {name: John, age: 30}

Custom Indentation

echo '{"name": "John", "age": 30}' | pormat -i 2

Reading from Files

cat data.json | pormat -f yaml
pormat -f python < data.json

Format Conversion Examples

JSON to YAML:

echo '{"users": [{"name": "Alice"}, {"name": "Bob"}]}' | pormat -f yaml

YAML to JSON:

echo '- name: Alice
  - name: Bob' | pormat

Python dict to JSON:

echo "{'name': 'John', 'age': 30}" | pormat -f json

JSON to TOML:

echo '{"name": "pormat", "version": "1.0"}' | pormat -f toml
# Output:
# name = "pormat"
# version = "1.0"

TOML to YAML:

cat Cargo.toml | pormat -f yaml

TOML to JSON:

echo '[package]
name = "myapp"
version = "0.1.0"' | pormat -f json
# Output:
# {
#     "package": {
#         "name": "myapp",
#         "version": "0.1.0"
#     }
# }

Configuration

Pormat supports configuration files in multiple formats. Create a configuration file in your project directory or home directory.

Configuration File Locations (searched in order)

  1. Custom path (with -c option)
  2. ./pormat.yml, ./pormat.yaml
  3. ./pormat.toml
  4. ./pormat.json
  5. .pormat.yml, .pormat.yaml
  6. .pormat.toml, .pormat.json
  7. .env (with PORMAT_ prefix)
  8. ~/.config/pormat/config.yml
  9. ~/.pormat.yml

YAML Configuration

# pormat.yml
default_format: yaml
default_indent: 2

TOML Configuration

# pormat.toml
default_format = "yaml"
default_indent = 2

JSON Configuration

{
  "default_format": "yaml",
  "default_indent": 2
}

Environment Variables

# .env
PORMAT_FORMAT=yaml
PORMAT_INDENT=2

Using Custom Config

pormat -c /path/to/config.yml '{"key": "value"}'

Options

Option Short Description
--format -f Output format: json, yaml, toml, python
--indent -i Indentation spaces (default: 4)
--compact -C Output compact single-line format
--config -c Custom config file path
--help Show help message

Real-World Examples

API Response Formatting

# Pretty-print JSON from API
curl -s https://api.github.com/users/github | pormat

# Convert API response to YAML
curl -s https://api.github.com/users/github | pormat -f yaml -i 2

# Convert API response to TOML
curl -s https://api.github.com/repos/python/cpython | pormat -f toml > repo.toml

Configuration File Conversion

# Convert pyproject.toml to YAML
cat pyproject.toml | pormat -f yaml

# Convert package.json to TOML
cat package.json | pormat -f toml

# Convert Docker Compose YAML to JSON
cat docker-compose.yml | pormat -f json

Data Processing

# Compact JSON for storage
echo '{"large": "data", "here": true}' | pormat -C > compact.json

# Pipeline with jq
cat data.yaml | pormat -f json -C | jq '.key'

# Extract specific fields from TOML
cat Cargo.toml | pormat -f json | jq '.package.name'

Multi-Format Workflows

# Python config to TOML
echo "{'database': {'host': 'localhost', 'port': 5432}}" | pormat -f toml

# YAML to Python literal
cat config.yml | pormat -f python

# TOML to Python literal
cat pyproject.toml | pormat -f python

File Comparison

# Compare JSON and YAML files by converting to same format
diff <(cat file1.json | pormat -f yaml) <(cat file2.yaml | pormat -f yaml)

Architecture

Pormat is built with a modular, plugin-based architecture that makes it easy to extend and maintain.

Core Components

pormat/
├── cli.py              # Command-line interface (Typer)
├── detector.py         # Auto-format detection
├── parsers/            # Input format parsers
│   ├── json_parser.py
│   ├── yaml_parser.py
│   ├── toml_parser.py
│   └── python_parser.py
├── formatters/         # Output format formatters
│   ├── json_formatter.py
│   ├── yaml_formatter.py
│   ├── toml_formatter.py
│   └── python_formatter.py
├── config/             # Configuration management
│   ├── defaults.py
│   └── loader.py
└── utils/              # Utility functions
    └── io.py

Data Flow

Input (stdin/arg)
    ↓
Load Configuration
    ↓
Detect Format (detector.py)
    ↓
Parse Input (parsers/*)
    ↓
Python Object
    ↓
Format Output (formatters/*)
    ↓
Output (stdout)

Key Design Principles

  1. Separation of Concerns: Parsing, formatting, and detection are independent modules
  2. Plugin Architecture: Adding a new format only requires two files (parser + formatter)
  3. Type Safety: Uses Literal types for compile-time format validation
  4. Error Handling: Graceful fallbacks with clear error messages

Development

Adding a New Format

To add support for a new format (e.g., XML), follow these steps:

1. Create the Parser

Create src/pormat/parsers/xml_parser.py:

"""XML parser."""

from typing import Any

class XmlParser:
    """Parser for XML format."""

    @staticmethod
    def parse(content: str) -> Any:
        """Parse XML content.

        Args:
            content: The XML string to parse.

        Returns:
            The parsed Python object (dict).

        Raises:
            ValueError: If content is not valid XML.
        """
        # Import your XML library here
        import xmltodict

        try:
            return xmltodict.parse(content)
        except Exception as e:
            raise ValueError(f"Invalid XML: {e}")

2. Create the Formatter

Create src/pormat/formatters/xml_formatter.py:

"""XML formatter."""

from typing import Any

class XmlFormatter:
    """Formatter for XML output."""

    @staticmethod
    def format(data: Any, indent: int = 4, compact: bool = False) -> str:
        """Format data as XML.

        Args:
            data: The data to format (must be a dict).
            indent: Indentation spaces.
            compact: If True, output compact format.

        Returns:
            Formatted XML string.
        """
        import xmltodict

        if compact:
            return xmltodict.unparse(data, pretty=False)
        return xmltodict.unparse(data, pretty=True, indent=" " * indent)

3. Update Exports

Update src/pormat/parsers/__init__.py:

from pormat.parsers.xml_parser import XmlParser

__all__ = ["JsonParser", "YamlParser", "TomlParser", "PythonParser", "XmlParser"]

Update src/pormat/formatters/__init__.py:

from pormat.formatters.xml_formatter import XmlFormatter

__all__ = ["JsonFormatter", "YamlFormatter", "TomlFormatter", "PythonFormatter", "XmlFormatter"]

4. Register in CLI

Update src/pormat/cli.py:

from pormat.parsers.xml_parser import XmlParser
from pormat.formatters.xml_formatter import XmlFormatter

FormatType = Literal["json", "yaml", "toml", "python", "xml"]

FORMATTERS = {
    "json": JsonFormatter,
    "yaml": YamlFormatter,
    "toml": TomlFormatter,
    "python": PythonFormatter,
    "xml": XmlFormatter,  # Add this
}

PARSERS = {
    "json": JsonParser,
    "yaml": YamlParser,
    "toml": TomlParser,
    "python": PythonParser,
    "xml": XmlParser,  # Add this
}

5. Update Type Definitions

Update src/pormat/config/defaults.py:

DEFAULT_FORMAT: Literal["json", "yaml", "toml", "python", "xml"] = "json"

Update src/pormat/config/loader.py:

FormatType = Literal["json", "yaml", "toml", "python", "xml"]

Update src/pormat/detector.py:

FormatType = Literal["json", "yaml", "toml", "python", "xml"]

def detect_format(content: str) -> FormatType:
    # ... existing detection logic ...

    # Add XML detection
    if _try_parse_xml(content):
        return "xml"

    # ... rest of function ...

def _try_parse_xml(content: str) -> bool:
    """Try to parse content as XML."""
    import xmltodict
    try:
        xmltodict.parse(content)
        return True
    except Exception:
        return False

6. Add Dependencies

Update pyproject.toml:

dependencies = [
    "typer>=0.12.0",
    "pyyaml>=6.0",
    "tomli>=2.0",
    "tomli-w>=1.0",
    "python-dotenv>=1.0",
    "xmltodict>=0.13.0",  # Add your library
]

7. Test

uv sync
echo '<root><name>test</name></root>' | uv run pormat -f json

Running Tests

./test.sh

Development Setup

# Clone the repository
git clone <repository-url>
cd pormat

# Install with uv (recommended)
uv sync

# Or with pip
pip install -e .

# Run tests
./test.sh

# Run the tool
uv run pormat '{"key": "value"}'

License

MIT

Release files for pormat 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pormat 0.1.0
File Size Uploaded
pormat-0.1.0.tar.gz 8.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pormat 0.1.0
File Interpreter ABI Platform
pormat-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 24.5 kB

Release files / pormat-0.1.0.tar.gz

Download URL pormat-0.1.0.tar.gz
Size 8.9 kB
Tags Source
SHA-256 checksum
How to use checksums
985d6e9c1829bc49fc744e5c8b63965e2c055ee413a8a3a3517ebeec6db27d0f
BLAKE2b-256 checksum
How to use checksums
00f66b66ae046541bb47a684b9c83526adc4cc3932331b968c578fb6e271ad6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.11 {"installer":{"name":"uv","version":"0.9.11"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pormat-0.1.0-py3-none-any.whl

Download URL pormat-0.1.0-py3-none-any.whl
Size 15.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5fb37573f98f57de070d329b0ed7ae8e7c1a5f5570597ec0aae5e37c18d8d6bb
BLAKE2b-256 checksum
How to use checksums
cbb547a0e505ae94432dcbfba01fdfe29f06cb5f6a1a3a69fe57d0f299d40ffc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.11 {"installer":{"name":"uv","version":"0.9.11"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page