diff --git a/README.md b/README.md
index 0ea9215..da73a59 100755
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-
up
+# up
+
@@ -39,7 +40,7 @@ Check [the examples](examples) to see how to use this project in your own code.
## License
-This project is under the MIT License. See the [LICENSE](LICENSE) file for the full license text.
+This project is under the MIT License. See the [LICENSE](LICENSE) file for the full text.
[doc-img]: https://pkg.go.dev/badge/github.com/jesusprubio/up
[doc]: https://pkg.go.dev/github.com/jesusprubio/up
diff --git a/Taskfile.yml b/Taskfile.yml
index 1071833..6bde099 100755
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -10,7 +10,7 @@ tasks:
build:
summary: "Build application"
- cmd: go build -o dist/up cmd/main.go
+ cmd: go build -o dist/up .
clean:
summary: "Clean the project"
diff --git a/examples/doc.go b/examples/doc.go
deleted file mode 100644
index a12e7bd..0000000
--- a/examples/doc.go
+++ /dev/null
@@ -1,2 +0,0 @@
-// Package examples provides examples of how to use this project as a library.
-package examples
diff --git a/examples/probe.go b/examples/probe.go
deleted file mode 100644
index a4e427f..0000000
--- a/examples/probe.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package examples
-
-// TODO(#28)
diff --git a/main.go b/main.go
index 84a39b1..6264254 100644
--- a/main.go
+++ b/main.go
@@ -1,3 +1,4 @@
+// Package main implements a simple CLI to use the library.
package main
import (
@@ -15,6 +16,8 @@ import (
"github.com/jesusprubio/up/pkg"
)
+// TODO(#39): STDIN piped input.
+
const (
appName = "up"
appDesc = `
@@ -29,72 +32,41 @@ const (
This utility exits with one of the following values:
0 At least one response was heard.
2 The transmission was successful but no responses were received.
- 1 Any other value An error occurred.
+ 1 Any other error occurred.
`
)
-// CLI initialization.
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
- // Input options.
- flagProto := flag.String(
- "p",
- "",
- fmt.Sprintf("Use only one protocol: %v", pkg.Protocols),
- )
- flagCount := flag.Uint("c", 0, "Number of iterations. (0 = infinite)")
- flagTimeout := flag.Duration("t",
- 5*time.Second,
- "Time to wait for a response",
- )
- flagDelay := flag.Duration(
- "d",
- 500*time.Millisecond,
- "Delay between requests",
- )
- flagStop := flag.Bool("s", false, "Stop after the first successful request")
- // Output options.
- flagJSONOutput := flag.Bool("j", false, "Output in JSON format")
- flagNoColor := flag.Bool("nc", false, "Disable color output")
- flagVerbose := flag.Bool("v", false, "Verbose output")
- flagHelp := flag.Bool("h", false, "Show app documentation")
- flag.Parse()
+ // Only used for debugging.
lvl := new(slog.LevelVar)
lvl.Set(slog.LevelError)
- // Only used for debugging.
logger := slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
Level: lvl,
}))
- if *flagVerbose {
+ var opts options
+ opts.parse()
+ if opts.verbose {
+ // TODO(#37): Debug and verbose should not be the same thing.
lvl.Set(slog.LevelDebug)
}
- logger.Debug("Running",
- "protocol", *flagProto,
- "count", *flagCount,
- "timeout", *flagTimeout,
- "delay", *flagDelay,
- "stop", *flagStop,
- "json", *flagJSONOutput,
- "no-color", *flagNoColor,
- "verbose", *flagVerbose,
- "help", *flagHelp,
- )
+ logger.Debug("Starting", "options", opts)
protocols := pkg.Protocols
- if *flagProto != "" {
- protocol := pkg.ProtocolByID(*flagProto)
+ if opts.protocol != "" {
+ protocol := pkg.ProtocolByID(opts.protocol)
if protocol == nil {
- fatal(fmt.Errorf("unknown protocol: %s", *flagProto))
+ fatal(fmt.Errorf("unknown protocol: %s", opts.protocol))
}
protocols = []*pkg.Protocol{protocol}
}
- logger.Info("Running", "protocols", protocols, "count", *flagCount)
- if *flagHelp {
+ logger.Info("Running", "protocols", protocols, "count", opts.count)
+ if opts.help {
fmt.Fprintf(os.Stderr, "%s\n", appDesc)
flag.Usage()
os.Exit(1)
}
- if *flagNoColor {
+ if opts.noColor {
color.NoColor = true
}
// To wait for termination signals.
@@ -111,9 +83,9 @@ func main() {
}()
probe := pkg.Probe{
Protocols: protocols,
- Count: *flagCount,
- Delay: *flagDelay,
- Timeout: *flagTimeout,
+ Count: opts.count,
+ Timeout: opts.timeout,
+ Delay: opts.delay,
Logger: logger,
ReportCh: make(chan *pkg.Report),
}
@@ -122,19 +94,19 @@ func main() {
for report := range probe.ReportCh {
logger.Debug("New report", "report", *report)
var line string
- if *flagJSONOutput {
+ if opts.jsonOutput {
reportJSON, err := json.Marshal(report)
if err != nil {
fatal(fmt.Errorf("marshaling report: %w", err))
}
line = string(reportJSON)
} else {
- line = report.String()
+ line = reportToLine(report)
}
fmt.Println(line)
if report.Error == nil {
- if *flagStop {
- logger.Debug("Stop requested")
+ if opts.stop {
+ logger.Debug("Stopping after first successful request")
cancel()
}
}
@@ -143,12 +115,87 @@ func main() {
logger.Debug("Running", "setup", probe)
err := probe.Run(ctx)
if err != nil {
- fatal(err)
+ fatal(fmt.Errorf("running probe: %w", err))
}
logger.Debug("Bye!")
}
+// Flags passed by the user.
+type options struct {
+ // Input flags.
+ // Protocol to use.
+ protocol string
+ // Number of iterations. Zero means infinite.
+ count uint
+ // Time to wait for a response.
+ timeout time.Duration
+ // Delay between requests.
+ delay time.Duration
+ // Stop after the first successful request.
+ stop bool
+ // Output flags.
+ // Output in JSON format.
+ jsonOutput bool
+ // Disable color output.
+ noColor bool
+ // Verbose output.
+ verbose bool
+ // Show app documentation.
+ help bool
+}
+
+// Parses the command line flags provided by the user.
+func (opts *options) parse() {
+ flag.StringVar(&opts.protocol, "p", "", "Test only one protocol")
+ flag.UintVar(&opts.count, "c", 0, "Number of iterations")
+ flag.DurationVar(
+ &opts.timeout,
+ "t",
+ 5*time.Second,
+ "Time to wait for a response",
+ )
+ flag.DurationVar(
+ &opts.delay,
+ "d",
+ 500*time.Millisecond,
+ "Delay between requests",
+ )
+ flag.BoolVar(
+ &opts.stop,
+ "s",
+ false,
+ "Stop after the first successful request",
+ )
+ flag.BoolVar(&opts.jsonOutput, "j", false, "Output in JSON format")
+ flag.BoolVar(&opts.noColor, "nc", false, "Disable color output")
+ flag.BoolVar(&opts.verbose, "v", false, "Verbose output")
+ flag.BoolVar(&opts.help, "h", false, "Show app documentation")
+ flag.Parse()
+}
+
+// Logs the error to the standard output and exits with status 1.
func fatal(err error) {
fmt.Fprintf(os.Stderr, "%s: %s\n", appName, err)
os.Exit(1)
}
+
+// String returns a human-readable representation of the report.
+func reportToLine(r *pkg.Report) string {
+ // TODO(#40): Use Go string padding.
+ line := fmt.Sprintf("%s\t%s\t%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()
+)
diff --git a/pkg/probe.go b/pkg/probe.go
index 3b0eac2..1867fb0 100644
--- a/pkg/probe.go
+++ b/pkg/probe.go
@@ -16,13 +16,13 @@ type Probe struct {
Protocols []*Protocol
// Number of iterations. Zero means infinite.
Count uint
- // Delay between requests.
- Delay time.Duration
// Time to wait for a response.
Timeout time.Duration
+ // Delay between requests.
+ Delay time.Duration
// For debugging purposes.
Logger *slog.Logger
- // Optional channel to send back partial results.
+ // Channel to send back partial results.
ReportCh chan *Report
}
@@ -34,6 +34,7 @@ func (p Probe) validate() error {
if p.Timeout == 0 {
return fmt.Errorf(tmplRequiredProp, "Timeout")
}
+ // 'Delay' could be zero.
if p.Logger == nil {
return fmt.Errorf(tmplRequiredProp, "Logger")
}
@@ -43,17 +44,17 @@ func (p Probe) validate() error {
return nil
}
-// Run the connection attempts to the public servers.
+// Run the connection requests against the public servers.
//
// The context can be cancelled between different protocol attempts or count
// iterations.
// Returns an error if the setup is invalid.
func (p Probe) Run(ctx context.Context) error {
- p.Logger.Debug("Starting", "setup", p)
err := p.validate()
if err != nil {
return fmt.Errorf("invalid setup: %w", err)
}
+ p.Logger.Debug("Starting", "setup", p)
count := uint(0)
for {
select {
@@ -99,11 +100,11 @@ func (p Probe) Run(ctx context.Context) error {
time.Sleep(p.Delay)
}
}
- count++
p.Logger.Debug(
"End of iteration",
"count", count, "p.Count", p.Count,
)
+ count++
if count == p.Count {
p.Logger.Debug("Count limit reached", "count", count)
return nil
diff --git a/pkg/protocol.go b/pkg/protocols.go
similarity index 100%
rename from pkg/protocol.go
rename to pkg/protocols.go
index a194d40..94951b7 100644
--- a/pkg/protocol.go
+++ b/pkg/protocols.go
@@ -9,9 +9,9 @@ import (
// Protocols included in the library.
var Protocols = []*Protocol{
- {ID: "http", Request: requestHTTP, RHost: RandomCaptivePortal},
{ID: "tcp", Request: requestTCP, RHost: RandomTCPServer},
{ID: "dns", Request: requestDNS, RHost: RandomDomain},
+ {ID: "http", Request: requestHTTP, RHost: RandomCaptivePortal},
}
// ProtocolByID returns a protocol from the list.
diff --git a/pkg/report.go b/pkg/report.go
index ba45f51..44747e6 100644
--- a/pkg/report.go
+++ b/pkg/report.go
@@ -1,23 +1,12 @@
package pkg
import (
- "fmt"
"time"
-
- "github.com/fatih/color"
-)
-
-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()
)
// Report is the result of a connection attempt.
//
-// Depending on the result, only one of the properties 'Response' or 'Error'
-// is set.
+// Only one of the properties 'Response' or 'Error' is set.
type Report struct {
// Protocol used to connect to.
ProtocolID string `json:"protocol"`
@@ -33,16 +22,3 @@ type Report struct {
// Network error.
Error error `json:"error,omitempty"`
}
-
-// String returns a human-readable representation of the report.
-func (r *Report) String() string {
- line := fmt.Sprintf("%s\t%s\t%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))
-}