Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ on:
- main
workflow_dispatch:


permissions:
contents: read
pages: write
Expand Down
6 changes: 3 additions & 3 deletions internal/client/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ func (e *Executor) Execute(req *parser.Request) *ExecutionResult {
}
}

for key, value := range req.Headers {
substitutedValue := parser.SubstituteVariables(value, e.variables)
httpReq.Header.Set(key, substitutedValue)
for _, h := range req.Headers {
substitutedValue := parser.SubstituteVariables(h.Value, e.variables)
httpReq.Header.Add(h.Key, substitutedValue)
}

httpResp, err := e.client.Do(httpReq)
Expand Down
8 changes: 3 additions & 5 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ func Parse(r io.Reader) (*ParsedFile, error) {
LineStart: lineNum,
Method: httpMatches[1],
URL: strings.TrimSpace(httpMatches[2]),
Headers: make(map[string]string),
Description: lastComment,
}
lastComment = ""
Expand All @@ -118,7 +117,6 @@ func Parse(r io.Reader) (*ParsedFile, error) {
LineStart: lineNum,
Method: "GET",
URL: trimmedLine,
Headers: make(map[string]string),
Description: lastComment,
}
lastComment = ""
Expand All @@ -131,7 +129,7 @@ func Parse(r io.Reader) (*ParsedFile, error) {
if headerMatches := headerRegex.FindStringSubmatch(trimmedLine); headerMatches != nil {
headerName := headerMatches[1]
headerValue := strings.TrimSpace(headerMatches[2])
currentRequest.Headers[headerName] = headerValue
currentRequest.Headers = append(currentRequest.Headers, Header{Key: headerName, Value: headerValue})
continue
}
}
Expand Down Expand Up @@ -227,8 +225,8 @@ func ExtractUsedVariables(req *Request, allVariables map[string]string) map[stri
}

checkText(req.URL)
for _, value := range req.Headers {
checkText(value)
for _, h := range req.Headers {
checkText(h.Value)
}
checkText(req.Body)

Expand Down
7 changes: 6 additions & 1 deletion internal/parser/types.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package parser

type Header struct {
Key string
Value string
}

type Request struct {
ID string
LineStart int
LineEnd int
Method string
URL string
Headers map[string]string
Headers []Header
Body string
Description string
}
Expand Down
277 changes: 0 additions & 277 deletions internal/ui/components.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@ package ui

import (
"fmt"
"regexp"
"strings"

"httpyum/internal/client"
"httpyum/internal/parser"
)

Expand Down Expand Up @@ -42,274 +40,6 @@ func RenderRequestListItem(req parser.Request, isSelected bool, index int) strin
return sb.String()
}

func RenderResponseBox(result *client.ExecutionResult, showHeaders bool, width int) string {
return RenderResponseBoxWithVariables(result, showHeaders, nil, false, width)
}

func RenderResponseStaticSection(result *client.ExecutionResult, showHeaders bool, variables map[string]string, showVariables bool, width int) string {
var sections []string

if showVariables && variables != nil {
reqSection := renderRequestSectionWithVariables(result, variables, width)
sections = append(sections, reqSection)
} else {
reqSection := renderRequestSection(result)
sections = append(sections, reqSection)
}

statusSection := renderStatusSection(result)
sections = append(sections, statusSection)

if showHeaders && result.Response != nil && len(result.Response.Headers) > 0 {
headerSection := renderHeadersSection(result)
sections = append(sections, headerSection)
}

paddedSections := make([]string, len(sections))
for i, section := range sections {
lines := strings.Split(section, "\n")
for j, line := range lines {
lines[j] = " " + line
}
paddedSections[i] = strings.Join(lines, "\n")
}

repeatCount := max(width-4, 0)
boxWidth := max(width-4, 1)
separator := "\n" + mutedStyle.Render(strings.Repeat("─", repeatCount)) + "\n"
content := strings.Join(paddedSections, separator)

return boxStyle.Width(boxWidth).Render(content)
}

func RenderResponseBodyContent(result *client.ExecutionResult) string {
bodySection := renderBodySection(result)

lines := strings.Split(bodySection, "\n")
for j, line := range lines {
lines[j] = " " + line
}

return strings.Join(lines, "\n")
}

func RenderResponseBoxWithVariables(result *client.ExecutionResult, showHeaders bool, variables map[string]string, showVariables bool, width int) string {
var sections []string

if showVariables && variables != nil {
reqSection := renderRequestSectionWithVariables(result, variables, width)
sections = append(sections, reqSection)
} else {
reqSection := renderRequestSection(result)
sections = append(sections, reqSection)
}

statusSection := renderStatusSection(result)
sections = append(sections, statusSection)

if showHeaders && result.Response != nil && len(result.Response.Headers) > 0 {
headerSection := renderHeadersSection(result)
sections = append(sections, headerSection)
}

bodySection := renderBodySection(result)
sections = append(sections, bodySection)

paddedSections := make([]string, len(sections))
for i, section := range sections {
lines := strings.Split(section, "\n")
for j, line := range lines {
lines[j] = " " + line
}
paddedSections[i] = strings.Join(lines, "\n")
}

repeatCount := max(width-4, 0)
boxWidth := max(width-4, 1)
separator := "\n" + mutedStyle.Render(strings.Repeat("─", repeatCount)) + "\n"
content := strings.Join(paddedSections, separator)

return boxStyle.Width(boxWidth).Render(content)
}

func renderRequestSection(result *client.ExecutionResult) string {
var sb strings.Builder

sb.WriteString(infoStyle.Bold(true).Render("Request"))
sb.WriteString("\n")
sb.WriteString(successStyle.Render(fmt.Sprintf("%s %s", result.Request.Method, result.Request.URL)))

if len(result.Request.Headers) > 0 {
sb.WriteString("\n")
for key, value := range result.Request.Headers {
if len(value) > 80 {
value = value[:77] + "..."
}
sb.WriteString(fmt.Sprintf("%s: %s\n", headerKeyStyle.Render(key), headerValueStyle.Render(value)))
}
}

return sb.String()
}

func renderRequestSectionWithVariables(result *client.ExecutionResult, allVariables map[string]string, totalWidth int) string {
contentWidth := totalWidth - 4
leftWidth := int(float64(contentWidth) * 0.65)
rightWidth := contentWidth - leftWidth - 3

var leftSb strings.Builder
leftSb.WriteString(infoStyle.Bold(true).Render("Request"))
leftSb.WriteString("\n")
leftSb.WriteString(successStyle.Render(fmt.Sprintf("%s %s", result.Request.Method, result.Request.URL)))

if len(result.Request.Headers) > 0 {
leftSb.WriteString("\n")
for key, value := range result.Request.Headers {
maxValueLen := leftWidth - len(key) - 4
if maxValueLen > 0 && len(value) > maxValueLen {
value = value[:maxValueLen-3] + "..."
}
leftSb.WriteString(fmt.Sprintf("%s: %s\n", headerKeyStyle.Render(key), headerValueStyle.Render(value)))
}
}

var rightSb strings.Builder
rightSb.WriteString(infoStyle.Bold(true).Render("Variables Used"))
rightSb.WriteString("\n")

usedVars := parser.ExtractUsedVariables(result.Request, allVariables)

if len(usedVars) == 0 {
rightSb.WriteString(mutedStyle.Render("(none)"))
} else {
for key, value := range usedVars {
maskedValue := maskValue(value)
displayKey := key
if strings.HasPrefix(key, "$dotenv_") {
displayKey = "$" + strings.TrimPrefix(key, "$dotenv_")
}
line := fmt.Sprintf("%s = %s", displayKey, maskedValue)
if len(line) > rightWidth {
line = line[:rightWidth-3] + "..."
}
rightSb.WriteString(fmt.Sprintf("%s\n", mutedStyle.Render(line)))
}
}

leftLines := strings.Split(strings.TrimSuffix(leftSb.String(), "\n"), "\n")
rightLines := strings.Split(strings.TrimSuffix(rightSb.String(), "\n"), "\n")

maxLines := len(leftLines)
if len(rightLines) > maxLines {
maxLines = len(rightLines)
}

var output strings.Builder
divider := mutedStyle.Render("│")

for i := 0; i < maxLines; i++ {
leftLine := ""
if i < len(leftLines) {
leftLine = leftLines[i]
}

visualLen := visualLength(leftLine)
output.WriteString(leftLine)

if visualLen < leftWidth {
output.WriteString(strings.Repeat(" ", leftWidth-visualLen))
}

output.WriteString(" " + divider + " ")

if i < len(rightLines) {
output.WriteString(rightLines[i])
}

output.WriteString("\n")
}

return strings.TrimSuffix(output.String(), "\n")
}

func visualLength(s string) int {
ansiRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`)
cleaned := ansiRegex.ReplaceAllString(s, "")
return len(cleaned)
}

func renderStatusSection(result *client.ExecutionResult) string {
var sb strings.Builder

sb.WriteString(infoStyle.Bold(true).Render("Response"))
sb.WriteString("\n")

if result.Error != nil {
sb.WriteString(errorStyle.Render(fmt.Sprintf("Error: %v", result.Error)))
if result.Response != nil {
sb.WriteString(fmt.Sprintf(" | %s", mutedStyle.Render(result.Response.Duration.String())))
}
} else if result.Response != nil {
statusStyle := StatusCodeStyle(result.Response.StatusCode, result.Response.Status)
sb.WriteString(statusStyle.Render(result.Response.Status))
sb.WriteString(fmt.Sprintf(" | %s", mutedStyle.Render(result.Response.Duration.String())))
sb.WriteString(fmt.Sprintf(" | %s", mutedStyle.Render(client.FormatSize(result.Response.Size))))
}

return sb.String()
}

func renderHeadersSection(result *client.ExecutionResult) string {
var sb strings.Builder

sb.WriteString(infoStyle.Bold(true).Render("Response Headers"))
sb.WriteString(mutedStyle.Render(" (press 'h' to hide)"))
sb.WriteString("\n")

for key, values := range result.Response.Headers {
value := strings.Join(values, ", ")
if len(value) > 80 {
value = value[:77] + "..."
}
sb.WriteString(fmt.Sprintf("%s: %s\n", headerKeyStyle.Render(key), headerValueStyle.Render(value)))
}

return strings.TrimSuffix(sb.String(), "\n")
}

func renderBodySection(result *client.ExecutionResult) string {
var sb strings.Builder

sb.WriteString(infoStyle.Bold(true).Render("Response Body"))

if result.Response != nil && client.IsJSON(result.Response.ContentType) {
sb.WriteString(mutedStyle.Render(" (press 'f' to explore interactively)"))
}
sb.WriteString("\n")

if result.Response == nil || len(result.Response.Body) == 0 {
sb.WriteString(mutedStyle.Render("(empty)"))
} else {
body := string(result.Response.Body)

if client.IsJSON(result.Response.ContentType) {
prettyJSON, err := client.PrettyPrintJSON(result.Response.Body)
if err == nil {
body = prettyJSON
}
}

maxBodyLength := 2000
if len(body) > maxBodyLength {
body = body[:maxBodyLength] + "\n" + mutedStyle.Render("... (truncated)")
}

sb.WriteString(body)
}

return sb.String()
}

func RenderHelpBar(currentView ViewType) string {
var shortcuts []string

Expand Down Expand Up @@ -349,10 +79,3 @@ func RenderSpinner(frame int) string {
spinner := spinners[frame%len(spinners)]
return infoStyle.Render(fmt.Sprintf("%s Loading...", spinner))
}

func maskValue(value string) string {
if len(value) <= 3 {
return "***"
}
return "..." + value[len(value)-3:]
}
Loading