Skip to content

Commit 3a5eb3d

Browse files
committed
Started on urly util
1 parent 8972b36 commit 3a5eb3d

File tree

9 files changed

+285
-4
lines changed

9 files changed

+285
-4
lines changed

TODO.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
- [ ] Flag to print script
2424
- [ ] Flag to print block
2525
- [ ] Flag for format: utf8/16/32/latin1/etc.
26+
- [ ] Flag to print column (in row)
2627

2728
## Unicount
2829

cmd/bytecount/bytecount.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"bufio"
55
"fmt"
66
"io"
7-
"log"
87
"os"
98

109
"github.com/FileFormatInfo/fftools/internal"

cmd/uniwhat/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Uniwhat
2+
3+

cmd/urly/urly.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/url"
7+
"os"
8+
9+
"github.com/FileFormatInfo/fftools/internal"
10+
"github.com/spf13/pflag"
11+
)
12+
13+
func setUserName(userInfo *url.Userinfo, username string) *url.Userinfo {
14+
if userInfo != nil {
15+
password, hasPassword := userInfo.Password()
16+
if hasPassword {
17+
return url.UserPassword(username, password)
18+
} else {
19+
return url.User(username)
20+
}
21+
} else {
22+
return url.User(username)
23+
}
24+
}
25+
26+
func setPassword(userInfo *url.Userinfo, password string) *url.Userinfo {
27+
if userInfo != nil {
28+
username := userInfo.Username()
29+
return url.UserPassword(username, password)
30+
} else {
31+
return url.UserPassword("", password)
32+
}
33+
}
34+
35+
type UrlJson struct {
36+
Scheme string `json:"scheme"`
37+
Host string `json:"host"`
38+
Port string `json:"port"`
39+
Path string `json:"path"`
40+
Query string `json:"query"`
41+
Fragment string `json:"fragment"`
42+
Username string `json:"username"`
43+
Password string `json:"password"`
44+
Url string `json:"url"`
45+
Params map[string][]string `json:"params,omitempty"`
46+
}
47+
48+
func toJson(theUrl *url.URL) string {
49+
urlJson := UrlJson{
50+
Scheme: theUrl.Scheme,
51+
Host: theUrl.Hostname(),
52+
Port: theUrl.Port(),
53+
Path: theUrl.Path,
54+
Query: theUrl.RawQuery,
55+
Fragment: theUrl.Fragment,
56+
Url: theUrl.String(),
57+
Params: theUrl.Query(),
58+
}
59+
if theUrl.User != nil {
60+
urlJson.Username = theUrl.User.Username()
61+
password, hasPassword := theUrl.User.Password()
62+
if hasPassword {
63+
urlJson.Password = password
64+
}
65+
}
66+
jsonStr, err := json.Marshal(urlJson)
67+
if err != nil {
68+
fmt.Fprintf(os.Stderr, "ERROR: unable to marshal URL to JSON: %v\n", err)
69+
os.Exit(1)
70+
}
71+
return string(jsonStr)
72+
}
73+
74+
// Detailed help text
75+
var helpText = `urly: A URL parsing and processing tool.`
76+
77+
func main() {
78+
79+
var scheme = pflag.String("scheme", "", "Set the URL scheme")
80+
var envPassword = pflag.String("password-env", "", "Environment variable containing the password for URL processing")
81+
var stdinPassword = pflag.Bool("password-stdin", false, "Read password from standard input")
82+
var envUrl = pflag.String("url-env", "", "Environment variable containing the URL to process")
83+
var envUsername = pflag.String("username-env", "", "Environment variable containing the username for URL processing")
84+
var textUsername = pflag.String("username", "", "Username for URL processing")
85+
//LATER: var format = pflag.String("format", "text", "Output format: text or json")
86+
var output = pflag.String("output", "url", "Output type: url, scheme, host, port, path, query, fragment, userinfo, username, password")
87+
var newline = pflag.Bool("newline", false, "Append newline to output")
88+
var help = pflag.Bool("help", false, "Detailed help")
89+
var version = pflag.Bool("version", false, "Version info")
90+
91+
pflag.Parse()
92+
93+
if *version {
94+
internal.PrintVersion("urly")
95+
return
96+
}
97+
98+
if *help {
99+
fmt.Printf("%s\n", helpText)
100+
return
101+
}
102+
103+
var theUrl *url.URL
104+
var parseErr error
105+
106+
if *envUrl != "" {
107+
textUrl := os.Getenv(*envUrl)
108+
theUrl, parseErr = url.Parse(textUrl)
109+
if parseErr != nil {
110+
fmt.Fprintf(os.Stderr, "ERROR: unable to parse URL from environment variable %s: %v\n", *envUrl, parseErr)
111+
os.Exit(1)
112+
}
113+
} else {
114+
args := pflag.Args()
115+
if len(args) > 0 {
116+
theUrl, parseErr = url.Parse(args[0])
117+
if parseErr != nil {
118+
fmt.Fprintf(os.Stderr, "ERROR: Unable to parse URL from argument: %v\n", parseErr)
119+
os.Exit(1)
120+
}
121+
if len(args) > 1 {
122+
fmt.Fprintf(os.Stderr, "WARNING: Ignoring extra arguments (count=%d)\n", len(args)-1)
123+
}
124+
}
125+
}
126+
127+
if theUrl == nil {
128+
theUrl = &url.URL{}
129+
}
130+
131+
if *envUsername != "" {
132+
theUrl.User = setUserName(theUrl.User, os.Getenv(*envUsername))
133+
} else if *textUsername != "" {
134+
theUrl.User = setUserName(theUrl.User, *textUsername)
135+
}
136+
137+
if *envPassword != "" {
138+
theUrl.User = setPassword(theUrl.User, os.Getenv(*envPassword))
139+
} else if *stdinPassword {
140+
var password string
141+
_, err := fmt.Fscanln(os.Stdin, &password)
142+
if err != nil {
143+
fmt.Fprintf(os.Stderr, "ERROR: Unable to read password from stdin: %v\n", err)
144+
os.Exit(1)
145+
}
146+
theUrl.User = setPassword(theUrl.User, password)
147+
}
148+
149+
if *scheme != "" {
150+
theUrl.Scheme = *scheme
151+
}
152+
153+
switch *output {
154+
case "url":
155+
fmt.Println(theUrl.String())
156+
case "scheme":
157+
fmt.Print(theUrl.Scheme)
158+
case "host":
159+
fmt.Print(theUrl.Hostname())
160+
case "port":
161+
fmt.Print(theUrl.Port())
162+
case "path":
163+
fmt.Print(theUrl.Path)
164+
case "query":
165+
fmt.Print(theUrl.RawQuery)
166+
case "fragment":
167+
fmt.Print(theUrl.Fragment)
168+
case "userinfo":
169+
if theUrl.User != nil {
170+
fmt.Print(theUrl.User.Username())
171+
password, isSet := theUrl.User.Password()
172+
if isSet {
173+
fmt.Print(":")
174+
fmt.Print(password)
175+
}
176+
}
177+
case "username":
178+
if theUrl.User != nil {
179+
fmt.Print(theUrl.User.Username())
180+
}
181+
case "password":
182+
if theUrl.User != nil {
183+
password, hasPassword := theUrl.User.Password()
184+
if hasPassword {
185+
fmt.Print(password)
186+
}
187+
}
188+
case "json":
189+
fmt.Print(toJson(theUrl))
190+
default:
191+
fmt.Fprintf(os.Stderr, "ERROR: Unknown output type: %s\n", *output)
192+
os.Exit(1)
193+
}
194+
if *newline {
195+
fmt.Println()
196+
}
197+
}

