This commit is contained in:
282
src/http_convert/cli.py
Normal file
282
src/http_convert/cli.py
Normal 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()
|
||||
Reference in New Issue
Block a user