Re-upload: CI infrastructure issue resolved, all tests verified passing
This commit is contained in:
7
http_log_explorer/exporters/__init__.py
Normal file
7
http_log_explorer/exporters/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
"""Exporters for various formats."""
|
||||
|
||||
from http_log_explorer.exporters.code_exporter import CodeExporter
|
||||
from http_log_explorer.exporters.curl_exporter import CurlExporter
|
||||
from http_log_explorer.exporters.json_exporter import JSONExporter
|
||||
|
||||
__all__ = ["CodeExporter", "CurlExporter", "JSONExporter"]
|
||||
263
http_log_explorer/exporters/code_exporter.py
Normal file
263
http_log_explorer/exporters/code_exporter.py
Normal file
@@ -0,0 +1,263 @@
|
||||
"""Code exporter for HTTP entries (Python, JavaScript, Go)."""
|
||||
|
||||
import json
|
||||
|
||||
from http_log_explorer.models import HTTPEntry
|
||||
|
||||
|
||||
class CodeExporter:
|
||||
"""Export HTTP entries as code snippets in various languages."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""Initialize code exporter."""
|
||||
self._template_dir = ""
|
||||
|
||||
PYTHON_TEMPLATE = '''import requests
|
||||
|
||||
headers = {headers}
|
||||
{data}
|
||||
response = requests.{method}(
|
||||
"{url}"{params}
|
||||
{headers_param})
|
||||
{body}
|
||||
print(response.status_code)
|
||||
print(response.json())
|
||||
'''
|
||||
|
||||
JAVASCRIPT_TEMPLATE = '''const axios = require('axios');
|
||||
|
||||
const config = {{
|
||||
method: '{method}',
|
||||
url: '{url}',
|
||||
{headers_js}
|
||||
{data_js}
|
||||
{body_js}
|
||||
}};
|
||||
|
||||
axios(config)
|
||||
.then(response => {{
|
||||
console.log(response.status);
|
||||
console.log(response.data);
|
||||
}})
|
||||
.catch(error => {{
|
||||
console.error(error);
|
||||
}});
|
||||
'''
|
||||
|
||||
GO_TEMPLATE = '''package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {{
|
||||
{headers_go}
|
||||
{data_go}
|
||||
body{data_var} := {body_val}
|
||||
{body_prep}
|
||||
req, err := http.NewRequest("{method}", "{url}", {body_ref})
|
||||
if err != nil {{
|
||||
panic(err)
|
||||
}}
|
||||
{set_headers}
|
||||
client := &http.Client{{}}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {{
|
||||
panic(err)
|
||||
}}
|
||||
defer resp.Body.Close()
|
||||
|
||||
fmt.Println("Status:", resp.Status)
|
||||
}}
|
||||
|
||||
'''
|
||||
|
||||
def export_python(self, entry: HTTPEntry) -> str:
|
||||
"""Export entry as Python code.
|
||||
|
||||
Args:
|
||||
entry: HTTPEntry object
|
||||
|
||||
Returns:
|
||||
Python code string
|
||||
"""
|
||||
headers_str = self._format_python_dict(entry.request.headers)
|
||||
|
||||
data_line = ""
|
||||
body_line = ""
|
||||
params_line = ""
|
||||
|
||||
if entry.request.query_params:
|
||||
params_line = f", params={self._format_python_dict(entry.request.query_params)}"
|
||||
|
||||
if entry.request.body:
|
||||
body_line = "data=data,"
|
||||
|
||||
if entry.request.headers:
|
||||
headers_param = "\n headers=headers"
|
||||
else:
|
||||
headers_param = ""
|
||||
|
||||
method_lower = entry.request.method.lower()
|
||||
|
||||
return self.PYTHON_TEMPLATE.format(
|
||||
method=method_lower,
|
||||
url=entry.request.url,
|
||||
headers=headers_str,
|
||||
params=params_line,
|
||||
headers_param=headers_param,
|
||||
data=data_line,
|
||||
body=body_line,
|
||||
)
|
||||
|
||||
def export_javascript(self, entry: HTTPEntry) -> str:
|
||||
"""Export entry as JavaScript code.
|
||||
|
||||
Args:
|
||||
entry: HTTPEntry object
|
||||
|
||||
Returns:
|
||||
JavaScript code string
|
||||
"""
|
||||
headers_lines = []
|
||||
for name, value in entry.request.headers.items():
|
||||
headers_lines.append(f' "{name}": "{value}",')
|
||||
|
||||
headers_js = "\n".join(headers_lines)
|
||||
if headers_js:
|
||||
headers_js = "headers: {\n" + headers_js + "\n},"
|
||||
|
||||
data_js = ""
|
||||
data_val = "{}"
|
||||
body_js = ""
|
||||
|
||||
if entry.request.body:
|
||||
data_val = json.dumps(entry.request.body)
|
||||
data_js = f"const data = {data_val};"
|
||||
body_js = "data: data,"
|
||||
|
||||
if entry.request.query_params:
|
||||
data_val = json.dumps(entry.request.query_params)
|
||||
data_js = f"const params = {data_val};"
|
||||
body_js = "params: params,"
|
||||
|
||||
return self.JAVASCRIPT_TEMPLATE.format(
|
||||
method=entry.request.method.lower(),
|
||||
url=entry.request.url,
|
||||
headers_js=headers_js,
|
||||
data_js=data_js,
|
||||
body_js=body_js,
|
||||
)
|
||||
|
||||
def export_go(self, entry: HTTPEntry) -> str:
|
||||
"""Export entry as Go code.
|
||||
|
||||
Args:
|
||||
entry: HTTPEntry object
|
||||
|
||||
Returns:
|
||||
Go code string
|
||||
"""
|
||||
headers_lines = []
|
||||
for name, value in entry.request.headers.items():
|
||||
headers_lines.append(f' req.Header.Set("{name}", "{value}")')
|
||||
|
||||
headers_go = "\n".join(headers_lines)
|
||||
|
||||
data_val = "nil"
|
||||
data_var = ""
|
||||
body_prep = ""
|
||||
body_ref = "nil"
|
||||
data_go = ""
|
||||
|
||||
if entry.request.body:
|
||||
escaped = self._escape_go_string(entry.request.body)
|
||||
data_val = f"`{escaped}`"
|
||||
body_prep = f' body := bytes.NewBufferString({data_val})'
|
||||
body_ref = "body"
|
||||
|
||||
set_headers = headers_go if headers_go else " // No headers"
|
||||
|
||||
return self.GO_TEMPLATE.format(
|
||||
method=entry.request.method,
|
||||
url=entry.request.url,
|
||||
headers_go=headers_go,
|
||||
data_go=data_go,
|
||||
data_var=data_var,
|
||||
body_val=data_val,
|
||||
body_prep=body_prep,
|
||||
body_ref=body_ref,
|
||||
set_headers=set_headers,
|
||||
)
|
||||
|
||||
def export_batch(
|
||||
self, entries: list[HTTPEntry], language: str
|
||||
) -> list[str]:
|
||||
"""Export multiple entries as code snippets.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
language: Target language (python, javascript, go)
|
||||
|
||||
Returns:
|
||||
List of code strings
|
||||
|
||||
Raises:
|
||||
ValueError: If language is not supported
|
||||
"""
|
||||
language = language.lower()
|
||||
if language == "python":
|
||||
return [self.export_python(e) for e in entries]
|
||||
elif language == "javascript":
|
||||
return [self.export_javascript(e) for e in entries]
|
||||
elif language == "go":
|
||||
return [self.export_go(e) for e in entries]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Unsupported language: {language}. "
|
||||
f"Supported: python, javascript, go"
|
||||
)
|
||||
|
||||
def _format_python_dict(self, d: dict[str, str]) -> str:
|
||||
"""Format dictionary as Python code.
|
||||
|
||||
Args:
|
||||
d: Dictionary to format
|
||||
|
||||
Returns:
|
||||
Python dict string
|
||||
"""
|
||||
if not d:
|
||||
return "{}"
|
||||
items = [f'"{k}": "{v}"' for k, v in d.items()]
|
||||
return "{\n " + ",\n ".join(items) + "\n}"
|
||||
|
||||
def _escape_go_string(self, s: str) -> str:
|
||||
"""Escape string for Go.
|
||||
|
||||
Args:
|
||||
s: String to escape
|
||||
|
||||
Returns:
|
||||
Escaped string
|
||||
"""
|
||||
return s.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$")
|
||||
|
||||
def to_file(
|
||||
self, entries: list[HTTPEntry], path: str, language: str
|
||||
) -> None:
|
||||
"""Write code snippets to file.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
path: Output file path
|
||||
language: Target language
|
||||
"""
|
||||
snippets = self.export_batch(entries, language)
|
||||
with open(path, "w") as f:
|
||||
for snippet in snippets:
|
||||
f.write(snippet)
|
||||
f.write("\n\n")
|
||||
70
http_log_explorer/exporters/curl_exporter.py
Normal file
70
http_log_explorer/exporters/curl_exporter.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""cURL exporter for HTTP entries."""
|
||||
|
||||
|
||||
from http_log_explorer.models import HTTPEntry
|
||||
|
||||
|
||||
class CurlExporter:
|
||||
"""Export HTTP entries as cURL commands."""
|
||||
|
||||
def export(self, entry: HTTPEntry) -> str:
|
||||
"""Export a single entry as cURL command.
|
||||
|
||||
Args:
|
||||
entry: HTTPEntry object
|
||||
|
||||
Returns:
|
||||
cURL command string
|
||||
"""
|
||||
parts = ["curl"]
|
||||
|
||||
parts.append("-X")
|
||||
parts.append(entry.request.method)
|
||||
|
||||
if entry.request.headers:
|
||||
for name, value in entry.request.headers.items():
|
||||
if name.lower() not in ("host", "content-length"):
|
||||
parts.append("-H")
|
||||
parts.append(f"{name}: {value}")
|
||||
|
||||
if entry.request.body:
|
||||
escaped_body = self._escape_body(entry.request.body)
|
||||
parts.append("-d")
|
||||
parts.append(f"'{escaped_body}'")
|
||||
|
||||
parts.append(f"'{entry.request.url}'")
|
||||
|
||||
return " ".join(parts)
|
||||
|
||||
def export_batch(self, entries: list[HTTPEntry]) -> list[str]:
|
||||
"""Export multiple entries as cURL commands.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
|
||||
Returns:
|
||||
List of cURL command strings
|
||||
"""
|
||||
return [self.export(entry) for entry in entries]
|
||||
|
||||
def _escape_body(self, body: str) -> str:
|
||||
"""Escape body string for shell.
|
||||
|
||||
Args:
|
||||
body: Body content
|
||||
|
||||
Returns:
|
||||
Escaped string
|
||||
"""
|
||||
return body.replace("'", "'\\''")
|
||||
|
||||
def to_file(self, entries: list[HTTPEntry], path: str) -> None:
|
||||
"""Write cURL commands to file (one per line).
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
path: Output file path
|
||||
"""
|
||||
with open(path, "w") as f:
|
||||
for entry in entries:
|
||||
f.write(self.export(entry) + "\n")
|
||||
66
http_log_explorer/exporters/json_exporter.py
Normal file
66
http_log_explorer/exporters/json_exporter.py
Normal file
@@ -0,0 +1,66 @@
|
||||
"""JSON exporter for HTTP entries."""
|
||||
|
||||
import json
|
||||
|
||||
from http_log_explorer.models import HTTPEntry
|
||||
|
||||
|
||||
class JSONExporter:
|
||||
"""Export HTTP entries to JSON format."""
|
||||
|
||||
def export(self, entries: list[HTTPEntry], indent: int = 2) -> str:
|
||||
"""Export entries to JSON string.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
indent: JSON indent level
|
||||
|
||||
Returns:
|
||||
JSON string representation
|
||||
"""
|
||||
data = [entry.to_dict() for entry in entries]
|
||||
return json.dumps(data, indent=indent, default=str)
|
||||
|
||||
def export_compact(self, entries: list[HTTPEntry]) -> str:
|
||||
"""Export entries to compact JSON (no indent).
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
|
||||
Returns:
|
||||
Compact JSON string
|
||||
"""
|
||||
data = [entry.to_dict() for entry in entries]
|
||||
return json.dumps(data, separators=(",", ":"), default=str)
|
||||
|
||||
def save(self, entries: list[HTTPEntry], path: str, indent: int = 2) -> None:
|
||||
"""Save entries to JSON file.
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
path: Output file path
|
||||
indent: JSON indent level
|
||||
"""
|
||||
with open(path, "w") as f:
|
||||
f.write(self.export(entries, indent))
|
||||
|
||||
def export_summary(self, entries: list[HTTPEntry]) -> str:
|
||||
"""Export summary of entries (URL, method, status only).
|
||||
|
||||
Args:
|
||||
entries: List of HTTPEntry objects
|
||||
|
||||
Returns:
|
||||
JSON string with summary info
|
||||
"""
|
||||
summary = []
|
||||
for entry in entries:
|
||||
summary.append({
|
||||
"id": entry.id,
|
||||
"method": entry.request.method,
|
||||
"url": entry.request.url,
|
||||
"status": entry.response.status,
|
||||
"content_type": entry.content_type,
|
||||
"duration_ms": entry.duration_ms,
|
||||
})
|
||||
return json.dumps(summary, indent=2)
|
||||
Reference in New Issue
Block a user