Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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