Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: grepable output #72

Merged
merged 3 commits into from
Nov 19, 2024
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
3 changes: 3 additions & 0 deletions internal/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type Options struct {
// Output flags.
// Output in JSON format.
JSONOutput bool
// Output in Grepable format.
GrepFormat bool
// Disable color output.
NoColor bool
// Enable debugging.
Expand All @@ -47,6 +49,7 @@ func (opts *Options) Parse() {
)
flag.StringVar(&opts.DNSResolver, "dr", "", "DNS resolution server")
flag.BoolVar(&opts.JSONOutput, "j", false, "Output in JSON format")
flag.BoolVar(&opts.GrepFormat, "g", false, "Output in Grepable format")
flag.BoolVar(&opts.NoColor, "nc", false, "Disable color output")
flag.BoolVar(&opts.Debug, "dbg", false, "Verbose output")
flag.BoolVar(&opts.Help, "h", false, "Show app documentation")
Expand Down
80 changes: 79 additions & 1 deletion internal/report.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package internal

import (
"encoding/json"
"fmt"
"time"

"github.com/fatih/color"
)

// Report is the result of a connection attempt.
//
// Only one of the properties 'Response' or 'Error' is set.

type Format int

const (
JSONFormat Format = iota
HumanFormat
GrepFormat
)

type Report struct {
// Protocol used to connect to.
ProtocolID string `json:"protocol"`
Expand All @@ -17,5 +30,70 @@ type Report struct {
// Network error.
Error error `json:"error,omitempty"`
// Extra information. Depends on the protocol.
Extra string `json:"extra,omitempty"`
Extra string `json:"extra,omitempty"`
Format Format
}

func (r *Report) NewLine(f Format) (string, error) {
switch f {
case HumanFormat:
return r.newLineHuman(), nil
case JSONFormat:
line, err := r.newLineJSON()
if err != nil {
return "", fmt.Errorf("error generating JSON report: %w", err)
}
return line, nil
case GrepFormat:
return r.newLineGrep(), nil
default:
return "", fmt.Errorf("unsupported format: %v", f)
}
}

func (r *Report) newLineJSON() (string, error) {

reportJSON, err := json.Marshal(r)
if err != nil {
return "", fmt.Errorf("marshaling report: %w", err)

}
return string(reportJSON), nil
}

var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)

func (r *Report) newLineHuman() string {
line := fmt.Sprintf("%-15s %-14s %s", bold(r.ProtocolID), r.Time, r.RHost)
suffix := r.Extra
prefix := green("✔")
if r.Error != nil {
prefix = red("✘")
suffix = r.Error.Error()
}
suffix = fmt.Sprintf("(%s)", suffix)

return fmt.Sprintf("%s %s %s", prefix, line, faint(suffix))
}

// Output: HTTP/1.1 2024-11-18T15:00:00Z 192.168.1.1 success Request
// processed successfully
func (r *Report) newLineGrep() string {
status := "success"
if r.Error != nil {
status = "failure"
}
line := fmt.Sprintf("%s\t%s\t%s\t%s\t%s",
r.ProtocolID,
r.Time,
r.RHost,
status,
r.Extra,
)
return line
}
50 changes: 17 additions & 33 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main

import (
"context"
"encoding/json"
"flag"
"fmt"
"log/slog"
Expand Down Expand Up @@ -33,8 +32,6 @@ const (
`
)

// TODO(#39): STDIN piped input.

func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down Expand Up @@ -106,17 +103,24 @@ func main() {
logger.Debug("Listening for reports ...")
for report := range probe.ReportCh {
logger.Debug("New report", "report", *report)
var line string
if opts.JSONOutput {
reportJSON, err := json.Marshal(report)
if err != nil {
fatal(fmt.Errorf("marshaling report: %w", err))
}
line = string(reportJSON)
} else {
line = reportToLine(report)

var format internal.Format
switch {
case opts.JSONOutput:
format = internal.JSONFormat
case opts.GrepFormat:
format = internal.GrepFormat
default:
format = internal.HumanFormat
}
fmt.Println(line)
repLine, err := report.NewLine(format)

if err != nil {
fatal(err)
}

fmt.Println(repLine)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, remember to remove all blank lines inside functions.

if report.Error == nil {
if opts.Stop {
logger.Debug("Stopping after first successful request")
Expand All @@ -137,23 +141,3 @@ func fatal(err error) {
fmt.Fprintf(os.Stderr, "%s: %s\n", appName, err)
os.Exit(1)
}

// Returns a human-readable representation of the report.
func reportToLine(r *internal.Report) string {
line := fmt.Sprintf("%-15s %-14s %s", bold(r.ProtocolID), r.Time, r.RHost)
suffix := r.Extra
prefix := green("✔")
if r.Error != nil {
prefix = red("✘")
suffix = r.Error.Error()
}
suffix = fmt.Sprintf("(%s)", suffix)
return fmt.Sprintf("%s %s %s", prefix, line, faint(suffix))
}

var (
green = color.New(color.FgGreen).SprintFunc()
red = color.New(color.FgRed).SprintFunc()
bold = color.New(color.Bold).SprintFunc()
faint = color.New(color.Faint).SprintFunc()
)