Compare commits

..

No commits in common. "main" and "v0.2.7" have entirely different histories.
main ... v0.2.7

65 changed files with 1262 additions and 1377 deletions

23
.chglog/CHANGELOG.tpl.md Executable file
View File

@ -0,0 +1,23 @@
# Changelog
{{ range .Versions -}}
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ (regexReplaceAll "(.*)/issues/(.*)" (regexReplaceAll "(Co-\\w*-by.*)" .Subject "") "${1}/pull/${2}") | trim }}
{{ end }}
{{- end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}

25
.chglog/config.yml Executable file
View File

@ -0,0 +1,25 @@
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/thegeeklab/url-parser
options:
commit_groups:
title_maps:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
refactor: Code Refactoring
chore: Others
test: Testing
ci: CI Pipeline
docs: Documentation
header:
pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
pattern_maps:
- Type
- Scope
- Subject
notes:
keywords:
- BREAKING CHANGE

View File

@ -1,4 +0,0 @@
url-parser
herloct
multiarch
(P|p)rebuilt

210
.drone.jsonnet Normal file
View File

@ -0,0 +1,210 @@
local PipelineTest = {
kind: 'pipeline',
image_pull_secrets: ['docker_config'],
name: 'test',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'staticcheck',
image: 'golang:1.16',
commands: [
'go run honnef.co/go/tools/cmd/staticcheck ./...',
],
volumes: [
{
name: 'gopath',
path: '/go',
},
],
},
{
name: 'lint',
image: 'golang:1.16',
commands: [
'go run golang.org/x/lint/golint -set_exit_status ./...',
],
volumes: [
{
name: 'gopath',
path: '/go',
},
],
},
{
name: 'vet',
image: 'golang:1.16',
commands: [
'go vet ./...',
],
volumes: [
{
name: 'gopath',
path: '/go',
},
],
},
{
name: 'test',
image: 'golang:1.16',
commands: [
'go test -race -coverprofile=coverage.txt -covermode=atomic ./...',
],
volumes: [
{
name: 'gopath',
path: '/go',
},
],
},
{
name: 'coverage',
image: 'plugins/codecov',
settings: {
token: {
from_secret: 'codecov_token',
},
files: [
'coverage.txt',
],
},
},
],
volumes: [
{
name: 'gopath',
temp: {},
},
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineBuildBinaries = {
kind: 'pipeline',
image_pull_secrets: ['docker_config'],
name: 'build-binaries',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'build',
image: 'techknowlogick/xgo:go-1.16.x',
commands: [
'[ -z "${DRONE_TAG}" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}',
'mkdir -p release/',
"cd cmd/url-parser && xgo -ldflags \"-s -w -X main.version=$BUILD_VERSION\" -tags netgo -targets 'linux/amd64,linux/arm-6,linux/arm-7,linux/arm64' -out url-parser .",
'mv /build/* /drone/src/release/',
'ls -l /drone/src/release/',
],
},
{
name: 'executable',
image: 'alpine',
commands: [
'$(find release/ -executable -type f | grep url-parser-linux-amd64) --help',
],
},
{
name: 'compress',
image: 'alpine',
commands: [
'apk add upx',
'find release/ -maxdepth 1 -executable -type f -exec upx {} \\;',
'ls -lh release/',
],
},
{
name: 'checksum',
image: 'alpine',
commands: [
'cd release/ && sha256sum * > sha256sum.txt',
],
},
{
name: 'changelog-generate',
image: 'thegeeklab/git-chglog',
commands: [
'git fetch -tq',
'git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}',
],
},
{
name: 'changelog-format',
image: 'thegeeklab/alpine-tools',
commands: [
'prettier CHANGELOG.md',
'prettier -w CHANGELOG.md',
],
},
{
name: 'publish',
image: 'plugins/github-release',
settings: {
overwrite: true,
api_key: {
from_secret: 'github_token',
},
files: ['release/*'],
title: '${DRONE_TAG}',
note: 'CHANGELOG.md',
},
when: {
ref: [
'refs/tags/**',
],
},
},
],
depends_on: [
'test',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'],
},
};
local PipelineNotifications = {
kind: 'pipeline',
image_pull_secrets: ['docker_config'],
name: 'notifications',
platform: {
os: 'linux',
arch: 'amd64',
},
steps: [
{
name: 'matrix',
image: 'thegeeklab/drone-matrix',
settings: {
homeserver: { from_secret: 'matrix_homeserver' },
roomid: { from_secret: 'matrix_roomid' },
template: 'Status: **{{ build.Status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.Link }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}',
username: { from_secret: 'matrix_username' },
password: { from_secret: 'matrix_password' },
},
when: {
status: ['success', 'failure'],
},
},
],
depends_on: [
'build-binaries',
],
trigger: {
ref: ['refs/heads/main', 'refs/tags/**'],
status: ['success', 'failure'],
},
};
[
PipelineTest,
PipelineBuildBinaries,
PipelineNotifications,
]

180
.drone.yml Normal file
View File