cmd/urly/urly_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/rogpeppe/go-internal/testscript"
8+
)
9+
10+
func TestMain(m *testing.M) {
11+
exitVal := testscript.RunMain(m, nil)
12+
13+
os.Exit(exitVal)
14+
}
15+
16+
func TestUrly(t *testing.T) {
17+
testscript.Run(t, testscript.Params{
18+
Dir: "../../testdata",
19+
})
20+
}

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ toolchain go1.24.2
77
require (
88
github.com/anyascii/go v0.3.2
99
github.com/olekukonko/tablewriter v1.0.7
10+
github.com/rogpeppe/go-internal v1.14.1
1011
github.com/spf13/pflag v1.0.6
1112
golang.org/x/crypto v0.38.0
13+
golang.org/x/term v0.36.0
1214
golang.org/x/text v0.26.0
1315
)
1416

@@ -21,5 +23,5 @@ require (
2123
github.com/olekukonko/ll v0.0.8 // indirect
2224
github.com/rivo/uniseg v0.2.0 // indirect
2325
golang.org/x/sys v0.37.0 // indirect
24-
golang.org/x/term v0.36.0 // indirect
26+
golang.org/x/tools v0.33.0 // indirect
2527
)

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,19 @@ github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm
1717
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
1818
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
1919
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
20+
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
21+
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
2022
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
2123
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
2224
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
2325
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
2426
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2527
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
26-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
27-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
2828
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
2929
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
3030
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
3131
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
3232
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
3333
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
34+
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
35+
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=

