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
-Cflag - 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)
- Custom path (with
-coption) ./pormat.yml,./pormat.yaml./pormat.toml./pormat.json.pormat.yml,.pormat.yaml.pormat.toml,.pormat.json.env(withPORMAT_prefix)~/.config/pormat/config.yml~/.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
- Separation of Concerns: Parsing, formatting, and detection are independent modules
- Plugin Architecture: Adding a new format only requires two files (parser + formatter)
- Type Safety: Uses
Literaltypes for compile-time format validation - 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)
| File | Size | Uploaded | |
|---|---|---|---|
| pormat-0.1.0.tar.gz | 8.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|