@ -0,0 +1,180 @@
---
kind: pipeline
name: test
platform:
os: linux
arch: amd64
steps:
- name: staticcheck
image: golang:1.16
commands:
- go run honnef.co/go/tools/cmd/staticcheck ./...
volumes:
- name: gopath
path: /go
- name: lint
image: golang:1.16
commands:
- go run golang.org/x/lint/golint -set_exit_status ./...
volumes:
- name: gopath
path: /go
- name: vet
image: golang:1.16
commands:
- go vet ./...
volumes:
- name: gopath
path: /go
- name: test
image: golang:1.16
commands:
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
volumes:
- name: gopath
path: /go
- name: coverage
image: plugins/codecov
settings:
files:
- coverage.txt
token:
from_secret: codecov_token
volumes:
- name: gopath
temp: {}
image_pull_secrets:
- docker_config
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
---
kind: pipeline
name: build-binaries
platform:
os: linux
arch: amd64
steps:
- name: build
image: techknowlogick/xgo:go-1.16.x
commands:
- "[ -z \"${DRONE_TAG}\" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}"
- mkdir -p release/
- cd cmd/url-parser && xgo -ldflags "-s -w -X main.version=$BUILD_VERSION" -tags netgo -targets 'linux/amd64,linux/arm-6,linux/arm-7,linux/arm64' -out url-parser .
- mv /build/* /drone/src/release/
- ls -l /drone/src/release/
- name: executable
image: alpine
commands:
- $(find release/ -executable -type f | grep url-parser-linux-amd64) --help
- name: compress
image: alpine
commands:
- apk add upx
- find release/ -maxdepth 1 -executable -type f -exec upx {} \;
- ls -lh release/
- name: checksum
image: alpine
commands:
- cd release/ && sha256sum * > sha256sum.txt
- name: changelog-generate
image: thegeeklab/git-chglog
commands:
- git fetch -tq
- git-chglog --no-color --no-emoji -o CHANGELOG.md ${DRONE_TAG:---next-tag unreleased unreleased}
- name: changelog-format
image: thegeeklab/alpine-tools
commands:
- prettier CHANGELOG.md
- prettier -w CHANGELOG.md
- name: publish
image: plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- release/*
note: CHANGELOG.md
overwrite: true
title: ${DRONE_TAG}
when:
ref:
- refs/tags/**
image_pull_secrets:
- docker_config
trigger:
ref:
- refs/heads/main
- refs/tags/**
- refs/pull/**
depends_on:
- test
---
kind: pipeline
name: notifications
platform:
os: linux
arch: amd64
steps:
- name: matrix
image: thegeeklab/drone-matrix
settings:
homeserver:
from_secret: matrix_homeserver
password:
from_secret: matrix_password
roomid:
from_secret: matrix_roomid
template: "Status: **{{ build.Status }}**<br/> Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.Link }}){{#if build.Branch}} ({{ build.Branch }}){{/if}} by {{ commit.Author }}<br/> Message: {{ commit.Message.Title }}"
username:
from_secret: matrix_username
when:
status:
- success
- failure
image_pull_secrets:
- docker_config
trigger:
ref:
- refs/heads/main
- refs/tags/**
status:
- success
- failure
depends_on:
- build-binaries
---
kind: signature
hmac: 93e1fe6fe0e097355d77fd21bc67f05998e1fccec3848624cc6508f9de8facd9
...

View File

@ -50,9 +50,6 @@ branches:
required_status_checks: required_status_checks:
strict: false strict: false
contexts: contexts:
- ci/woodpecker/pr/test - continuous-integration/drone/pr
- ci/woodpecker/pr/build-package enforce_admins: null
- ci/woodpecker/pr/docs
enforce_admins: false
required_linear_history: true
restrictions: null restrictions: null

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
/dist /release/
/release
/url-parser* /url-parser*
coverage.out coverage.out

View File

@ -1,47 +0,0 @@
---
version: "1.1"
versioning:
update-major: []
update-minor: [feat]
update-patch: [fix, perf, refactor, chore, test, ci, docs]
tag:
pattern: "v%d.%d.%d"
release-notes:
sections:
- name: Features
commit-types: [feat]
section-type: commits
- name: Bug Fixes
commit-types: [fix]
section-type: commits
- name: Performance Improvements
commit-types: [perf]
section-type: commits
- name: Code Refactoring
commit-types: [refactor]
section-type: commits
- name: Others
commit-types: [chore]
section-type: commits
- name: Testing
commit-types: [test]
section-type: commits
- name: CI Pipeline
commit-types: [ci]
section-type: commits
- name: Documentation
commit-types: [docs]
section-type: commits
- name: BREAKING CHANGES
section-type: breaking-changes
commit-message:
footer:
issue:
key: issue
add-value-prefix: "#"
issue:
regex: "#?[0-9]+"

View File

@ -1,96 +0,0 @@
linters:
enable-all: false
disable-all: true
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- unused
- asasalint
- asciicheck
- bidichk
- bodyclose
- containedctx
- contextcheck
- decorder
- dogsled
- dupl
- dupword
- durationcheck
- errchkjson
- errname
- errorlint
- exhaustive
- copyloopvar
- forcetypeassert
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- err113
- gofmt
- gofumpt
- goheader
- goimports
- mnd
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec
- grouper
- importas
- interfacebloat
- ireturn
- lll
- loggercheck
- maintidx
- makezero
- misspell
- musttag
- nakedret
- nestif
- nilerr
- nilnil
- nlreturn
- noctx
- nolintlint
- nonamedreturns
- nosprintfhostport
- prealloc
- predeclared
- promlinter
- reassign
- revive
# - rowserrcheck
# - sqlclosecheck
# - structcheck
- stylecheck
- tagliatelle
- tenv
- testableexamples
- thelper
- tparallel
- unconvert
- unparam
- usestdlibvars
# - wastedassign
- whitespace
- wsl
- zerologlint
fast: false
run:
timeout: 3m
linters-settings:
gofumpt:
extra-rules: true

View File

@ -1,6 +0,0 @@
---
default: True
MD013: False
MD041: False
MD004:
style: dash

View File

@ -1,2 +1,3 @@
.drone.yml
*.tpl.md *.tpl.md
LICENSE LICENSE

View File

@ -1,41 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: build
image: docker.io/techknowlogick/xgo:go-1.23.3
commands:
- ln -s $(pwd) /source
- make release
- name: executable
image: quay.io/thegeeklab/alpine-tools
commands:
- $(find dist/ -executable -type f -iname ${CI_REPO_NAME}-linux-amd64) --help
- name: changelog
image: quay.io/thegeeklab/git-sv
commands:
- git sv current-version
- git sv release-notes -t ${CI_COMMIT_TAG:-next} -o CHANGELOG.md
- cat CHANGELOG.md
- name: publish-github
image: docker.io/plugins/github-release
settings:
api_key:
from_secret: github_token
files:
- dist/*
note: CHANGELOG.md
overwrite: true
title: ${CI_COMMIT_TAG}
when:
- event: [tag]
depends_on:
- test

View File

@ -1,22 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: markdownlint
image: quay.io/thegeeklab/markdownlint-cli
commands:
- markdownlint 'README.md' 'CONTRIBUTING.md'
- name: spellcheck
image: quay.io/thegeeklab/alpine-tools
commands:
- spellchecker --files 'README.md' 'CONTRIBUTING.md' -d .dictionary -p spell indefinite-article syntax-urls
environment:
FORCE_COLOR: "true"
depends_on:
- build-package

View File

@ -1,26 +0,0 @@
---
when:
- event: [tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
runs_on: [success, failure]
steps:
- name: matrix
image: quay.io/thegeeklab/wp-matrix
settings:
homeserver:
from_secret: matrix_homeserver
room_id:
from_secret: matrix_room_id
user_id:
from_secret: matrix_user_id
access_token:
from_secret: matrix_access_token
when:
- status: [success, failure]
depends_on:
- docs

View File

@ -1,17 +0,0 @@
---
when:
- event: [pull_request, tag]
- event: [push, manual]
branch:
- ${CI_REPO_DEFAULT_BRANCH}
steps:
- name: lint
image: docker.io/library/golang:1.23.3
commands:
- make lint
- name: test
image: docker.io/library/golang:1.23.3
commands:
- make test

View File

@ -3,7 +3,7 @@
## Security ## Security
If you think you have found a **security issue**, please do not mention it in this repository. If you think you have found a **security issue**, please do not mention it in this repository.
Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential. Instead, send an email to security@thegeeklab.de with as many details as possible so it can be handled confidential.
## Bug Reports and Feature Requests ## Bug Reports and Feature Requests

104
Makefile
View File

@ -1,104 +0,0 @@
# renovate: datasource=github-releases depName=mvdan/gofumpt
GOFUMPT_PACKAGE_VERSION := v0.7.0
# renovate: datasource=github-releases depName=golangci/golangci-lint
GOLANGCI_LINT_PACKAGE_VERSION := v1.62.0
# renovate: datasource=docker depName=docker.io/techknowlogick/xgo
XGO_PACKAGE_VERSION := go-1.23.3
EXECUTABLE := url-parser
DIST := dist
DIST_DIRS := $(DIST)
IMPORT := github.com/thegeeklab/$(EXECUTABLE)
GO ?= go
CWD ?= $(shell pwd)
PACKAGES ?= $(shell go list ./...)
SOURCES ?= $(shell find . -name "*.go" -type f)
GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION)
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION)
XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest
GOTESTSUM_PACKAGE ?= gotest.tools/gotestsum@latest
GENERATE ?=
XGO_TARGETS ?= linux/amd64,linux/arm-6,linux/arm-7,linux/arm64
TARGETOS ?= linux
TARGETARCH ?= amd64
ifneq ("$(TARGETVARIANT)","")
GOARM ?= $(subst v,,$(TARGETVARIANT))
endif
TAGS ?= netgo
ifndef VERSION
ifneq ($(CI_COMMIT_TAG),)
VERSION ?= $(subst v,,$(CI_COMMIT_TAG))
else
VERSION ?= $(shell git rev-parse --short HEAD)
endif
endif
ifndef DATE
DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%z")
endif
LDFLAGS += -s -w -X "main.BuildVersion=$(VERSION)" -X "main.BuildDate=$(DATE)"
.PHONY: all
all: clean build
.PHONY: clean
clean:
$(GO) clean -i ./...
rm -rf $(DIST_DIRS)
.PHONY: fmt
fmt:
$(GO) run $(GOFUMPT_PACKAGE) -extra -w $(SOURCES)
.PHONY: golangci-lint
golangci-lint:
$(GO) run $(GOLANGCI_LINT_PACKAGE) run
.PHONY: lint
lint: golangci-lint
.PHONY: generate
generate:
$(GO) generate $(GENERATE)
.PHONY: test
test:
$(GO) run $(GOTESTSUM_PACKAGE) --no-color=false -- -coverprofile=coverage.out $(PACKAGES)
.PHONY: build
build: $(DIST)/$(EXECUTABLE)
$(DIST)/$(EXECUTABLE): $(SOURCES)
GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) GOARM=$(GOARM) $(GO) build -v -tags '$(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -o $@ ./cmd/$(EXECUTABLE)
$(DIST_DIRS):
mkdir -p $(DIST_DIRS)
.PHONY: xgo
xgo: | $(DIST_DIRS)
$(GO) run $(XGO_PACKAGE) -go $(XGO_PACKAGE_VERSION) -v -ldflags '-extldflags "-static" $(LDFLAGS)' -tags '$(TAGS)' -targets '$(XGO_TARGETS)' -out $(EXECUTABLE) --pkg cmd/$(EXECUTABLE) .
cp /build/* $(CWD)/$(DIST)
ls -l $(CWD)/$(DIST)
.PHONY: checksum
checksum:
cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
ls -l $(CWD)/$(DIST)
.PHONY: release
release: xgo checksum
.PHONY: deps
deps:
$(GO) mod download
$(GO) install $(GOFUMPT_PACKAGE)
$(GO) install $(GOLANGCI_LINT_PACKAGE)
$(GO) install $(XGO_PACKAGE)
$(GO) install $(GOTESTSUM_PACKAGE)

View File

@ -1,9 +1,8 @@
# url-parser # url-parser
Simple command-line URL parser [![Build Status](https://img.shields.io/drone/build/thegeeklab/url-parser?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/url-parser)
[![Build Status](https://ci.thegeeklab.de/api/badges/thegeeklab/url-parser/status.svg)](https://ci.thegeeklab.de/repos/thegeeklab/url-parser)
[![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/url-parser)](https://goreportcard.com/report/github.com/thegeeklab/url-parser) [![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/url-parser)](https://goreportcard.com/report/github.com/thegeeklab/url-parser)
[![Codecov](https://img.shields.io/codecov/c/github/thegeeklab/url-parser)](https://codecov.io/gh/thegeeklab/url-parser)
[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/url-parser)](https://github.com/thegeeklab/url-parser/graphs/contributors) [![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/url-parser)](https://github.com/thegeeklab/url-parser/graphs/contributors)
[![License: MIT](https://img.shields.io/github/license/thegeeklab/url-parser)](https://github.com/thegeeklab/url-parser/blob/main/LICENSE) [![License: MIT](https://img.shields.io/github/license/thegeeklab/url-parser)](https://github.com/thegeeklab/url-parser/blob/main/LICENSE)
@ -11,11 +10,12 @@ Inspired by [herloct/url-parser](https://github.com/herloct/url-parser), a simpl
## Installation ## Installation
Prebuilt multiarch binaries are available for Linux only. Prebuild multiarch binaries are availabe for Linux only:
```Shell ```Shell
curl -SsfL https://github.com/thegeeklab/url-parser/releases/latest/download/url-parser-linux-amd64 -o /usr/local/bin/url-parser curl -L https://github.com/thegeeklab/url-parser/releases/download/v0.1.0/url-parser-0.1.0-linux-amd64 > /usr/local/bin/url-parser
chmod +x /usr/local/bin/url-parser chmod +x /usr/local/bin/url-parser
url-parser --help
``` ```
## Build ## Build
@ -23,7 +23,12 @@ chmod +x /usr/local/bin/url-parser
Build the binary from source with the following command: Build the binary from source with the following command:
```Shell ```Shell
make build export GOOS=linux
export GOARCH=amd64
export CGO_ENABLED=0
export GO111MODULE=on
go build -v -a -tags netgo -o release/url-parser ./cmd/url-parser/
``` ```
## Usage ## Usage
@ -34,7 +39,7 @@ NAME:
url-parser - Parse URL and shows the part of it. url-parser - Parse URL and shows the part of it.
USAGE: USAGE:
url-parser [global options] command [command options] url-parser [global options] command [command options] [arguments...]
VERSION: VERSION:
devel devel
@ -45,7 +50,7 @@ COMMANDS:
user, u Get username from url user, u Get username from url
password, pw Get password from url password, pw Get password from url
path, pt Get path from url path, pt Get path from url
host, ht Get hostname from url host, h Get hostname from url
port, p Get port from url port, p Get port from url
query, q Get query from url query, q Get query from url
fragment, f Get fragment from url fragment, f Get fragment from url
@ -53,43 +58,36 @@ COMMANDS:
GLOBAL OPTIONS: GLOBAL OPTIONS:
--url value source url to parse [$URL_PARSER_URL] --url value source url to parse [$URL_PARSER_URL]
--help, -h show help --help, -h show help (default: false)
--version, -v print the version --version, -v print the version (default: false)
``` ```
## Examples ## Examples
```Shell ```Shell
$ url-parser --url https://somedomain.com host $ url-parser host --url https://somedomain.com
somedomain.com somedomain.com
$ url-parser --url https://herloct@somedomain.com user $ url-parser user --url https://herloct@somedomain.com
herloct herloct
$ url-parser --url https://somedomain.com/path/to path $ url-parser path --url https://somedomain.com/path/to
/path/to /path/to
$ url-parser --url https://somedomain.com/path/to path --path-index=1 $ url-parser path --path-index=1 --url https://somedomain.com/path/to
to to
$ url-parser --url https://somedomain.com/?some-key=somevalue query $ url-parser query --url https://somedomain.com/?some-key=somevalue
some-key=somevalue some-key=somevalue
$ url-parser --url https://somedomain.com/?some-key=somevalue query --query-field=some-key $ url-parser query --query-field=some-key --url https://somedomain.com/?some-key=somevalue
somevalue somevalue
# It is also possible to read the URL from stdin
$ echo "https://somedomain.com" | url-parser host
somedomain.com
# Get json output or all parsed parts
$ url-parser --url https://somedomain.com/?some-key=somevalue all --json
{"scheme":"https","hostname":"somedomain.com","port":"","path":"/","fragment":"","rawQuery":"some-key=somevalue","queryParams":[{"key":"some-key","value":"somevalue"}],"username":"","password":""}
``` ```
## Contributors ## Contributors
Special thanks to all [contributors](https://github.com/thegeeklab/url-parser/graphs/contributors). If you would like to contribute, please see the [instructions](https://github.com/thegeeklab/url-parser/blob/main/CONTRIBUTING.md). Special thanks goes to all [contributors](https://github.com/thegeeklab/url-parser/graphs/contributors). If you would like to contribute,
please see the [instructions](https://github.com/thegeeklab/url-parser/blob/main/CONTRIBUTING.md).
## License ## License

84
cmd/url-parser/config.go Normal file
View File

@ -0,0 +1,84 @@
package main
import (
"github.com/thegeeklab/url-parser/internal/command"
"github.com/urfave/cli/v2"
)
func globalFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "url",
Usage: "source url to parse",
EnvVars: []string{"URL_PARSER_URL"},
},
}
}
func configCommands() []*cli.Command {
return []*cli.Command{
{
Name: "all",
Aliases: []string{"a"},
Usage: "Get all parts from url",
Action: command.Run,
Flags: globalFlags(),
},
{
Name: "scheme",
Aliases: []string{"s"},
Usage: "Get scheme from url",
Action: command.Scheme,
Flags: globalFlags(),
},
{
Name: "user",
Aliases: []string{"u"},
Usage: "Get username from url",
Action: command.User,
Flags: globalFlags(),
},
{
Name: "password",
Aliases: []string{"pw"},
Usage: "Get password from url",
Action: command.Password,
Flags: globalFlags(),
},
{
Name: "path",
Aliases: []string{"pt"},
Usage: "Get path from url",
Action: command.Path,
Flags: append(globalFlags(), command.PathFlags()...),
},
{
Name: "host",
Aliases: []string{"h"},
Usage: "Get hostname from url",
Action: command.Host,
Flags: globalFlags(),
},
{
Name: "port",
Aliases: []string{"p"},
Usage: "Get port from url",
Action: command.Port,
Flags: globalFlags(),
},
{
Name: "query",
Aliases: []string{"q"},
Usage: "Get query from url",
Action: command.Query,
Flags: append(globalFlags(), command.QueryFlags()...),
},
{
Name: "fragment",
Aliases: []string{"f"},
Usage: "Get fragment from url",
Action: command.Fragment,
Flags: globalFlags(),
},
}
}

View File

@ -1,126 +1,26 @@
package main package main
import ( import (
"fmt"
"io"
"os" "os"
"strings"
"github.com/rs/zerolog" "github.com/sirupsen/logrus"
"github.com/rs/zerolog/log" "github.com/thegeeklab/url-parser/internal/command"
"github.com/thegeeklab/url-parser/command"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
) )
//nolint:gochecknoglobals // Version of current build
var ( var Version = "devel"
BuildVersion = "devel"
BuildDate = "00000000"
)
func main() { func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) app := cli.NewApp()
app.Name = "url-parser"
cli.VersionPrinter = func(c *cli.Context) { app.Usage = "Parse URL and shows the part of it."
fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate) app.Version = Version
} app.Action = command.Run
app.Flags = globalFlags()
cfg := &config.Config{} app.Commands = configCommands()
app := &cli.App{
Name: "url-parser",
Usage: "Parse URL and shows the part of it.",
Version: BuildVersion,
Action: command.Run(cfg),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Usage: "source url to parse",
EnvVars: []string{"URL_PARSER_URL"},
Destination: &cfg.URL,
},
},
Commands: []*cli.Command{
{
Name: "all",
Aliases: []string{"a"},
Usage: "Get all parts from url",
Action: command.Run(cfg),
Flags: command.AllFlags(cfg),
},
{
Name: "scheme",
Aliases: []string{"s"},
Usage: "Get scheme from url",
Action: command.Scheme(cfg),
},
{
Name: "user",
Aliases: []string{"u"},
Usage: "Get username from url",
Action: command.User(cfg),
},
{
Name: "password",
Aliases: []string{"pw"},
Usage: "Get password from url",
Action: command.Password(cfg),
},
{
Name: "path",
Aliases: []string{"pt"},
Usage: "Get path from url",
Action: command.Path(cfg),
Flags: command.PathFlags(cfg),
},
{
Name: "host",
Aliases: []string{"ht"},
Usage: "Get hostname from url",
Action: command.Host(cfg),
},
{
Name: "port",
Aliases: []string{"p"},
Usage: "Get port from url",
Action: command.Port(cfg),
},
{
Name: "query",
Aliases: []string{"q"},
Usage: "Get query from url",
Action: command.Query(cfg),
Flags: command.QueryFlags(cfg),
},
{
Name: "fragment",
Aliases: []string{"f"},
Usage: "Get fragment from url",
Action: command.Fragment(cfg),
},
},
Before: func(_ *cli.Context) error {
if cfg.URL == "" {
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.ErrReadStdin, err)
}
cfg.URL = strings.TrimSuffix(string(stdin), "\n")
}
}
if cfg.URL == "" {
return fmt.Errorf("error: %w", config.ErrEmptyURL)
}
return nil
},
}
if err := app.Run(os.Args); err != nil { if err := app.Run(os.Args); err != nil {
log.Fatal().Err(err).Msg("Execution error") logrus.Fatal(err)
} }
} }

8
cmd/url-parser/tools.go Normal file
View File

@ -0,0 +1,8 @@
// +build tools
package tools
import (
_ "golang.org/x/lint/golint"
_ "honnef.co/go/tools/cmd/staticcheck"
)

21
codecov.yml Normal file
View File

@ -0,0 +1,21 @@
codecov:
require_ci_to_pass: true
coverage:
status:
project:
default:
target: auto
threshold: 5%
branches:
- main
if_ci_failed: error
informational: false
only_pulls: false
patch:
default:
target: auto
threshold: 5%
branches:
- main
if_ci_failed: error
only_pulls: false

View File

@ -1,109 +0,0 @@
package command
import (
"net/url"
"strings"
"github.com/rs/zerolog/log"
"github.com/thegeeklab/url-parser/config"
)
type QueryParam struct {
Key string `json:"key"`
Value string `json:"value"`
}
type URL struct {
url *url.URL
Scheme string `json:"scheme"`
Hostname string `json:"hostname"`
Port string `json:"port"`
Path string `json:"path"`
Fragment string `json:"fragment"`
RawQuery string `json:"rawQuery"`
Query string `json:"-"`
QueryParams []QueryParam `json:"queryParams"`
Username string `json:"username"`
Password string `json:"password"`
}
func (u *URL) String() string {
return u.url.String()
}
type Parser struct {
URL string
QueryField string
QuerySplit bool
}
func NewURLParser(url, queryField string, querySplit bool) *Parser {
return &Parser{
URL: url,
QueryField: queryField,
QuerySplit: querySplit,
}
}
func (p *Parser) parse() *URL {
urlString := strings.TrimSpace(p.URL)
parts, err := url.Parse(urlString)
if err != nil {
log.Fatal().Err(err).Msg(config.ErrParseURL.Error())
}
extURL := &URL{
url: parts,
Scheme: parts.Scheme,
Hostname: parts.Hostname(),
Path: parts.Path,
Fragment: parts.Fragment,
QueryParams: []QueryParam{},
}
if len(parts.Scheme) > 0 {
extURL.Hostname = parts.Hostname()
extURL.Port = parts.Port()
}
if parts.User != nil {
if len(parts.User.Username()) > 0 {
extURL.Username = parts.User.Username()
}
}
if parts.User != nil {
pw, _ := parts.User.Password()
if len(pw) > 0 {
extURL.Password = pw
}
}
// Handle query field extraction
if parts.RawQuery != "" {
extURL.RawQuery = parts.RawQuery
}
if p.QueryField != "" {
if result := parts.Query().Get(p.QueryField); result != "" {
extURL.Query = result
}
} else {
extURL.Query = parts.RawQuery
}
// Handle query parameter splitting
values := parts.Query()
for k, v := range values {
if len(v) > 0 {
extURL.QueryParams = append(extURL.QueryParams, QueryParam{
Key: k,
Value: v[0],
})
}
}
return extURL
}

View File

@ -1,64 +0,0 @@
package command
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
)
func TestParse(t *testing.T) {
//nolint:goconst
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected *URL
}{
{
name: "parse url",
config: &config.Config{
URL: urlString,
QuerySplit: true,
},
expected: &URL{
Scheme: "postgres",
Username: "user",
Password: "pass",
Hostname: "host.com",
Port: "5432",
Path: "/path/to",
Query: "key=value&other=other%20value",
RawQuery: "key=value&other=other%20value",
QueryParams: []QueryParam{
{
Key: "key",
Value: "value",
},
{
Key: "other",
Value: "other value",
},
},
Fragment: "some-fragment",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NewURLParser(urlString, "", false).parse()
assert.Equal(t, tt.expected.Scheme, result.Scheme)
assert.Equal(t, tt.expected.Username, result.Username)
assert.Equal(t, tt.expected.Password, result.Password)
assert.Equal(t, tt.expected.Hostname, result.Hostname)
assert.Equal(t, tt.expected.Port, result.Port)
assert.Equal(t, tt.expected.Path, result.Path)
assert.Equal(t, tt.expected.Fragment, result.Fragment)
assert.Equal(t, tt.expected.RawQuery, result.RawQuery)
assert.Equal(t, tt.expected.Query, result.Query)
assert.ElementsMatch(t, tt.expected.QueryParams, result.QueryParams)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Fragment prints out the fragment part from the url.
func Fragment(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if len(parts.Scheme) > 0 {
fmt.Println(parts.Fragment)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestFragment(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get fragment",
config: &config.Config{URL: urlString},
expected: "some-fragment",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Fragment(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Host prints out the host part from the url.
func Host(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if len(parts.Scheme) > 0 {
fmt.Println(parts.Hostname)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestHost(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get host",
config: &config.Config{URL: urlString},
expected: "host.com",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Host(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Password prints out the password part from url.
func Password(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if parts.Password != "" {
fmt.Println(parts.Password)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestPassword(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get password",
config: &config.Config{URL: urlString},
expected: "pass",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Password(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,44 +0,0 @@
package command
import (
"fmt"
"strings"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// PathFlags defines flags for path subcommand.
func PathFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.IntFlag{
Name: "path-index",
Usage: "filter parsed path by index",
EnvVars: []string{"URL_PARSER_PATH_INDEX"},
Value: -1,
Destination: &cfg.PathIndex,
},
}
}
// Path prints out the path part from url.
func Path(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
i := cfg.PathIndex
if len(parts.Path) > 0 {
if i > -1 {
path := strings.Split(parts.Path, "/")
if i++; i < len(path) {
fmt.Println(path[i])
}
} else {
fmt.Println(parts.Path)
}
}
return nil
}
}

View File

@ -1,42 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestPath(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get path",
config: &config.Config{URL: urlString, PathIndex: -1},
expected: "/path/to",
},
{
name: "get path at index",
config: &config.Config{URL: urlString, PathIndex: 0},
expected: "path",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Path(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Port prints out the port from the url.
func Port(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if len(parts.Scheme) > 0 {
fmt.Println(parts.Port)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestPort(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get port",
config: &config.Config{URL: urlString},
expected: "5432",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Port(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,33 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// QueryFlags defines flags for query subcommand.
func QueryFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "query-field",
Usage: "filter parsed query string by field name",
EnvVars: []string{"URL_PARSER_QUERY_FIELD"},
Destination: &cfg.QueryField,
},
}
}
// Query prints out the query part from url.
func Query(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if parts.Query != "" {
fmt.Println(parts.Query)
}
return nil
}
}

View File

@ -1,43 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestQuery(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
QueryField string
expected string
}{
{
name: "get query",
config: &config.Config{URL: urlString},
expected: "key=value&other=other%20value",
},
{
name: "get query field",
config: &config.Config{URL: urlString, QueryField: "other"},
expected: "other value",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Query(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,39 +0,0 @@
package command
import (
"encoding/json"
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Run default command and print out full url.
func Run(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if len(parts.String()) > 0 {
if cfg.JSONOutput {
json, _ := json.Marshal(parts)
fmt.Println(string(json))
} else {
fmt.Println(parts)
}
}
return nil
}
}
// AllFlags defines flags for all subcommand.
func AllFlags(cfg *config.Config) []cli.Flag {
return []cli.Flag{
&cli.BoolFlag{
Name: "json",
Usage: "output json",
EnvVars: []string{"URL_PARSER_JSON"},
Destination: &cfg.JSONOutput,
},
}
}

View File

@ -1,88 +0,0 @@
package command
import (
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestRun(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get url",
config: &config.Config{URL: urlString},
expected: urlString,
},
{
name: "get url with query split",
config: &config.Config{
URL: urlString,
QuerySplit: true,
JSONOutput: true,
},
expected: `{
"scheme": "postgres",
"hostname": "host.com",
"port": "5432",
"path": "/path/to",
"fragment": "some-fragment",
"rawQuery": "key=value&other=other%20value",
"queryParams": [
{
"key": "key",
"value": "value"
},
{
"key": "other",
"value": "other value"
}
],
"username": "user",
"password": "pass"
}`,
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Run(tt.config)(ctx) }))
if tt.config.JSONOutput {
got := &URL{}
expected := &URL{}
_ = json.Unmarshal([]byte(result), &got)
_ = json.Unmarshal([]byte(tt.expected), &expected)
assert.Equal(t, expected.Scheme, got.Scheme)
assert.Equal(t, expected.Username, got.Username)
assert.Equal(t, expected.Password, got.Password)
assert.Equal(t, expected.Hostname, got.Hostname)
assert.Equal(t, expected.Port, got.Port)
assert.Equal(t, expected.Path, got.Path)
assert.Equal(t, expected.Fragment, got.Fragment)
assert.Equal(t, expected.RawQuery, got.RawQuery)
assert.Equal(t, expected.Query, got.Query)
assert.ElementsMatch(t, expected.QueryParams, got.QueryParams)
return
}
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// Scheme prints out the scheme part from the url.
func Scheme(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if len(parts.Scheme) > 0 {
fmt.Println(parts.Scheme)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestScheme(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get scheme",
config: &config.Config{URL: urlString},
expected: "postgres",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = Scheme(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,21 +0,0 @@
package command
import (
"fmt"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
)
// User prints out the user part from url.
func User(cfg *config.Config) cli.ActionFunc {
return func(_ *cli.Context) error {
parts := NewURLParser(cfg.URL, cfg.QueryField, cfg.QuerySplit).parse()
if parts.Username != "" {
fmt.Println(parts.Username)
}
return nil
}
}

View File

@ -1,37 +0,0 @@
package command
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/thegeeklab/url-parser/config"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
func TestUser(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tests := []struct {
name string
config *config.Config
expected string
}{
{
name: "get user",
config: &config.Config{URL: urlString},
expected: "user",
},
}
for _, tt := range tests {
app := cli.NewApp()
ctx := cli.NewContext(app, nil, nil)
t.Run(tt.name, func(t *testing.T) {
result := strings.TrimSpace(capturer.CaptureStdout(func() { _ = User(tt.config)(ctx) }))
assert.Equal(t, tt.expected, result)
})
}
}

View File

@ -1,17 +0,0 @@
package config
import "errors"
var (
ErrEmptyURL = errors.New("no url provided either by \"url\" or \"stdin\"")
ErrReadStdin = errors.New("failed to read \"stdin\"")
ErrParseURL = errors.New("failed to parse url")
)
type Config struct {
URL string
QueryField string
QuerySplit bool
PathIndex int
JSONOutput bool
}

25
go.mod
View File

@ -1,22 +1,15 @@
module github.com/thegeeklab/url-parser module github.com/thegeeklab/url-parser
go 1.23.3 go 1.16
require ( require (
github.com/rs/zerolog v1.33.0 github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.5
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04
)
require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/sirupsen/logrus v1.8.1
golang.org/x/sys v0.12.0 // indirect github.com/urfave/cli/v2 v2.3.0
gopkg.in/yaml.v3 v3.0.1 // indirect github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616
golang.org/x/mod v0.4.2 // indirect
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 // indirect
honnef.co/go/tools v0.2.2
) )

75
go.sum
View File

@ -1,35 +1,58 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04 h1:qXafrlZL1WsJW5OokjraLLRURHiw0OzKHD/RNdspp4w=
github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4= github.com/zenizh/go-capturer v0.0.0-20211219060012-52ea6c8fed04/go.mod h1:FiwNQxz6hGoNFBC4nIx+CxZhI3nne5RmIOlT/MXcSD4=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096 h1:5PbJGn5Sp3GEUjJ61aYbUP6RIo3Z3r2E4Tv9y2z8UHo=
golang.org/x/sys v0.0.0-20210507161434-a76c4d0a0096/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=

View File

@ -0,0 +1,19 @@
package command
import (
"net/url"
"strings"
"github.com/sirupsen/logrus"
)
func parseURL(raw string) *url.URL {
urlString := strings.TrimSpace(raw)
url, err := url.Parse(urlString)
if err != nil {
logrus.Fatal(err)
}
return url
}

View File

@ -0,0 +1,27 @@
package command
import "testing"
type TestParseData struct {
urlString string
expected string
}
func TestParseURL(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestParseData{
{
urlString: urlString,
expected: urlString,
},
}
for _, table := range tables {
result := parseURL(urlString)
if result.String() != table.expected {
t.Fatalf("URL `%v`, should be `%v`", result, table.expected)
}
}
}

View File

@ -0,0 +1,17 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Fragment prints out the fragment part from the url
func Fragment(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if len(parts.Scheme) > 0 {
fmt.Println(parts.Fragment)
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestFragmentData struct {
urlString string
expected string
}
func TestFragment(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestFragmentData{
{
urlString: urlString,
expected: "some-fragment",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Fragment(c) }))
if result != table.expected {
t.Fatalf("URL fragment `%v`, should be `%v`", result, table.expected)
}
}
}

17
internal/command/host.go Normal file
View File

@ -0,0 +1,17 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Host prints out the host part from the url
func Host(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if len(parts.Scheme) > 0 {
fmt.Println(parts.Hostname())
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestHostnameData struct {
urlString string
expected string
}
func TestHost(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestHostnameData{
{
urlString: urlString,
expected: "host.com",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Host(c) }))
if result != table.expected {
t.Fatalf("URL host `%v`, should be `%v`", result, table.expected)
}
}
}

View File

@ -0,0 +1,21 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Password prints out the password part from url
func Password(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if parts.User != nil {
pw, _ := parts.User.Password()
if len(pw) > 0 {
fmt.Println(pw)
}
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestPasswordData struct {
urlString string
expected string
}
func TestPassword(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestPasswordData{
{
urlString: urlString,
expected: "pass",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Password(c) }))
if result != table.expected {
t.Fatalf("URL password `%v`, should be `%v`", result, table.expected)
}
}
}

40
internal/command/path.go Normal file
View File

@ -0,0 +1,40 @@
package command
import (
"fmt"
"strings"
"github.com/urfave/cli/v2"
)
// PathFlags defines flags for path subcommand
func PathFlags() []cli.Flag {
return []cli.Flag{
&cli.IntFlag{
Name: "path-index",
Usage: "filter parsed path by index",
EnvVars: []string{"URL_PARSER_PATH_INDEX"},
Value: -1,
},
}
}
// Path prints out the path part from url
func Path(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
i := ctx.Int("path-index")
if len(parts.Path) > 0 {
if i > -1 {
path := strings.Split(parts.Path, "/")
if i = i + 1; i < len(path) {
fmt.Println(path[i])
}
} else {
fmt.Println(parts.Path)
}
}
return nil
}

View File

@ -0,0 +1,47 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestPathData struct {
urlString string
pathIndex int
expected string
}
func TestPath(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestPathData{
{
urlString: urlString,
pathIndex: -1,
expected: "/path/to",
},
{
urlString: urlString,
pathIndex: 0,
expected: "path",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
set.Int("path-index", table.pathIndex, "index")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Path(c) }))
if result != table.expected {
t.Fatalf("URL path `%v`, should be `%v`", result, table.expected)
}
}
}

17
internal/command/port.go Normal file
View File

@ -0,0 +1,17 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Port prints out the port from the url
func Port(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if len(parts.Scheme) > 0 {
fmt.Println(parts.Port())
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestPortData struct {
urlString string
expected string
}
func TestPort(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestPortData{
{
urlString: urlString,
expected: "5432",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Port(c) }))
if result != table.expected {
t.Fatalf("URL port `%v`, should be `%v`", result, table.expected)
}
}
}

36
internal/command/query.go Normal file
View File

@ -0,0 +1,36 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// QueryFlags defines flags for query subcommand
func QueryFlags() []cli.Flag {
return []cli.Flag{
&cli.StringFlag{
Name: "query-field",
Usage: "filter parsed query string by field name",
EnvVars: []string{"URL_PARSER_QUERY_FIELD"},
},
}
}
// Query prints out the query part from url
func Query(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
f := ctx.String("query-field")
if len(parts.RawQuery) > 0 {
if f != "" {
if result := parts.Query().Get(f); result != "" {
fmt.Println(result)
}
} else {
fmt.Println(parts.RawQuery)
}
}
return nil
}

View File

@ -0,0 +1,46 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestQueryData struct {
urlString string
QueryField string
expected string
}
func TestQuery(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestQueryData{
{
urlString: urlString,
expected: "key=value&other=other%20value",
},
{
urlString: urlString,
QueryField: "other",
expected: "other value",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
set.String("query-field", table.QueryField, "index")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Query(c) }))
if result != table.expected {
t.Fatalf("URL query `%v`, should be `%v`", result, table.expected)
}
}
}

17
internal/command/run.go Normal file
View File

@ -0,0 +1,17 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Run default command and print out full url
func Run(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if len(parts.String()) > 0 {
fmt.Println(parts)
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestRunData struct {
urlString string
expected string
}
func TestRun(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestRunData{
{
urlString: urlString,
expected: urlString,
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Run(c) }))
if result != table.expected {
t.Fatalf("URL `%v`, should be `%v`", result, table.expected)
}
}
}

View File

@ -0,0 +1,17 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// Scheme prints out the scheme part from the url
func Scheme(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if len(parts.Scheme) > 0 {
fmt.Println(parts.Scheme)
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestSchemeData struct {
urlString string
expected string
}
func TestScheme(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestSchemeData{
{
urlString: urlString,
expected: "postgres",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { Scheme(c) }))
if result != table.expected {
t.Fatalf("URL scheme `%v`, should be `%v`", result, table.expected)
}
}
}

19
internal/command/user.go Normal file
View File

@ -0,0 +1,19 @@
package command
import (
"fmt"
"github.com/urfave/cli/v2"
)
// User prints out the user part from url
func User(ctx *cli.Context) error {
parts := parseURL(ctx.String("url"))
if parts.User != nil {
if len(parts.User.Username()) > 0 {
fmt.Println(parts.User.Username())
}
}
return nil
}

View File

@ -0,0 +1,39 @@
package command
import (
"flag"
"strings"
"testing"
"github.com/urfave/cli/v2"
"github.com/zenizh/go-capturer"
)
type TestUserData struct {
urlString string
expected string
}
func TestUser(t *testing.T) {
urlString := "postgres://user:pass@host.com:5432/path/to?key=value&other=other%20value#some-fragment"
tables := []TestUserData{
{
urlString: urlString,
expected: "user",
},
}
for _, table := range tables {
app := cli.NewApp()
set := flag.NewFlagSet("test", 0)
set.String("url", table.urlString, "test url")
c := cli.NewContext(app, set, nil)
result := strings.TrimSpace(capturer.CaptureStdout(func() { User(c) }))
if result != table.expected {
t.Fatalf("URL user `%v`, should be `%v`", result, table.expected)
}
}
}