From b4236d73ba970fbe5c6ab05f6e557f2daedbca3f Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Thu, 29 Jan 2026 11:34:21 +0000 Subject: [PATCH] Initial commit: Add http-convert project --- src/http_convert/cli.py | 282 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 282 insertions(+) create mode 100644 src/http_convert/cli.py diff --git a/src/http_convert/cli.py b/src/http_convert/cli.py new file mode 100644 index 0000000..e259e54 --- /dev/null +++ b/src/http_convert/cli.py @@ -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()