A CLI tool to format and convert data (JSON/YAML/TOML/Python) in the terminal
Project description
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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pormat-0.1.0.tar.gz.
File metadata
- Download URL: pormat-0.1.0.tar.gz
- Upload date:
- Size: 8.9 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
985d6e9c1829bc49fc744e5c8b63965e2c055ee413a8a3a3517ebeec6db27d0f
|
|
| MD5 |
7b8ce97548d5f1900610510822305720
|
|
| BLAKE2b-256 |
00f66b66ae046541bb47a684b9c83526adc4cc3932331b968c578fb6e271ad6e
|
File details
Details for the file pormat-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pormat-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5fb37573f98f57de070d329b0ed7ae8e7c1a5f5570597ec0aae5e37c18d8d6bb
|
|
| MD5 |
39bbe9d44d7ac2fa6d4fe28e93daeddd
|
|
| BLAKE2b-256 |
cbb547a0e505ae94432dcbfba01fdfe29f06cb5f6a1a3a69fe57d0f299d40ffc
|