Initial commit: Add http-convert project
Some checks failed
CI / test (push) Has been cancelled

This commit is contained in:
2026-01-29 11:34:21 +00:00
parent 48690221d2
commit b4236d73ba

282
src/http_convert/cli.py Normal file
View File

@@ -0,0 +1,282 @@
import click
from pathlib import Path
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
from rich.prompt import Prompt, Confirm
from rich import print as rprint
from typing import Optional, List
from .models import HTTPRequest, HttpMethod, OutputFormat, InputFormat
from .parsers import Parser
from .generators import Generator
from .highlighter import SyntaxHighlighter
from .config import Config
console = Console()
def format_methods() -> str:
return ", ".join([f"[bold]{m.value}[/bold]" for m in HttpMethod])
def format_output_formats() -> str:
return ", ".join([f"[bold]{f.value}[/bold]" for f in OutputFormat])
@click.group()
@click.option("--config", "-c", type=click.Path(), help="Path to config file")
@click.pass_context
def cli(ctx, config):
ctx.ensure_object(dict)
ctx.obj["config"] = Config() if not config else Config(Path(config))
@cli.command("convert")
@click.argument("input_str", type=str)
@click.option("--from", "-f", "input_format", type=click.Choice(["curl", "httpie", "fetch", "axios"]), help="Input format (auto-detected if not specified)")
@click.option("--to", "-t", "output_format", type=click.Choice(["curl", "httpie", "fetch", "axios"]), required=True, help="Output format")
@click.option("--compact/--no-compact", default=False, help="Compact output (no line breaks)")
@click.option("--highlight/--no-highlight", default=None, help="Enable/disable syntax highlighting")
@click.pass_context
def convert(ctx, input_str, input_format, output_format, compact, highlight):
config = ctx.obj.get("config", Config())
if highlight is None:
highlight = config.syntax_highlighting
try:
if not input_format:
input_format = Parser.detect_format(input_str)
request = Parser.parse(input_str, input_format)
output = Generator.generate(request, OutputFormat(output_format), compact=compact)
console.print(Panel.fit(
f"[bold]Input Format:[/bold] {input_format}\n"
f"[bold]Output Format:[/bold] {output_format}\n"
f"[bold]Method:[/bold] {request.method.value}\n"
f"[bold]URL:[/bold] {request.url}",
title="Request Details"
))
if highlight:
highlighted = SyntaxHighlighter.highlight(output, output_format)
console.print(Panel.fit(highlighted, title=f"Converted to {output_format}", style="green"))
else:
console.print(Panel.fit(output, title=f"Converted to {output_format}", style="green"))
except Exception as e:
console.print(f"[bold red]Error:[/bold red] {str(e)}")
raise click.Abort()
@cli.command("interactive")
@click.option("--format", "-f", "output_format", type=click.Choice(["curl", "httpie", "fetch", "axios"]), default="curl", help="Output format")
@click.pass_context
def interactive(ctx, output_format):
config = ctx.obj.get("config", Config())
console.print(Panel.fit(
"[bold cyan]HTTP Request Builder[/bold cyan]\n"
"Let's build your HTTP request step by step.",
title="Welcome"
))
method_map = {
"1": HttpMethod.GET,
"2": HttpMethod.POST,
"3": HttpMethod.PUT,
"4": HttpMethod.PATCH,
"5": HttpMethod.DELETE,
}
console.print("\n[bold]Select HTTP Method:[/bold]")
for key, value in method_map.items():
console.print(f" {key}. {value.value}")
method_choice = Prompt.ask("Choice", choices=list(method_map.keys()), default="1")
method = method_map[method_choice]
url = Prompt.ask("\n[bold]Enter URL[/bold] (e.g., https://api.example.com/users)")
add_headers = Confirm.ask("\n[bold]Add headers?[/bold]", default=False)
headers = {}
if add_headers:
console.print("\n[bold]Enter headers (press Enter on key to finish):[/bold]")
while True:
key = Prompt.ask(" Header name (or Enter to finish)", default="")
if not key:
break
value = Prompt.ask(f" Value for '{key}'")
headers[key] = value
add_params = Confirm.ask("\n[bold]Add query parameters?[/bold]", default=False)
params = {}
if add_params:
console.print("\n[bold]Enter query parameters (press Enter on key to finish):[/bold]")
while True:
key = Prompt.ask(" Parameter name (or Enter to finish)", default="")
if not key:
break
value = Prompt.ask(f" Value for '{key}'")
params[key] = value
add_body = Confirm.ask("\n[bold]Add request body?[/bold]", default=False)
body = None
body_json = None
if add_body:
body_type = Confirm.ask(" Is it JSON?", default=True)
if body_type:
import json
body_json_str = Prompt.ask(" Enter JSON string")
try:
body_json = json.loads(body_json_str)
except json.JSONDecodeError as e:
console.print(f"[yellow]Warning: Invalid JSON - {e}[/yellow]")
body = body_json_str
else:
body = Prompt.ask(" Enter body content")
request = HTTPRequest(
method=method,
url=url,
headers=headers,
params=params,
body=body,
body_json=body_json
)
console.print("\n[bold]Request Preview:[/bold]")
preview_table = Table()
preview_table.add_column("Property", style="cyan")
preview_table.add_column("Value", style="magenta")
preview_table.add_row("Method", request.method.value)
preview_table.add_row("URL", request.get_final_url())
for key, value in request.headers.items():
preview_table.add_row(f"Header: {key}", value)
for key, value in request.params.items():
preview_table.add_row(f"Param: {key}", value)
if request.body:
preview_table.add_row("Body", request.body)
if request.body_json:
preview_table.add_row("Body (JSON)", str(request.body_json))
console.print(preview_table)
output = Generator.generate(request, OutputFormat(output_format))
console.print(Panel.fit(
SyntaxHighlighter.highlight(output, output_format),
title=f"Generated {output_format}",
style="green"
))
if Confirm.ask("\n[bold]Convert to another format?[/bold]", default=False):
available_formats = [f.value for f in OutputFormat if f.value != output_format]
new_format = Prompt.ask("Choose format", choices=available_formats)
new_output = Generator.generate(request, OutputFormat(new_format))
console.print(Panel.fit(
SyntaxHighlighter.highlight(new_output, new_format),
title=f"Converted to {new_format}",
style="green"
))
@cli.command("formats")
def list_formats():
table = Table(title="Supported Formats")
table.add_column("Format", style="cyan")
table.add_column("Description", style="magenta")
table.add_column("Example Start", style="yellow")
formats_info = [
("curl", "cURL command line tool", "curl -X POST https://..."),
("httpie", "HTTPie command line tool", "https://..."),
("fetch", "JavaScript Fetch API", "fetch('https://...')"),
("axios", "Axios HTTP client", "axios({ url: '...' })"),
]
for fmt, desc, example in formats_info:
table.add_row(fmt, desc, f"``{example}``")
console.print(table)
@cli.command("config")
@click.option("--set-format", "set_format", type=click.Choice(["curl", "httpie", "fetch", "axios"]), help="Set default output format")
@click.option("--toggle-highlight/--no-toggle-highlight", "toggle_highlight", default=None, help="Toggle syntax highlighting")
@click.option("--toggle-compact/--no-toggle-compact", "toggle_compact", default=None, help="Toggle compact output")
@click.option("--show", is_flag=True, help="Show current configuration")
@click.option("--reset", is_flag=True, help="Reset to defaults")
@click.pass_context
def config_cmd(ctx, set_format, toggle_highlight, toggle_compact, show, reset):
config = ctx.obj.get("config", Config())
if reset:
config.reset()
console.print("[green]Configuration reset to defaults[/green]")
return
if set_format:
config.default_format = set_format
console.print(f"[green]Default format set to: {set_format}[/green]")
if toggle_highlight is not None:
config.syntax_highlighting = toggle_highlight
console.print(f"[green]Syntax highlighting: {'enabled' if toggle_highlight else 'disabled'}[/green]")
if toggle_compact is not None:
config.compact_output = toggle_compact
console.print(f"[green]Compact output: {'enabled' if toggle_compact else 'disabled'}[/green]")
if show or not any([set_format, toggle_highlight is not None, toggle_compact is not None]):
table = Table(title="Current Configuration")
table.add_column("Setting", style="cyan")
table.add_column("Value", style="magenta")
table.add_row("Default Format", config.default_format)
table.add_row("Syntax Highlighting", "Enabled" if config.syntax_highlighting else "Disabled")
table.add_row("Compact Output", "Enabled" if config.compact_output else "Disabled")
table.add_row("Theme", config.theme)
console.print(table)
@cli.command("web")
@click.option("--host", default="127.0.0.1", help="Host to bind to")
@click.option("--port", default=8000, type=int, help="Port to bind to")
@click.option("--open/--no-open", default=True, help="Open browser automatically")
def web(host, port, open):
from .web import app
import uvicorn
import webbrowser
import threading
url = f"http://{host}:{port}"
if open:
def open_browser():
webbrowser.open(url)
threading.Timer(1.0, open_browser).start()
console.print(Panel.fit(
f"[bold green]Web Interface[/bold green]\n"
f"Open your browser at: [cyan]{url}[/cyan]\n"
f"Press Ctrl+C to stop the server",
title="HTTP Convert"
))
uvicorn.run(app, host=host, port=port)
@cli.command("version")
def version():
from . import __version__
console.print(f"[bold]HTTP Convert[/bold] v{__version__}")
def main():
cli(obj={})
if __name__ == "__main__":
main()