feat: add option to read url from stdin (#66)

This commit is contained in:
Robert Kaussow 2023-07-19 20:39:30 +02:00 committed by GitHub
parent db299322bc
commit 0af993f5b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 91 additions and 39 deletions

View File

@ -53,6 +53,7 @@ COMMANDS:
GLOBAL OPTIONS: GLOBAL OPTIONS:
--url value source url to parse [$URL_PARSER_URL] --url value source url to parse [$URL_PARSER_URL]
--stdin read url to parse from stdin (default: false) [$URL_PARSER_STDIN]
--help, -h show help --help, -h show help
--version, -v print the version --version, -v print the version
``` ```

View File

@ -2,7 +2,9 @@ package main
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/thegeeklab/url-parser/command" "github.com/thegeeklab/url-parser/command"
@ -21,19 +23,25 @@ func main() {
fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate) fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate)
} }
config := &config.Config{} cfg := &config.Config{}
app := &cli.App{ app := &cli.App{
Name: "url-parser", Name: "url-parser",
Usage: "Parse URL and shows the part of it.", Usage: "Parse URL and shows the part of it.",
Version: BuildVersion, Version: BuildVersion,
Action: command.Run(config), Action: command.Run(cfg),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "url", Name: "url",
Usage: "source url to parse", Usage: "source url to parse",
EnvVars: []string{"URL_PARSER_URL"}, EnvVars: []string{"URL_PARSER_URL"},
Destination: &config.URL, Destination: &cfg.URL,
},
&cli.BoolFlag{
Name: "stdin",
Usage: "read url to parse from stdin",
EnvVars: []string{"URL_PARSER_STDIN"},
Destination: &cfg.Stdin,
}, },
}, },
Commands: []*cli.Command{ Commands: []*cli.Command{
@ -41,59 +49,89 @@ func main() {
Name: "all", Name: "all",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Get all parts from url", Usage: "Get all parts from url",
Action: command.Run(config), Action: command.Run(cfg),
}, },
{ {
Name: "scheme", Name: "scheme",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Get scheme from url", Usage: "Get scheme from url",
Action: command.Scheme(config), Action: command.Scheme(cfg),
}, },
{ {
Name: "user", Name: "user",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Get username from url", Usage: "Get username from url",
Action: command.User(config), Action: command.User(cfg),
}, },
{ {
Name: "password", Name: "password",
Aliases: []string{"pw"}, Aliases: []string{"pw"},
Usage: "Get password from url", Usage: "Get password from url",
Action: command.Password(config), Action: command.Password(cfg),
}, },
{ {
Name: "path", Name: "path",
Aliases: []string{"pt"}, Aliases: []string{"pt"},
Usage: "Get path from url", Usage: "Get path from url",
Action: command.Path(config), Action: command.Path(cfg),
Flags: command.PathFlags(config), Flags: command.PathFlags(cfg),
}, },
{ {
Name: "host", Name: "host",
Aliases: []string{"h"}, Aliases: []string{"h"},
Usage: "Get hostname from url", Usage: "Get hostname from url",
Action: command.Host(config), Action: command.Host(cfg),
}, },
{ {
Name: "port", Name: "port",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Get port from url", Usage: "Get port from url",
Action: command.Port(config), Action: command.Port(cfg),
}, },
{ {
Name: "query", Name: "query",
Aliases: []string{"q"}, Aliases: []string{"q"},
Usage: "Get query from url", Usage: "Get query from url",
Action: command.Query(config), Action: command.Query(cfg),
Flags: command.QueryFlags(config), Flags: command.QueryFlags(cfg),
}, },
{ {
Name: "fragment", Name: "fragment",
Aliases: []string{"f"}, Aliases: []string{"f"},
Usage: "Get fragment from url", Usage: "Get fragment from url",
Action: command.Fragment(config), Action: command.Fragment(cfg),
}, },
}, },
Before: func(ctx *cli.Context) error {
if cfg.URL == "" && !cfg.Stdin {
_ = cli.ShowAppHelp(ctx)
return fmt.Errorf("error: %w", config.ErrRequiredFlagsNotSet)
}
if cfg.URL != "" && cfg.Stdin {
_ = cli.ShowAppHelp(ctx)
return fmt.Errorf("error: %w", config.ErrExclusiveFlags)
}
if cfg.Stdin {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
stdin, err := io.ReadAll(os.Stdin)
if err != nil {
return fmt.Errorf("error: %w: %w", config.ErrEmptyStdin, err)
}
cfg.URL = strings.TrimSuffix(string(stdin), "\n")
}
if cfg.URL == "" {
return fmt.Errorf("error: %w", config.ErrEmptyStdin)
}
}
return nil
},
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {

View File

@ -1,10 +1,12 @@
package command package command
import ( import (
"fmt"
"net/url" "net/url"
"strings" "strings"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/thegeeklab/url-parser/config"
) )
func parseURL(raw string) *url.URL { func parseURL(raw string) *url.URL {
@ -12,7 +14,7 @@ func parseURL(raw string) *url.URL {
url, err := url.Parse(urlString) url, err := url.Parse(urlString)
if err != nil { if err != nil {
logrus.Fatal(err) logrus.Fatal(fmt.Errorf("%w: %w", config.ErrParseURL, err))
} }
return url return url

View File

@ -8,9 +8,9 @@ import (
) )
// Fragment prints out the fragment part from the url. // Fragment prints out the fragment part from the url.
func Fragment(config *config.Config) cli.ActionFunc { func Fragment(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if len(parts.Scheme) > 0 { if len(parts.Scheme) > 0 {
fmt.Println(parts.Fragment) fmt.Println(parts.Fragment)

View File

@ -8,9 +8,9 @@ import (
) )
// Host prints out the host part from the url. // Host prints out the host part from the url.
func Host(config *config.Config) cli.ActionFunc { func Host(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if len(parts.Scheme) > 0 { if len(parts.Scheme) > 0 {
fmt.Println(parts.Hostname()) fmt.Println(parts.Hostname())

View File

@ -8,9 +8,9 @@ import (
) )
// Password prints out the password part from url. // Password prints out the password part from url.
func Password(config *config.Config) cli.ActionFunc { func Password(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if parts.User != nil { if parts.User != nil {
pw, _ := parts.User.Password() pw, _ := parts.User.Password()

View File

@ -9,23 +9,23 @@ import (
) )
// PathFlags defines flags for path subcommand. // PathFlags defines flags for path subcommand.
func PathFlags(config *config.Config) []cli.Flag { func PathFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
&cli.IntFlag{ &cli.IntFlag{
Name: "path-index", Name: "path-index",
Usage: "filter parsed path by index", Usage: "filter parsed path by index",
EnvVars: []string{"URL_PARSER_PATH_INDEX"}, EnvVars: []string{"URL_PARSER_PATH_INDEX"},
Value: -1, Value: -1,
Destination: &config.PathIndex, Destination: &cfg.PathIndex,
}, },
} }
} }
// Path prints out the path part from url. // Path prints out the path part from url.
func Path(config *config.Config) cli.ActionFunc { func Path(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
i := config.PathIndex i := cfg.PathIndex
if len(parts.Path) > 0 { if len(parts.Path) > 0 {
if i > -1 { if i > -1 {

View File

@ -8,9 +8,9 @@ import (
) )
// Port prints out the port from the url. // Port prints out the port from the url.
func Port(config *config.Config) cli.ActionFunc { func Port(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if len(parts.Scheme) > 0 { if len(parts.Scheme) > 0 {
fmt.Println(parts.Port()) fmt.Println(parts.Port())

View File

@ -8,22 +8,22 @@ import (
) )
// QueryFlags defines flags for query subcommand. // QueryFlags defines flags for query subcommand.
func QueryFlags(config *config.Config) []cli.Flag { func QueryFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{ return []cli.Flag{
&cli.StringFlag{ &cli.StringFlag{
Name: "query-field", Name: "query-field",
Usage: "filter parsed query string by field name", Usage: "filter parsed query string by field name",
EnvVars: []string{"URL_PARSER_QUERY_FIELD"}, EnvVars: []string{"URL_PARSER_QUERY_FIELD"},
Destination: &config.QueryField, Destination: &cfg.QueryField,
}, },
} }
} }
// Query prints out the query part from url. // Query prints out the query part from url.
func Query(config *config.Config) cli.ActionFunc { func Query(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
f := config.QueryField f := cfg.QueryField
if len(parts.RawQuery) > 0 { if len(parts.RawQuery) > 0 {
if f != "" { if f != "" {

View File

@ -8,9 +8,9 @@ import (
) )
// Run default command and print out full url. // Run default command and print out full url.
func Run(config *config.Config) cli.ActionFunc { func Run(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if len(parts.String()) > 0 { if len(parts.String()) > 0 {
fmt.Println(parts) fmt.Println(parts)

View File

@ -8,9 +8,9 @@ import (
) )
// Scheme prints out the scheme part from the url. // Scheme prints out the scheme part from the url.
func Scheme(config *config.Config) cli.ActionFunc { func Scheme(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if len(parts.Scheme) > 0 { if len(parts.Scheme) > 0 {
fmt.Println(parts.Scheme) fmt.Println(parts.Scheme)

View File

@ -8,9 +8,9 @@ import (
) )
// User prints out the user part from url. // User prints out the user part from url.
func User(config *config.Config) cli.ActionFunc { func User(cfg *config.Config) cli.ActionFunc {
return func(ctx *cli.Context) error { return func(ctx *cli.Context) error {
parts := parseURL(config.URL) parts := parseURL(cfg.URL)
if parts.User != nil { if parts.User != nil {
if len(parts.User.Username()) > 0 { if len(parts.User.Username()) > 0 {

View File

@ -1,7 +1,18 @@
package config package config
import "errors"
var (
ErrRequiredFlagsNotSet = errors.New("either \"url\" or \"stdin\" must be set")
ErrExclusiveFlags = errors.New("\"url\" and \"stdin\" are mutually exclusive")
ErrEmptyStdin = errors.New("\"stdin\" must not be empty")
ErrReadStdin = errors.New("failed to read \"stdin\"")
ErrParseURL = errors.New("failed to parse url")
)
type Config struct { type Config struct {
URL string URL string
Stdin bool
QueryField string QueryField string
PathIndex int PathIndex int
} }