run.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env bash
2+
#
3+
# run tests locally
4+
#
5+
6+
set -o errexit
7+
set -o pipefail
8+
set -o nounset
9+
10+
if [ -d "./dist" ]; then
11+
echo "INFO: removing old binaries"
12+
rm -rf ./dist
13+
fi
14+
15+
mkdir ./dist
16+
17+
echo "INFO: building new binaries"
18+
go build -o ./dist/urly cmd/urly/urly.go
19+
if [ ! -f "./dist/urly" ]; then
20+
echo "ERROR: failed to build urly"
21+
exit 1
22+
fi
23+
24+
25+
export PATH=$(pwd)/dist:$PATH
26+
27+
echo "INFO: running tests"
28+
go test -timeout 30s -run "^TestUrly$" github.com/FileFormatInfo/fftools/cmd/urly
29+
30+
echo "INFO: complete at $(date -u +%Y-%m-%dT%H:%M:%SZ)"

testdata/urly.txt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
exec urly --username=me https://www.example.com/
2+
stdout 'https://[email protected]/'
3+
4+
# set both username and password
5+
env DB_PASSWORD=the-db-password
6+
env DB_USERNAME=who-am-i
7+
env DB_URL=postgres://myhosted-server.example.com/default?sslmode=disabled
8+
exec urly --username-env=DB_USERNAME --password-env=DB_PASSWORD --url-env=DB_URL
9+
stdout 'postgres://who-am-i:the-db-password@myhosted-server\.example\.com/default\?sslmode=disabled'
10+
11+
# set just password
12+
env DB_PASSWORD=the-db-password
13+
env DB_URL=postgres://[email protected]/default?sslmode=disabled
14+
exec urly --username-env=DB_USERNAME --password-env=DB_PASSWORD --url-env=DB_URL
15+
stdout 'postgres://who-am-i:the-db-password@myhosted-server\.example\.com/default\?sslmode=disabled'
16+
17+
# fix the scheme
18+
exec urly --scheme=postgresql postgres://[email protected]/db
19+
stdout 'postgresql://user@server\.example\.com/db'
20+
21+
# json output
22+
exec urly --output=json https://u:[email protected]/path/to/file.ext?param=value
23+
stdout '{"scheme":"https","host":"example\.com","port":"","path":"/path/to/file\.ext","query":"param=value","fragment":"","username":"u","password":"p","url":"https://u:p@example\.com/path/to/file\.ext\?param=value","params":{"param":\["value"\]}}'
24+
25+
# add a parameter
26+
# remove a parameter
27+
# remove all parameters

0 commit comments

Comments
 (0)